← Back to the screen

Design system portfolio

claude-brand — Recto — the record

The four direction lines behind this page, the generator command that produced its tokens, and the pairs it was measured against.

The translation desk at Halvorsen & Wren, a literary press: chapter seven of a novel out of Spanish, the source and the English side by side, and every open decision in the margin beside the line it is about.

claude-brand is the third house style in this portfolio, after calm-practice and apex-brand, and the one that needed the least inventing. The other two had no vendor and no published values, so their generators solve a palette from a declared ground up. This style publishes everything: a twenty-one step ramp, thirteen named colours, fifteen type steps, a leading ladder, ten radii, four border widths, seventeen spacing rungs, six measures in ch, nine easings and three durations, all of it in its own stylesheet. There was nothing here to make up, which changes what the generator is for.

Where the style comes from

The reference is claude.com. Seven stylesheets carry the whole style — 419,714 bytes, re-fetched 2026-09-16 across /, /pricing and /product/claude-code, which is what it takes to see all seven — and everything in the table below was measured out of those files rather than described from a look at the page.

DecisionMeasured
Facesthree, self-hosted: a sans (28 declarations), a serif (21), a mono (7), each shipping a roman and an italic
Ramp21 steps at 50-unit granularity, L 0.9818 down to 0.1908, plus pure white and pure black at the ends
Ramp hue19 of the 21 steps carry chroma, at hue 95–107; two of them (850, 900) leave for hue 68
Named colours13: one brand terracotta with two darker rungs, and twelve illustration colours named after materials
Type ramp15 steps: 2 display, 6 headline, 2 large body, 3 body, a caption at 12px and a micro at 10px
Fluidevery step above 19px is a clamp(), and all of them are solved between 320px and 1440px
Leadinga named ladder of seven — 1, 1.1, 1.2, 1.3, 1.5, 1.6, 1.7 — where every reading step takes 1.6 and every display step takes 1.1 or 1.2
Trackingsix distinct values in 373 kB, and the display steps are not among them: the largest type is set at normal tracking
Weights300 to 700, plus 480 for a button, which only a variable face can express
Radii2 · 4 · 6 · 8 · 12 · 16 · 24 · 32 · 48 · 64px, and the four in real use are 8, 12, 16 and 24 (21, 19, 21 and 16 declarations)
Borders0.5 · 1 · 1.5 · 2px
Spacing17 rungs, 2px to 200px
Section rhythmthree rungs, 64→96 · 96→128 · 128→200, plus an even half-step at 52→64. The 32→64 clamp beside them is the page's own side gutter, --container-margin, and is not a rung
Measuressix, in ch: 20 · 30 · 45 · 60 · 70 · 80
Holds660 · 960 · 1192 · 1440px
Easingsnine, named by curve rather than by use, the extreme of them cubic-bezier(1, 0, 0, 1)
Durationsthree: 60ms · 200ms · 450ms. .3s appears eighteen times as a literal and never as a declared rung
Shadowsthree, and the largest is 16px of blur at 10% black

The structural finding, and the reason this is a system rather than a palette. Every step of that ramp is warm. The lightest is a paper at hue 95 and the darkest is an ink at hue 107, with a real chroma on nineteen of the twenty-one steps — so a page in this style is not a grey page with a colour on it, it is paper. That is also why neither end of the ramp is white or black even though both are declared: the two pure values sit outside the family and are used for a label on a fill rather than for a ground.

The second finding is about what a published palette leaves out. A ramp is not a palette until something says which step is body text and which is a rule, and the reference's own semantic layer answers that for its pages, not for somebody else's. So claude_tokens.py solves the assignment: each role is the step nearest its limit that still clears its WCAG ratio against the worst ground in its theme rather than against the page background, which is the mistake that ships a secondary colour legible on a card and illegible in the well beside it. Fifty pairs, both themes, none failing.

The three things that came out of doing it that way

  1. The brand terracotta cannot be text on the paper. It measures 2.96:1, which clears neither the 4.5:1 body text owes nor the 3:1 a control boundary owes; its two darker published rungs reach 3.68:1 and 3.85:1 and stop. So a text rung is solved at the same hue and chroma — the only colour in the emitted file the reference does not publish — and the brand colour itself stays a fill and a mark.
  1. White on terracotta is 4.05:1, and the reference puts white on terracotta. The label on the fill is the near-black here instead, at 4.55:1. Darkening the brand colour until white cleared was the other option and it was refused: this style publishes that colour, and moving it to keep a white label spends the thing the style is for. The reference's own primary button token is the far end of the ramp rather than the brand colour, which is the same conclusion reached from the other direction, so the filled control on this page is the ink at 17.50:1.
  1. The illustration palette is theme-independent. All twelve of those colours are lighter than L 0.84, so the near-black is the only label either theme can put on them — 11.42:1 to 13.31:1 — and a note keeps its colour when the page inverts. They are grounds for a mark, never ink: every one of them is under 1.6:1 as text on the paper, which --self-test asserts in both directions so that spending one as a colour is impossible rather than discouraged.

The direction

The hard state

CONTRACT.md §3b asks for the state the archetype makes hardest, and for a split that is non-correspondence: the shape pretends the two sides pair up one to one, and a real translation does not. Three of the six segments on the page are that, drawn as ordinary states of a bilingual document rather than as errors:

And 30 is the one waiting on a person: a term with three defensible renderings and no right answer, each with what it costs, on the page's only ground of its own. Choosing one writes it into the translation, settles the segment, and takes one off the count the chapter head and the delivery panel both report.

Generated

python3 tools/claude_tokens.py --both > systems/claude-brand/tokens.css
python3 tools/claude_tokens.py --check      # 50 pairs, both palettes
python3 tools/claude_tokens.py --self-test

Measured

python3 tools/claude_tokens.py --check — 50 pairs across both palettes, 0 failing. The pairs the page leans on hardest:

What sits on whatCarriesRatio
#141413 on #faf9f5the source, the translation, every heading17.50:1
#4d4c48 on #f0eee6every margin line, on the worst ground7.40:1
#5e5d59 on #f0eee6the quietest label, on the worst ground5.67:1
#141413 on #c6613fthe label on the terracotta mark4.55:1
#ad5030 on #f0eee6every link, on the worst ground4.56:1
#87867f on #f0eee6every control boundary, on the worst ground3.15:1
#141413 on #e3daccthe marked term, both themes13.31:1

Dark theme, the same roles: 16.66:1, 6.82:1, 5.38:1, 5.90:1, 4.51:1, 3.14:1 and 13.31:1.

The decorative rule is held deliberately below the 3:1 a control boundary owes — 2.11:1 light, 2.04:1 dark — so substituting it for a stroke has to be somebody's decision. --self-test asserts that, and asserts that the step one rung past each solved limit fails, without which every pass above is uninformative.

The highlight under segment 30 is solved as a ground rather than as a colour: the faintest wash of the brand colour into the paper that is still visibly another ground, at ΔEOK 0.02, which lands at 6% and carries the body ink at 16.51:1 light and 15.83:1 dark. Solved the other way — the strongest wash the ink still clears — it came out at 100%, which is the brand colour itself, and a highlight the same colour as the mark beside it is one signal doing two jobs.

Browser, both themes: a11y-probe.mjs clean at 1280px with every run catching its control probe; shots.mjs clean at 1440px and 375px, document 1440/1440 and 375/375; drive-probe.mjs drove thirteen controls without a throw, an emptied region or a lost focus. Driven by hand as well: choosing a rendering moves the counts 3 → 2 and 40 → 41 and back on reopen, the balance writes the column header and the segment list to the same three tracks (428/224/428 even, 548/224/308 source-wide), and interrupting the swap mid-exit still lands the last choice, which is the transitioncancel path.

Deviations, and their reasons

Findings kept, with reasons

The logo-swap test, and why its number is high here

tools/distinct-one.sh claude-brand — 22 pairs, worst 0.74 against a ceiling of 0.75, which passes by one hundredth and is worth explaining rather than celebrating. Every point of it is the instrument. Against luxury-hospitality, the pair that scores highest, five of the seven dimensions come back 1.00 — radii and type_steps because CONTRACT.md §1 forbids a literal and both pages are fully tokenised, so the tool compares two empty sets and scores them identical; hero, card_density and bleed because both pages have none of the thing being counted. The one dimension with real content in it, grid_shapes, is 0.17: a two-column bilingual document with a margin track and a photography-led hospitality page do not share a shape. The same holds for centauri at 0.73, where grid_shapes is 0.10.

That is the floor tools/distinctness.sh's own header warns about, and this page sits closer to the ceiling than any other in the set because it declares no literals at all and has no hero, no cards and no bleed to be counted against. Reading the number without the breakdown would report a resemblance that does not exist.

What I am least sure about

Whether the margin should hold the decision at all. The bold thing says every control sits beside the line it changes, and the three renderings with their costs are the most valuable thing on the page — a fourteen-rem column is where they fit worst. They were three cards at twelve rem and every label broke inside its own border; the boxes came off and the margin went to fourteen, which is legible now and still the narrowest column on the screen. The alternative is a panel that opens over the translation, and I think it is worse — it covers the text the decision is about at the moment somebody is deciding — but it would let the three costs be read side by side, and somebody could reasonably prefer that trade.