Customization
Site settings live in src/config.ts, colours and spacing in src/styles/, fonts in astro.config.mjs. Nothing site-specific is hardcoded in components. For posts, notes and marks, see content.md.
Site identity
src/config.ts, site:
| Key | What it is |
|---|---|
name |
The site’s name: the header, page titles, the feed. For a personal site, usually your name. |
description |
The home page’s meta description and the feed description. |
url |
Your domain, https://…, no trailing slash. Sitemap, canonical links and social cards use it. |
locale |
The page language, en, ko, de … |
author |
Your name in the footer, structured data and social cards. |
defaultOgImage |
The social card for pages without their own: a 1200×630 PNG in public/. |
seo.titleTemplate sets page titles (%s · Your Name). seo.jsonLd says whether the site belongs to a Person or an Organization.
Navigation
nav.header in src/config.ts: a list of { label, href }. The current page is underlined. The name in the header always links home. The footer holds the copyright line and the RSS link: edit src/components/common/Footer.astro.
The home page
gloss in src/config.ts:
| Key | What it does |
|---|---|
intro |
The opening sentence. ==text== highlights, ++text++ underlines in pencil. |
introNote |
One handwritten line under it. '' leaves it out. |
homePosts |
How many recent posts to list. The rest are in /posts. |
startHere |
Pro. A post’s filename (without .md). It is circled in pencil in lists; if it is older than the recent list, the home page adds “new here? start with” and its title. '' turns it off. |
marginNotes |
false renders footnotes as an ordinary list instead of margin notes. |
Colours and other tokens
Every colour is a token in src/styles/tokens.css, written as light-dark(<light>, <dark>). Do not edit that file: override tokens in src/styles/theme.css, which loads after it.
:root {
--primary: light-dark(#7a2e8a, #d6a3e0); /* pencil: links, notes, hand marks */
--mark: light-dark(#bfe3f2, #24485a); /* highlighter */
}
| Token | Used for |
|---|---|
--background |
The paper. |
--surface |
Code blocks and inline code. |
--foreground |
Text and table rules. |
--muted |
Dates, reading times, long notes, struck-out words. |
--border |
Hairlines between list rows. |
--primary |
The pencil: links, margin notes, circles, strike-throughs, focus rings. |
--primary-hover |
Links on hover. |
--mark |
The highlighter behind ==text==. Text on it is --foreground. |
--content-width |
The text column, 40rem (about 65 characters). |
--margin-width, --margin-gap |
The note margin and the space before it (src/styles/gloss.css). |
Run pnpm check:contrast after changing a colour. It checks every text colour against every background, in light and dark, and fails below 4.5:1.
The pencil underline has its own colour
++text++ is drawn with an SVG, and an SVG in CSS cannot read --primary. If you change --primary, copy the --hand-line block from the top of src/styles/gloss.css into theme.css and change the two stroke='%23…' colours (the hex value after %23, which is #). Circles and strike-throughs follow --primary on their own.
Colour presets (Pro)
Three palettes ship in src/styles/. Turn one on by uncommenting its line at the top of src/styles/theme.css:
@import './tokens-sepia.css';
| Preset | Paper | Pencil | Highlighter |
|---|---|---|---|
sepia |
warm | red | green |
garden |
green-grey | green | pink |
graphite |
neutral | grey | grey |
Each preset also sets the pencil underline. Your own rules go below the import and still win.
Dark mode
The site follows the reader’s system setting; the switch in the header cycles system, light and dark and remembers the choice. Set features.darkMode to false to remove the switch (the system setting still applies).
Fonts
astro.config.mjs loads the fonts through Astro’s font API from Fontsource, Latin subset, all under the SIL Open Font License. There are three roles, each a CSS variable:
| Variable | Role | Default |
|---|---|---|
--font-body |
Text, titles and long notes | Gelasio |
--font-heading |
Navigation, dates, labels | Geist |
--font-hand |
Margin notes and handwritten lines | Caveat |
To use another face, change name in the pairings entry and run pnpm dev. For a font that needs a wider character set, change subsets.
Pro has a second pairing: set theme.fonts in src/config.ts to 'literary' for EB Garamond, Instrument Sans and Kalam.
Fonts differ in how big they look at the same size. src/styles/gloss.css sets font-size-adjust per role to the x-heights of the default fonts, so any pairing renders at the same visual size. If your own font looks too large or small, adjust those three numbers.
Search (Pro)
Pagefind indexes every post at the end of pnpm build, margin notes included. The index is written to dist/pagefind/, so search works on the built site (pnpm build && pnpm preview), not in pnpm dev. features.search: false removes the page and the index.
Topics and series (Pro)
features.tagPages builds /tags and a page per tag; with it off, tags print as plain text under a post. Series need no switch: give two or more posts the same series value.
Subscribe form (Pro)
newsletter in src/config.ts. Set username to your Buttondown username and every post ends with a subscribe form that opens Buttondown’s confirmation in a new tab. An empty username removes the form. pitch is the line above it.
For another provider, replace the form’s action and the email field’s name in src/components/pro/Subscribe.astro with the values from that provider’s embed code.
Demo builds. With the environment variable DEMO_FORMS=1 the form sends nothing: the email field has no name, so the address never leaves the page, and the browser lands on /subscribed. Use it for a public demo; never for your real site.
Comments (Pro)
Giscus stores comments in GitHub Discussions. Install the Giscus app on a public repository, enable Discussions, then copy the four values from giscus.app into giscus in src/config.ts. Until all four are set, nothing renders. The widget loads when a reader scrolls to it.
Social cards (Pro)
seo.ogImages: true draws a 1200×630 card per post at build time (/og/posts/<slug>.png): your name, the title, and a handwritten line, which is the post’s note, or else its first footnote. Cards use the current colour preset. A post’s own ogImage wins. The drawing is in src/lib/pro/og.ts.
Analytics
analytics in src/config.ts. Off by default: provider: null renders nothing and the site loads no third-party script.
provider |
id |
host |
|---|---|---|
'plausible' |
Your site’s domain | Your instance’s origin, if self-hosted |
'umami' |
The website ID | Your instance’s origin, if self-hosted |
'ga4' |
The measurement ID, G-… |
Ignored |
Favicon and default social card
Replace public/favicon.svg, public/favicon.ico and public/og-default.png (1200×630).
Next: Deploy