Design system · v10

Everything on this site, and the measurement behind it.

This page is generated from tokens.css by build-design-system.py. Every swatch, value and contrast ratio below is read or computed from the token file at build time — so the gallery cannot claim one thing while the system does another. The written contract is DESIGN.md.

Colour

Ratios are computed OKLch → linear sRGB → relative luminance → WCAG. Text needs 4.5, a meaningful graphic needs 3.0. Both grounds are shown for every token because a value that passes on white can fail on the brand blue — and one of them does.

Ground

--white#ffffff1.00on white5.43on blue
--paperoklch(97.5% .004 252)1.07on white5.05on blue
--paleoklch(96% .028 252)1.13on white4.82on blue
--lineoklch(88% .035 252)1.43on white3.79on blue

The three fields

--blueoklch(53% .205 258)5.43on white1.00on blue
--greenoklch(53% .130 152)4.97on white1.09on blue
--slateoklch(53% .060 262)5.29on white1.03on blue

On the blue

--ruleoklch(82% .09 252)1.74on white3.12on blue
--skyoklch(96% .028 252)1.13on white4.82on blue
--blue-2oklch(47% .205 258)7.00on white1.29on blue

Text

--inkoklch(23.6% .002 68)16.63on white3.06on blue
--mutedoklch(50% .005 78)6.00on white1.11on blue
--ink-navyoklch(24% .1 258)16.58on white3.05on blue

Signal

--warnoklch(52% .17 32)5.99on white1.10on blue
--mute-baroklch(66% .02 252)3.11on white1.75on blue

What the numbers forbid

Four rules that are consequences of the table above, not preferences.

On the brand blue, hierarchy is size and tracking — never tone.

White is 5.43 on --blue and --sky is 4.82. Nothing between them reads as a step.

A muted → lit reveal cannot sit on a saturated fill.

Both states must clear 4.5 and the lit state tops out at 5.43. The scroll wipe is a light-ground effect only.

--line is a hairline, never a bar.

1.43:1. It disappears as a chart element. --mute-bar at 3.11 is the floor, and a value label never takes the bar’s fill.

On the hero cloud, only --ink-navy survives.

A white pill on white cloud measures 1.00:1; a --sky outline measures 1.13:1. Navy clears both grounds at 3.05 and 16.58, which is why the hero buttons and the aeroplane are navy.

Type

Haffer and Brisa Pro are retail faces — buy a licence before a public deploy; the OFL stack renders this correctly but not identically. Orbitron sets the x2 and headline figures only: it has a slashed zero, and “Apache 2.0” rendered as “Apache 2.Ø”.

--t-heroHero headlineclamp(34px,6.2vw,88px)

Building the products healthcare needs.

--t-h2Section headingclamp(28px,4vw,56px)

Three fields. One layer underneath.

--t-sayPull lineclamp(24px,2.7vw,38px)

I kept chunking documents and hoping.

--t-leadLeadclamp(16px,1.35vw,21px)

Six products, all working today.

--t-bodyBodyclamp(15px,1.25vw,17.5px)

Every claim on this site is measured in public, including the runs that went against us.

--t-metaMetadata10.5px

HEALTHCARE · PILOT NEXT

Theming

Components read --accent, never --blue. A product page re-points it once on the page root and everything follows — nav, charts, buttons, rules — with no component touched.

Healthcare
Education
AI

Radius

Rounded rectangles and pills. No blob, no squircle, no inverted corner.

--rclamp(16px,1.7vw,24px)
--r-sm12px
--r-xs3px
--pill999px

Space

--nav-clear is not decoration: the nav is fixed and ends at 66px, while --gutter only reaches 66px at ~1650px wide. Any full-bleed card at the top of a page starts at --nav-clear.

--gutterclamp(20px,4vw,72px)
--nav-clear78px
--sectionclamp(64px,9vw,140px)

Buttons

A label pill and a separate square carrying the arrow. The hero pair is restyled for the cloud band and scoped to .hero, so the closing plate keeps the original.

Marks

One construction: a 48×48 box, fill:none, stroke:currentColor at 2, round caps and joins. Painted as a CSS mask in --accent, so a mark is tinted by a token like everything else. The hero aeroplane is built to the same rule — it was a filled silhouette first and read as a foreign object.

voxtar
aurahealth
coursified
hypatia
mb3prepbot
vectorless
llmgate
programmes
cohort

Motion

Two curves and nothing else.

--ease — cubic-bezier(.19,1,.22,1)

Entering and settling. Long tail, no overshoot.

--soft — cubic-bezier(.4,0,.2,1)

Colour and opacity only.

Reveals fail open.

The hidden state is applied only once JS adds .armed to <html>. If the script never runs, content is visible rather than permanently invisible.

Reduced motion is a layout, not animation:none.

The pinned deck becomes a static stack, the flight parks mid-path, the progress rail is hidden.

The rules that cost the most to learn

Only tokens.css may declare :root or @font-face.

There were two :root blocks and they had drifted — one had --ink-navy and no --warn, the other the reverse. The landing registered only HafferMono-Medium, so every mono weight on that page silently snapped to Medium.

aspect-ratio does not override an <img> height attribute.

A portrait rendered 900px tall. Set both axes in CSS.

mask-size: cover, never 100% 100%.

The latter force-fits: it squashed the hero cloud by 48% at 1440px and 73% on mobile.

url() inside a custom property resolves against the stylesheet that declared it.

Not the document. It resolved to assets/assets/…. Set mask-image inline and keep longhands in the sheet.

CSS transitions do not advance under headless virtual time.

Animations do. An open dropdown measures visibility:hidden 900ms later with the selector demonstrably matching. Suppress the transition before measuring, and never read a value in the same tick you set the class.

Pixel-diff every change to shared CSS.

Moving the tokens out of two :root blocks diffed to 0px on the landing and on a product page. That is what makes it a refactor rather than a redesign.