Build write-up · 5 min read

How I built this site

Thirty pages of plain HTML, no framework and no build step, held to four written specs by nine checks that measure the rendered page: every color a reader can see, every type size, every curve. It is the closest thing to a production codebase I own, and it is how I work.

Role
Everything: the design, the code, the checks that hold them together
Built with
HTML and CSS, Node scripts, and one Chrome that measures every page
Size
30 pages, 3 token files, 4 specs, 25 scripts
Status
Live. Every merge deploys; there is no staging in front of it
The homepage of this site at laptop width: the wordmark and five section links across the top, the portrait in a circle on the left, and on the right the eyebrow Senior Product Designer over the title “Product design for software that got complicated.”, a two-line dek, and two buttons reading Start a conversation and See selected work. Under the pair, six facts in two rows of three, each with a small line icon in a pale disc, and a row of six client wordmarks along the bottom.

My approach

  1. 01Tokens

    • Three token files: color, type, and the page shell
    • Four specs that are the rule, not the record of one
    • A fourteen-step type scale and four fluid ramps between its steps
    Tokens first
  2. 02Pages

    • Hand-written HTML with the shell copied in, not included
    • A third of the markup is comments saying why
    • One generated file, and a check that rebuilds and diffs it
    The pages
  3. 03Checks

    • Nine checks; four of them render every page in a real browser
    • Contrast measured on the resting page and in every forced state
    • Every number the specs assert, recounted from the tree
    What the checks measure
  4. 04Ship

    • A merge is live in about a minute, with nothing in between
    • Guards that refuse rather than warn
    • AI wrote most of the scripts, to rules that came from failures I found
    Nothing ships on a feeling

The problem

A portfolio that says design systems, accessibility and prototypes in real code, and is itself a template with a contrast failure in the hero, says the opposite of what it means. The site had to be built to the standard it claims for the work, or the claim was decoration.

That standard is not a look. It is that the rules are written down, that the page is measured against them rather than eyeballed, and that a change which breaks a rule is refused before a stranger sees it. Those are the three things I ask of a production codebase, and there was no reason a personal site should be held to less.

Tokens first, and the specs are the rule

Three files hold every value the site is allowed to use: the colors, the type scale and the leading and tracking that go with it, and the spacing and radii of the page shell. Nothing is made up in place. A component that needs a size takes one of the steps; a color that is not in the palette is not a color.

Four documents sit over those files and are normative: typography, color, spacing and motion. Where a spec and the stylesheet disagree, one of them is a bug, and it has not always been the stylesheet. All four have been found stale at least once, which is why a check reads them.

  • The type scale is fourteen steps, and every rendered size on every page at four widths is one of them. Headings ride four fluid ramps between named steps, all sharing one viewport window so the ladder stays in order at every width
  • The color spec tabulates every ink on every ground with its measured ratio; the fills and the text steps of the accent are two tokens because the one that clears a 2px rule does not clear small type
  • Motion is six durations and one curve, and a duration that is not one of them is refused by the same token check

The pages, written by hand

Twenty-eight pages, each a plain HTML file with its content in the markup. The header, the navigation and the skip link are copied into every one rather than pulled from an include, so a change to the shell is a change to twenty-eight files, and the site knows it: a check counts how many pages load each stylesheet and holds the documentation to that number.

There is no build step. One file is generated, the Tailwind stylesheet the six oldest case studies still load, and the pipeline rebuilds it and diffs the bytes against what is committed, because that build emits only the classes it has seen and a class the markup names that the build has never met is not an error. It is nothing at all, silently.

About a third of the markup is comments, and they are not what the code does. They are why: which alternative was tried, what it measured, and what sent it back. A rule with no reason attached is the first thing the next change deletes.

What the checks measure

Nine checks run on every merge. Five read the tree and finish in about a second: that it can be deployed at all, that every token the specs name exists, that every number the documentation asserts still counts, that what each page tells a machine matches the page, and that every article's key ideas are still its headings. The other four open every page in Chrome and measure what rendered.

  • Every color a reader can see, at rest: 5,785 measurements across the site, each against its floor. This one reads ink over artwork by the pixel, because a caption on a photograph has no background color to compute from, and it was that caption that once shipped at 3.88:1
  • Every hover, focus and script-applied state, forced: 1,867 state rules, each measured once per page that loads it
  • Every rendered type size at four widths against the fourteen steps: 23,854 of them
  • Every curved corner, for a border that follows only part of the curve: 14,501 elements

The prototype page renders its interesting surface from JavaScript in response to a press, and for a while every check said the page passed while measuring a sixth of it. A registry now names the states a reader can reach and the selectors that reach them; each check presses its way into every one before measuring, and a selector that matches nothing fails the run rather than quietly covering less. Its first pass found a live hover at 1.29:1.

The counts are watched as much as the verdicts. The state check once passed clean at 416 rules and at 617, and the gap was a third of the site going unmeasured. A run that comes back materially smaller means something stopped being measured, and that is worth more than any single pass.

Two things the site does for a reader that a check made honest

Every page of writing can read itself aloud, in the browser's own voice, marking the sentence and the word as it goes. It hands the synthesizer one sentence at a time, because a single long utterance stops mid-word after fifteen seconds in Chrome and reports no position at all in Safari, and it marks the live text with the CSS Highlight API rather than wrapping words in spans, so the text a reader copies is the text that was there before they pressed the button.

Every page carries a canonical address, a share card rendered from the site's own type, and a schema.org graph, sixty-one nodes across the site, all naming one Person. Dates are stamped from the file's last commit rather than typed, so the sitemap and the article can never disagree about when a page changed. A check holds all of it, and it holds the sitemap to listing every page exactly once.

Nothing ships on a feeling

A merge to the main branch is the live site about a minute later. There is no staging host in front of it any more; the branch and its checks are the gate, and what a stranger sees is the merge. That is a reason to make the guards refuse rather than warn.

  • The deploy tars the repository, and one tracked symlink the runner cannot follow loses the whole thing. A check refuses any symlink and any tracked file the ignore rules match, before the upload and before the merge
  • Pull requests are squash-merged, so a branch and the main line diverge by construction. A script decides whether a force-push can lose work by asking, per file, whether the branch's version exists anywhere in the main line's history; it exits 0, 1, or 2 for could not tell, and 2 is not a yes
  • The résumé is rendered from a script, to one Letter page at 1,056 pixels of 1,056. A tailored copy is a list of exact-match text swaps, and a swap that matches zero times or twice refuses to render, because a résumé that silently edited the wrong sentence is worse than one that did not render
  • A green run of the checks does not mean the site updated. Two workflows, failing independently; the instruction is to look for the deploy on the merge commit, and to fetch with a cache-buster before deciding it failed

Since June: 355 commits, 175 pull requests, and a written account under each of why. The account is the product as much as the pages are.

What AI actually did

The scripts were mostly written by an AI working in a terminal, to rules I set, and nearly every rule came from a failure I found first. The pixel-reading contrast check exists because a caption shipped at 3.88:1 and the ordinary check could not see it. The state registry exists because a page passed on a sixth of itself. The token check exists because the specs drifted from the stylesheet three times before anyone read them against it.

What that changed is not who decides. It is how fast a bad idea can be shown to be bad. A rule I would once have written down and hoped to remember is now a script that refuses, and the argument over whether it should exist happened once, in a comment, where the next person can read it.

The design is mine at every step: the scale, the palette, the order of the page, and every sentence. The judgment about what a check should hold the site to is mine. The honest line is the one the other write-ups use, and it is the same here: the thinking happened out loud, with something that answered back, and I decided what was true.

What it cost is worth saying. The site is slower to change than a template would be, because a change that moves a number has to move the sentence that states it, and a change to a shared stylesheet waits for a nine-minute run. That is the price of a claim being checked everywhere it is made, and for a site whose subject is that claim, it is the right price.