← Home
Colophon · How this is built

Colophon

This site is the only thing in my portfolio you can read the source of. Here is what it is made of, and why it is made that way.

0
Front-end dependencies
0
Build steps
128KB
First visit, fonts and all
62
Design tokens

What it is

Eleven pages of hand-written HTML, one stylesheet and one script. No framework, no bundler, no build step: the files your browser receives are the files I wrote. That is roughly 1,770 lines of CSS and 890 of JavaScript for the whole site, and it means there is no layer between a decision and its consequence.

I build this way because I lead teams that ship real software, and I want my own estimate of what a thing costs to stay honest. A framework would make this site easier to write and much harder to understand.

Type and grid

Helvetica Neue for display, at whatever weight the machine has, falling back to Helvetica and then Arial. No webfont for the headlines: the face I want is already on most of the machines that matter, and shipping a substitute for the rest costs more than it returns. Inter at 400 to 600 for running text, and one weight of JetBrains Mono for the uppercase labels, stat captions and index numerals. For a long time those labels were Inter with wide tracking doing an impression of a monospace; they are a real one now, which is the difference between a technical register and the look of one. A twelve-column grid with a 24px gutter on an 8pt scale, capped at 1536px. Sixty-two custom properties hold the type scale, spacing, colour and easing, so a change lands everywhere at once.

The design direction was explored and chosen in Northlit, HangarX's agentic design suite, and then implemented by hand. The system is called Low Anchor.

Motion

Twenty-eight scroll-driven animations, all of them CSS. No scroll library, no intersection observers for the reveals: animation-timeline: view() and scroll() do the work, inside a feature query, so a browser without them simply shows the finished state. The light theme turns dark as you scroll and back as you return; the toggle pins either one.

Everything is gated behind prefers-reduced-motion. If you have reduce-motion on, the site does not animate at all, and nothing is hidden waiting for an animation that will never run.

The assistant

The chat is a serverless function on the Anthropic Messages API, streaming over server-sent events so the answer arrives as it is written. It answers only from a knowledge base kept in the repository next to the code, which is sent as a cached prompt prefix. It is instructed to say when it does not know something rather than guess, and it does not speak to whether I am open to roles.

Two packages, @anthropic-ai/sdk and @vercel/kv, are the only dependencies in the project, and they run in that function. Nothing ships to your browser.

What it does not do

No analytics, no tracking pixels, no cookies, no consent banner, because there is nothing to consent to. I do not know you were here unless you email me.

One external request: two typefaces from Google Fonts. Everything else is served from this domain.

Those typefaces are, by a distance, the heaviest thing here: about 68KB against roughly 60KB for every line of markup, style and script on the site compressed. They are served as variable fonts, which is half what the same families cost as separate weights, and the browser keeps them after the first page. I would rather say that plainly than quote the flattering number.

Hosting and addresses

Static files on Vercel, with the one function for the chat. URLs have no extension and every page declares a canonical, so there is exactly one address for each thing. Every page carries Open Graph tags, structured data and an entry in the sitemap; there is a plain-text summary of the whole site for assistants that would rather read than crawl.

Credits

The Alaska photographs in the Roots gallery are Creative Commons or public domain, credited individually in the gallery and in CREDITS.md. The Google product screenshots on the Google case study come from Google Cloud's public documentation and are Google's, not mine.

If you want to see how any of it works, the source is right there: v3.css and v3.js.