Colophon.
What this site is built out of, and why each piece is here.
Stack.
- Next.js 16App Router, statically exported
- StyleXCompiled atomic CSS, no runtime
- MDX + ShikiPlain markdown, highlighted at build
- Base UIUnstyled primitives, three of them
- MotionExactly one component earns it
- Geist Sans / MonoDisplay and instrumentation
- Cloudflare PagesServing a folder of files
- nix + devenvCI and my laptop run the same thing
Static by default.
The whole site is output: "export". There's no server runtime, so there are no route handlers that read the request, no server actions, and no server-side searchParams. Every route is a file on disk before anyone asks for it.
That's also why the build is the real test. A static export fails loudly on anything it can't represent, so a green build is what guarantees the site ships. A dev server that renders proves nothing. Same reason search and tag filtering on the writing page run in your browser: there's no server here to read a query string on.
Styling.
StyleX, which replaced Tailwind and a pile of shadcn components. It's a compiler, so the style objects get read at build time and extracted to atomic CSS. Nothing ships a styling runtime.
The catch is that it fails silently. An invalid property, or a style object nothing reads, compiles to no CSS instead of an error. Two lint rules for valid and unused styles are all that stands between a typo and an unstyled element in production. StyleX has no descendant selectors either, by design, so the last hand-written stylesheet on the site is the one for post bodies. Markdown output gives you nothing to hang a style on.
Color and type.
A warm near-black canvas and one amber accent. I computed the contrast instead of eyeballing it. Body text sits at 18.51:1 against the background, the amber at 8.93:1, muted text at 8.72:1, and the hairline borders clear 3:1. That last number does more work than it looks like it should, because this design uses rules where most would use cards. The hairlines are structure.
Geist Sans does the display work and Geist Mono does the instrumentation. Most of the look comes down to the two letter-spacing values. Display type is pulled tight so the counters close up at size. Mono labels are opened right out so they read as machine annotation instead of small prose.
Motion.
Three layers, split by what each tool is good at. Entrances are compiled CSS keyframes, so the prerendered HTML never waits on JavaScript. Turn scripting off and the writing page still lists all 33 posts. Route changes use native view transitions, with the header pinned as a fixed reference point while the content crossfades underneath.
Motion gets used exactly once, on the writing page filter. I tried named view transitions there first and they lost. The search box filters as you type, and every keystroke starts a fresh transition that queues instead of interrupting. A spring just retargets mid-flight. That one component costs 38.9 KB gzipped, and it's the only route that pays for it.
All of it respects prefers-reduced-motion. The reading-progress bar on posts is the one exception, since a scroll timeline drives it instead of a clock. It only moves when you do.
Readable by machines.
Lineage.
The writing here goes back to October 2019 and has outlived a few versions of the site around it, including an Astro one I wrote about in 2023. The posts are markdown in a git repo so that the next rewrite is a rendering problem instead of a migration.