Two levels, 212 primitives and 133 semantic roles, with a second theme whose
only job is to prove the second level was worth its cost. Every swatch below reads the role by
name and resolves it through tokens.css, so nothing on this page can disagree with
the system. The contrast numbers are computed here, in the browser, against the composited
ground.
A raw value is a primitive: --graphite-930 is a number
and says nothing about where it goes. A semantic role says what a colour is FOR:
--bg-page, --text-muted, --outcome-yes. A component reads
a role, never a primitive, and never types a raw value.
Colour is the only axis with two levels, and that is a decision rather than an omission. A radius, a gap or a duration has nothing for a theme to override: 10px is 10px in daylight. A colour is the one thing whose value depends on what else is on the screen, so it is the one thing that needs a name for its job separate from its number.
A rebrand would not prove this. A theme does. Swapping brass for cobalt is a change of primitive and works fine on a flat file with no roles at all. In a theme the ground inverts, the ink inverts, the light and the shade swap places, and the action has to stay the action through all of it. That sentence can only be written if the reason a colour is there is stored somewhere, and here it is stored in the role name.
Painted here and nowhere else in the kit. Nothing in components/
may read one of these directly; a foundations page that hid them would be hiding what the roles
are cut from.
Graphite, 15 steps. The Vault canvas and every stone
Chalk, 9 steps. The pale stone of daylight
Bone, 9 steps. The warm light ink of the Vault
Ink, 5 steps. The dark text of daylight
Brass, 8 steps. The brand metal, and the only hue the product owns
Green and red, 9 and 6. Outcome only, and never decoration
Each ramp carries a dark half and a pale half, and that is the theme living
inside the primitive layer: brass-400 is the metal on graphite and
brass-700 is the same metal on chalk. Same hue, different lightness, because a role
has to clear a contrast floor on whichever ground it lands.
Thirteen grounds, each named for what it is rather than how dark it is. Read them across: the order of depth is preserved and its DIRECTION flips. On graphite a card rises off the page toward the light. In daylight it settles onto it, so the page is the lightest thing on screen, because the thing it answers to is the darkest thing on screen.
Eleven ink roles against five grounds, in both themes: 110 readings. Computed in this page, against the ground composited from the whole ancestor stack, because a role's number means nothing without the thing behind it. The floor is 4.5:1 for the sizes these roles are used at. A cell under it carries a red outline.
Vault, dark
Daylight, light
134 readings, and nothing is under its floor. Two floors, because a
glyph is not a word: 4.5:1 for the text roles and 3:1 for the icon roles, which is
what WCAG asks of a graphical object. The darkest text reading in the Vault is
text-muted on bg-control at 5.49:1; in daylight it is
text-icon on bg-card at 5.06:1.
One role has no headroom: icon-brass in daylight, 3.20:1 on
bg-card. It clears the graphics floor by two hundredths. It is the saved
bookmark and the active mark, so 3:1 is the right floor and it passes, but it is the only role
in the system with nowhere left to go, and any card ground made one step lighter puts it under.
Nothing else on this page is within 0.5 of a floor.
Two things the numbers say that the file did not
The brass ink ladder had four rungs in the Vault and one in daylight, and
on 2026-08-10 it turned out not to be a ladder. The reading that opened it stands:
text-brass, text-brass-lit, text-brass-chip and
text-brass-vol measured 8.98, 11.68, 13.20 and 10.58 on the page in graphite and
7.40 for all four in chalk. Re-read against the REAL composited ground on all 106
painted screens rather than against the page swatch: in graphite 5.70 to 8.98, 11.13 to
11.33, 9.86 to 11.79 and 10.08; in chalk one value on 704 placements, worst 5.24, best
7.40. The steps in graphite are not emphasis, they are the same ink compensating for four
different grounds - a brass-tinted chip, a photograph under a veil, a plate, bare stone -
and on the pale stone one value clears every one of them with 0.74 to spare over the 4.5 floor.
So neither answer the row offered was right: it is one ink with per-ground compensations that
daylight does not need, and two roles may share a value as long as each says so. The fourth
name is gone: text-brass-vol had ONE placement in the product and is deleted,
with the hero's volume tag on text-brass-lit, the role its own plate already
uses.
icon-quiet earns its existence, and the file said so before
the measurement did. It is identical to text-muted in the Vault, to the
hundredth, and separates in daylight: 4.59 against 7.67. A role that is a duplicate in one theme
and not in the other is exactly the case the two-level system exists for, and it is the one
place on this page where the design is doing something a flat palette could not.
This is the one rule that decides others. An accent never borrows the win or lose colour, and a favourite, a focus ring, a selected chip and an active nav item are all brass. A candidate line in a multi-outcome chart is not an outcome, which is why the categorical series exists and is neither green nor red.
Each outcome has a solid, a text, a line and one fill, and the pair below is the whole vocabulary. Note that the solids do not change between themes and the texts do: a filled YES button is a coloured object like a brand mark, and the word YES on a pale tint is ink that has to clear a floor on whichever stone it lands.
The third column is the quiet face and it stopped being a tint on
2026-08-16. There were two fills, --outcome-yes-fill-soft and
--outcome-no-fill-soft, both the same 12 per cent alpha as the fill beside them,
and their only reader was the compact pair in an outcome list. The spectator's pair and that
list both rest on --bg-control now, so the two tokens had no reader left and are
deleted rather than kept: the odds bar carries the outcome colour, the button carries it in
the word. The tint was never doing the reading either - YES moved 8.31 to 8.01 and NO
6.70 to 6.15 in the Vault, and both went UP in daylight. What it was doing was filling 44
pixels of card with an outcome colour, twelve cards deep. The last row uses this column for
something else: a result is won or lost and has no third state, so the cell holds the LOST
fill against the WON fill beside it. That was true of the old header too, and it is written down
now rather than left to be inferred from the word LOST.
The categorical series, and why it is not the outcome palette
A multi-outcome market has five candidates and none of them is a YES or a NO. Reaching for green and red there would say the chart has an opinion about who should win, which is the one thing a prediction market may never say.
It was shown here in one theme until 2026-08-08, and it is the only ramp on
this page that was. The five roles DO theme, cyan-400 to cyan-700 and so on down the row, with
the light values carrying their measured ratios in tokens.css: 6.1, 6.9, 7.3, 6.2,
7.3. A ramp that themes and is drawn once is a ramp showing whichever theme the reader happens
to be in, and saying nothing about it.
Brass appears as a tint far more often than as a solid: a selected chip, a
hover, a saved bookmark, an inset hairline. Six declared rungs, and they are declared
because a tint written as a colour function is invisible to every tool and to every
reader. Ten of them once lived in components/ as
color-mix(in oklab,var(--color-action) N%,transparent), seven at percentages this
ladder does not have, and no sweep could see them because a regular expression reads the
components of a colour function as separate numbers.
Quiet is a colour, not an opacity. The other half of the same trap:
opacity fades text into its background and no sweep reading
getComputedStyle().color can see it, because the colour it reports is the one before
the fade. Measured once: --chrome-muted is 5.03:1 on its panel and
2.37:1 at opacity .55. If a thing should be quieter, it gets a quieter role.
The file says of itself that both themes are "written out below rather than left to inherit, which is the point: a token declared once here would silently hand the dark number to daylight". 134 roles are declared for dark and 97 for light, so thirty-seven are handed over. This paragraph read 133 and 93 until 2026-08-13 and only one of those two was ever right: the light block held 96 the day it was written, so the section title below it counted forty holes where there were thirty-seven, and the three it invented were never listed in the table under it. The count moved again the same day, by the two brass blend roles backlog 120 added and the one dead role backlog 131 deleted, and both counts are read out of the source now rather than out of the sentence before them. Counted, read and classified rather than reported as a defect:
color-action color-action-lit color-action-pressed color-trustoutcome-yes outcome-no and their fills and lines, result-won and its fills and linestext-on-brass text-on-yes text-on-no text-on-photoscrim-photo line-on-photo line-on-photo-hoverchrome-bg chrome-border chrome-text chrome-muted chrome-accent chrome-pressed swatch-dark swatch-lightchart-line-glow mask-solid mask-mid control-knob line-brass-strong tint-brass-60control-knob holds: it is the dot inside the switch when it is ON, the switch's ON ground is --color-action, which does not theme, and white on brass is right in both. line-brass-strong does not. The reasoning was "an alpha over a theming ground is theme-aware by construction", and that is true of the COLOUR and says nothing about the STRENGTH: the light theme's own law is that every brass tint steps up one rung because the same alpha over a pale stone reads about a third as strongly, so an alpha that does not step is an alpha that arrives quieter than it was drawn. Measured 2026-08-12: the amount field's hover edge sat at 1.632 against its inside on chalk where it is 3.519 on graphite, and it tied with --border-brass-hover, which it is meant to be one rung louder than. It reads --brass-a75 in daylight now, the seventh rung, and measures 1.852. The way to know was to read the usage rather than the declaration, and this row is the case where reading the usage was still not enough: the declaration was an alpha, the usage was a hover edge, and the defect was in neither of them. It was in the LADDER, which had no rung left to give.The rule in components/CLAUDE.md is right and its wording is
not. "A state token has a value in both themes or it is not one" was written against holes
that rot quietly, and it reads as forbidding a role that is deliberately constant. What it means
is narrower and should say so: a role whose GROUND inverts must be declared for both
themes. Brass, the outcome solids and the ink on them do not qualify, and they are the
majority of the forty.
Found on 2026-08-12 by asking the browser which selectors in
_page.css match nothing on any of the 57 pages. Fifteen of the answers were
colour-role classes, declared here, worn nowhere, so the roles were invisible to a reader of the
one page whose whole subject is the roles. That is the more interesting half of the finding.
A dead class is a line to delete; fifteen roles missing from the colour page is a colour page that
is not the colour page, and no count could have told the difference, because the class existed
and the file looked complete.
Eight of the fifteen are LINES, which is why they were easy to miss:
three borders, the two outcome edges, the two result edges and the chart's stroke. Every table
above this one draws a role as a filled block, and a filled block is the wrong picture of a 30 per
cent brass hairline. They are drawn as borders and as a stroke here, taking the same
--tk-sw the fills take, so a swatch still carries no value of its own.
Three of the fifteen do not theme and the table says so by drawing them
twice. --text-on-photo stays white in daylight because a photograph is not a
theme surface, and --color-action-lit and --color-action-pressed are
brass in both, for the reason section 05 gives: a brass ground is a coloured object like a brand
mark. Three of the remaining twelve are alphas, --border-brass and
--border-brass-hover step a rung in daylight and are theme-aware by construction as
well, which is the same argument section 07 settled about the forty.
control-knob sits on brass, which does not theme, and
line-brass-strong is an alpha, which is theme-aware by construction. Forty
roles are declared once and thirty-nine of forty were right. A declaration cannot tell you this; only
the usage can.tokens.css. They
paint the stand, not the exhibit, and they are the only reason the semantic section has a
section that is not about the product.