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.
Measured
The whole thing, in numbers
| What | Measured | How |
|---|---|---|
| Hand-written HTML pages | 36 | find . -name "*.html", excluding the book and print sources |
| Stylesheet, entire site | 22.8 KB | stat -f "%z" styles.css → 22,774 bytes, unminified |
| Runtime dependencies, classic site | 0 | No root package.json, no node_modules, no bundler config |
| Third-party domains serving code | 2 | Google Fonts and Google Analytics. Nothing else is fetched. |
| Interactive pattern demos | 9 | All in patterns/demos.js — 234 lines, 12.9 KB |
| Its imports, requires, fetches | 0 | grep -nE "import |require\(|fetch\(" → no matches |
| Open Graph share cards | 24 | All exactly 1200×630, verified with sips -g pixelWidth |
| Pages carrying a skip link | 31 | grep -rl skip-link |
| Build steps between edit and deploy | 0 | Commit 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.