The Lab · 03 · Teardown

How this site is built

A designer's portfolio is a strange artifact: it is the one deliverable where the medium is also the sample. So here is the build, measured rather than described — what it costs, what it depends on, and what is still missing.

Every number on this page was measured, not estimated. The commands are printed beside the results so you can run them against the repository yourself. Where something is unmeasured, it says so.

Measured

The whole thing, in numbers

WhatMeasuredHow
Hand-written HTML pages36find . -name "*.html", excluding the book and print sources
Stylesheet, entire site22.8 KBstat -f "%z" styles.css → 22,774 bytes, unminified
Runtime dependencies, classic site0No root package.json, no node_modules, no bundler config
Third-party domains serving code2Google Fonts and Google Analytics. Nothing else is fetched.
Interactive pattern demos9All in patterns/demos.js — 234 lines, 12.9 KB
Its imports, requires, fetches0grep -nE "import |require\(|fetch\(" → no matches
Open Graph share cards24All exactly 1200×630, verified with sips -g pixelWidth
Pages carrying a skip link31grep -rl skip-link
Build steps between edit and deploy0Commit to master; GitHub Pages serves the files as written

Decisions

Four choices, and what each one cost

No build step, and therefore no build to break

There is no bundler, no transpiler, no CI pipeline. A file I edit is the file the browser receives. The benefit is that this site cannot rot: no dependency will publish a breaking major, and nothing in it needs a lockfile to reproduce in three years.

What it costs: no tree-shaking, no automatic minification, and cache-busting done by hand — you can see the manual query strings, styles.css?v=ds2, in the source of every page. On a site of this size that is the right trade. On a product it would not be, and I would not defend it there.

Fonts load without blocking the first paint

Two preconnect hints warm the connections, a preload as="style" starts the stylesheet early, and then the real stylesheet is declared for the print medium and promoted to all media on load — so it downloads without blocking rendering. A <noscript> copy keeps it working with JavaScript off.

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload"  as="style" href="…&display=swap">
<link rel="stylesheet" href="…&display=swap"
      media="print" onload="this.media='all';this.onload=null">
<noscript><link rel="stylesheet" href="…&display=swap"></noscript>

One type scale, written down, enforced by refusal

Every size on the classic site comes from twelve custom properties — --fs-eyebrow through --fs-display-xl — declared once in styles.css and documented in a 174-line spec in the repository. The rule in that file is one sentence: if a value isn't in this file, it doesn't ship. Roughly twenty earlier one-off sizes are listed explicitly as retired, so a stray font-size: 17px reads as a bug rather than a preference.

Writing this page is what caught the drift described at the bottom of it.

The documents are the same HTML, printed

The portfolio PDF and the résumé are not designed twice. They are HTML pages with an @page rule and millimetre units, printed to PDF by Chrome — which is why a wording change reaches the PDF in one step. The ATS-readable .docx is generated by a 107-line Node script using the docx library: the only npm dependency anywhere near this project, and it is offline tooling that never ships to a browser.

One honest exception

The book view does use React

"Zero dependencies" is true of the classic site you are reading and false of the book edition, which is a React application — around 120 KB of my own code plus React and React DOM, self-hosted from the repository rather than a CDN, so the page still has no third-party runtime.

Two views of one portfolio, built two ways, was a deliberate exercise: the classic site proves I can work without a framework, and the book proves I can work with one. It also means I have opinions about both that came from shipping, not reading.

What's missing

The gaps, before you find them

A teardown that only lists strengths is a brochure. These are real, and they are the next things I would fix.

  • There were no automated tests at all until this week. The repository contained zero test files and an empty CI directory; verification was a manual checklist — every page at two widths, no overflow, console clean. The 42 assertions behind trustlayer.js are the first tests in this codebase, which is a genuine gap being closed, not a strength.
  • No continuous integration. Nothing runs those tests on push yet. A GitHub Action would take twenty minutes and is the obvious next commit.
  • Cache-busting is manual. Version query strings are bumped by hand, which means they can be forgotten — and have been.
  • Two third-party domains still gate the first paint. Self-hosting the four font files would remove Google Fonts from the critical path entirely. It is on the list; it has not been done.
  • A token had drifted out of sync with its own spec. The design system documented --fs-display while the stylesheet defined --fs-display-xl — a rename that never reached the document. Found while writing this page, and fixed in the commit that published it.