Typefaces

Each family has one job. None of them does a second one.

FamilyWeightsJob
Bodoni Moda600 / 700Display. A claim I am making — headlines and the proof-band numbers. Never data, never labels.
Archivo400 / 500 / 600Structure. Body copy, navigation, buttons, h3 — everything functional.
IBM Plex Mono400 / 500Data. A verifiable fact: dates, locations, counts, scope notes, breadcrumbs.
Silkscreen400Pixel. A credential or a status token, at 10px. Never a full sentence.

Colours

Six, and no additions. Tints are derived with color-mix() rather than invented, so every shade on the site traces back to one of these.

TokenValueMeaning
--ink-plate#0B0D14Base canvas and every dark band.
--press-paper#F2EFE6Text on dark bands. Also the inverted editorial band, which the home page no longer uses.
--indigo-signal#5B5BF0Actionable: links, focus rings, primary CTA fill.
--cyan-scan#35E6FFA relationship between data points: hairlines, data labels, the scanline.
--brass-nouveau#C9A227Victorian ornament only, never above 12% opacity.
--graffiti-red#FF3B2EThis is live right now. Three uses per page, no more.

One exception, and it is deliberate. --indigo-signal measures 3.88:1 against the dark band and 4.35:1 against paper — both below the 4.5:1 that body-size text needs. Link text therefore uses a derived mix of it (5.77:1 and 5.04:1), while the focus ring keeps the pure token, where 3.88:1 clears the 3:1 a non-text indicator requires. Accessibility wins over the palette.

What each device means

Every visual device is assigned one job and appears only where that job applies. You should be able to work the rule out without being told. Two consequences follow literally: a download button is never glass, and an in-page card never gets the brutalist offset.

DeviceMeansAppears on
Bodoni displayA claim I am makingHeadlines only
IBM Plex MonoA verifiable factDates, locations, counts, scope notes, breadcrumbs
Silkscreen pixelA credential or a status tokenCertification badges, the availability pill
Graffiti strokeThis is live right nowHero headline, current role, contact CTA — exactly three
Brass filigreeA chapter boundaryOnce per page, at the one chapter break
Cyan hairlineA relationship between data pointsStat separators, timeline spine, tag borders
Glass surfaceFloats above the page and stays on itWhat I Do cards, certification cards
Brutalist offsetTakes you away from the pageRésumé download, case-study links, external certificate links
Indigo signalActionableLinks, focus rings, primary CTA fill

The budget this site holds itself to

When an effect cannot be built inside these numbers, the effect is cut rather than the budget raised.

MeasureLimit
Added JavaScript for motion≤ 45KB gzipped over the no-motion build
Cumulative Layout Shift≤ 0.02
Interaction to Next Paint≤ 200ms
Mobile Largest Contentful Paint≤ 2.0s
Long tasks during scrollNone over 50ms
Simultaneous ambient animationsAt most two, and none while the tab is hidden
Animatable propertiestransform, opacity, clip-path, stroke-dashoffset — nothing else
Body copy contrast≥ 4.5:1. Large display ≥ 3:1.

Two checks that run on every build

scripts/verify-assets.mjs resolves every referenced file against a real directory listing and fails the build on anything missing or mis-cased. It reads the directory rather than calling fs.existsSync, which returns true for the wrong case on Windows and macOS and then 404s on a case-sensitive host.

scripts/verify-tokens.mjs fails the build on a raw colour, a raw duration, an easing curve, a font-family declaration or a forced override outside the token layer. One-off values are how a system quietly rots into a dump three edits from now.