The whole case, in under a minute.

Problem

Strong work, badly ordered.

The old gallery set every project at the same level and left a reviewer to piece my career together. It looked like a polish job, but polish without hierarchy only makes clutter more convincing.

What I found

I never lacked evidence.

The job was making the strongest work easy to find without flattening the range behind it.

What I decided

Depth on demand.

Home carries the hiring read: the flagship projects lead into full case studies, supporting work stays compact, the career is one list on the homepage with “The full story” folded under it, and the resume is one PDF.

The live homepage on a phone: Design. Build. Prove. set large, then my name, the role, years in UX, where I work and the button to schedule an intro call
The live homepage work section on a phone: no heading, straight into the Precisely Developer Portal card with its outcome, eight to three steps to test an API, then the ThoughtSpot mobile card

What I decided

Built like a product.

Shared navigation, theme-aware media, route metadata and release checks keep every project page inside one system, so no page drifts off on its own.

The live Precisely case study on a phone: the title naming the outcome, one paragraph, a grey button to the live portal, then the case film

What I decided

I deleted a particle engine that worked.

Claude built a 1.49 MB particle engine that hit its frame-rate target. I deleted it because it explained nothing. AI will build anything I describe, so knowing what to delete is the job.

See the heroes I cut
A cut hero direction: the Design. Build. Proof. statement over a painted sky of sunlit clouds
Clouds: the background became the main character.
A cut hero direction: the same statement with faint embossed tool icons scattered between the words
Embossed tool icons.
A cut hero direction: the same statement with colourful tool and emoji stickers between the words
Tool stickers.

What shipped

Mistakes I made once.

Each gate exists because an earlier release let that mistake through.

Color and target size

Recomputes contrast for every color token pair in both themes against WCAG AA, and requires interactive text controls to be at least 24px tall. It caught four real contrast defects when it was introduced.

64 checks

Asset weight

Fails on any image over 300 KB, any unconverted raster, and any source model left where it would be deployed. Added after the homepage was found shipping 8 MB.

13.9 MB of 14 MB budget for all images across the site

Social cards

Requires every route to ship its own social card, drawn from the site’s own type and color tokens and held under 400 KB. Added after every route was found sharing a single image, so a shared link never showed the page it pointed at.

20 routes covered

The Runable case study’s social card: its headline, A promise, not a prediction, set in the site’s type beside a screenshot of the product

Alt text

Fails on alt text that names a section and a counter, a filename, or a bare noun. Added after 32 images shipped with placeholder alt like “Research overview 4,” which passed the accessibility gate and still told a screen reader nothing.

30 alt texts in the source checked

Open the full build report

The build report

Generated on 2026-10-22 from the same build data that feeds the gates above.

Build chain

  1. generate-journal-metadata
  2. tsc -b
  3. check-decisions
  4. build-report
  5. vite build
  6. postbuild
  7. prerender
  8. check-glass
  9. check-a11y
  10. check-weight
  11. check-og
  12. check-seo
  13. check-alt
  14. check-escapes
  15. check-voice

The other gates

Decision record integrity
9 records verified

Requires every published design decision to include considered options, positive and negative consequences, and a kill criterion. A decision cannot ship as a preference with the cost removed.

scripts/check-decisions.mjs
Flat surfaces
0 blurred surfaces

Fails the build if any compiled CSS blurs the backdrop, so glass cannot creep back in through a refactor or a utility class.

scripts/check-glass.mjs
Search and sharing
23 routes read

Reads every page’s built HTML the way a crawler does, and fails on a missing or duplicate title, a description outside 120 to 160 characters, a canonical that disagrees with og:url, a card of the wrong size, JSON-LD that does not parse, or a page without exactly one h1. Added after the sitemap was found listing a noindex page and the structured data pointing at a card that no longer existed.

scripts/check-seo.mjs
Rendered copy
0 literal escapes or em dashes

Fails on visible escape sequences and em dashes in source copy. Comments are ignored, so the gate checks what readers can actually encounter.

scripts/check-escapes.mjs
Practice log voice
0 generated-writing tells

Fails when a log entry uses the phrases that most often give generated writing away: filler transitions, stock openers, and vocabulary nobody says out loud.

scripts/check-voice.mjs

Looking back

Next, I watch real reviewers read it.

Next time I’d put the site in front of people who hire much earlier. The checks verify the rules I encode; they can’t tell me whether a reviewer understood the work, or whether every accessibility issue has been found.

There was no brief, so I took on what nobody asks of a portfolio: a social card for every route, and a build that checks contrast, alt text and its own copy before anything ships. I talk each change through out loud, Claude Code writes it, and I review every change.