Skip to content
Mavrick Blackburn

Colophon

How this site is built

A colophon is the page where a magazine credits how the thing was made. This one covers the stack, the host, the type, the color, and the bill. Every choice below had an easier alternative — the reason the harder one won is the part worth reading.

Stack

Next.js 16, and no server behind it

Next.js 16 with the App Router, React 19, TypeScript, and Tailwind v4. Animation is Motion. The interface components started life on 21st.dev and were restyled onto the tokens in section 04 — none of them ship with the palette they arrived in.

The decision worth explaining is output: "export". This site has no server-side logic. Nothing is fetched at request time, there are no accounts, and contact is a mailto: link to mavrick.blackburn@gmail.com. A server would exist here only to hand back the same HTML every time.

Removing it removes an entire class of problem: no runtime to keep patched, no cold starts, no drift between what runs locally and what runs in production, and no adapter sitting between the framework and the host. next build writes a folder of files, and the files are the deployment.

It costs something, and the cost is worth naming. Static export rules out the image optimization endpoint and route handlers. Screenshots are pre-sized and converted to WebP by a script instead — see section 06 — and this site has never needed a route handler.

Hosting

Why Cloudflare, and why the subdomains are flat

The site deploys to Cloudflare Workers static assets with Wrangler. There is no Worker script at all: wrangler.jsonc points at the exported out/ directory and Cloudflare serves those files directly. No OpenNext, no SSR adapter, nothing between the build output and the edge.

Two asset settings do real work. html_handling is set to auto-trailing-slash, because the static export writes about.html rather than about/index.html — this makes /about, /about/ and /about.html resolve to the same file, so no link shape can 404. not_found_handling is set to 404-page, which serves this site’s own 404 instead of a bare Cloudflare error page with none of the navigation on it.

Every project gets a flat subdomain — thing.mavrickblackburn.com, never thing.app.mavrickblackburn.com. That is not a naming preference. Cloudflare’s free Universal SSL covers the apex plus exactly one wildcard level. One level deep is free; two levels deep needs Advanced Certificate Manager at about $10 a month, which is more per month than the domain costs per year. Flat naming is the only reason the SSL row in the next table reads zero.

Adding a project later is a new Worker with a custom domain attached; Cloudflare creates the proxied DNS record and the certificate itself, so there is no wildcard DNS record to maintain. Each project deploys on its own, which means a broken project cannot take the hub down with it.

Typography

Two faces, because they are doing two jobs

Bricolage Grotesque sets everything large — the name in the masthead, section headings, the entry numerals down the left of this page. Public Sans sets everything you actually read in a paragraph, including this one.

Bricolage is a variable display face with visible character: tight, slightly odd, drawn to be noticed. That is exactly right at the size of a headline or one of the numerals down the left of this page, and it turns to noise at body size, where personality reads as friction. Public Sans is the opposite kind of face. It comes out of the U.S. Web Design System and is built to be read for a long time rather than looked at.

Using one face for both would have meant picking which of those two jobs to do badly. Both load through next/font with display: swap and a real fallback stack behind them, so text is visible while the webfont is still arriving instead of invisible.

Color

One color, measured twice

There is one saturated color on this site and it is used at architectural scale — full bands, oversized numerals, whole blocks — rather than sprinkled around as a small accent. Everything else is paper, ink, and a hairline.

Signal

#2B33F0

One value, and one measurement — the ratio holds whether the blue is the mark or the ground, which is why this band can be painted in it and still carry its text. The band uses the token rather than the hex, so it is the real thing and not a picture of it.

  • --paper

    Ground

    #FFFFFF

  • --paper-raised

    Raised surface

    #F7F7F9

  • --ink

    Body text

    #0A0A0B

  • --muted

    Secondary text

    #6A6C78

  • --rule

    Hairlines

    #DEDEE4

  • --on-signal

    Text on signal

    #FFFFFF

  • 7.41:1#2B33F0 on #FFFFFFPassAAA, normal text
  • 19.79:1#0A0A0B on #FFFFFFPassAAA, normal text
  • 5.21:1#6A6C78 on #FFFFFFPassAA, normal text

The ratios above are measured, not eyeballed. Contrast is not a thing you can judge by looking, because the person who cannot read it is by definition not the person doing the looking — you only find out by measuring, and the cost of not measuring is shipping unreadable text without ever seeing the problem yourself.

Every token is declared once, on bare :root. Components read those tokens and never a hardcoded value, so the swatches above are the page rather than a picture of it — and a colour can be changed in one place instead of hunted through the markup.

There is deliberately no dark mode and no theme control. One palette is one palette to get right; the alternative was two of everything — two sets of tokens, two contrast budgets, and a toggle whose state had to survive first paint. That is a real amount of machinery to carry, and this site would rather carry none of it.

Cost

Ten dollars a year, and it does not move

The hub, plus every project site that will ever hang off it, runs on one line item.

The hub plus unlimited project sites, at the prices in force when this page was written.
ItemCost
Domain — Cloudflare Registrar, at cost, no markup~$10/yr
Every additional subdomain$0
SSL, every host$0
Workers static asset requestsFree, unmetered
Total~$10/yr

Static asset requests on Workers are free and unmetered on every plan, which is why the bill does not move when the traffic does. The subdomain and SSL rows read zero because of the flat naming scheme in section 02 — the same table with one more level of nesting carries Advanced Certificate Manager at about $10 a month instead.

Content

Every word lives in one typed file

Every word, link, project, and address on this site comes from src/lib/content.ts. No component holds copy. Adding a project is one entry in one array, and no component is touched.

The types are what make that hold. Optional fields carry the page behavior with them: a project with no url renders without a link rather than with a dead one, and a project with no image gets an oversized numeral in the image well instead of a hole. Status is a union of four values and the visitor-facing labels are a lookup keyed by that union, so a fifth cannot ship without its label — the build stops first.

That guarantee has been collected on. unreleased was added after the fact, once it was clear that most of these were online and usable but had never actually been released, and calling them all “Live” was overclaiming. The compiler refused the new value until it had a label and a chip treatment in both places that render one. A string would have shipped four projects with a blank badge and told nobody.

Project screenshots are captured by node scripts/shots.mjs: headless Chromium, resized, and converted to WebP. That is a script and not a build step on purpose. These are other people’s live sites, and a build that fails because someone else’s server is down is a bad build. When a capture fails, that project falls back to the numeral treatment and the site still builds.

The domain is written down exactly once. Changing it is a one-line edit and every canonical URL and meta tag follows.

The work these decisions were made for is on the projects page.