Colour

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.

212 primitives 134 roles 2 themes 55 claims in the file

Two levels, and colour is the only thing that gets a second one

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.

The primitives, the material

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

950940930920910900880870860850830800780750720

Chalk, 9 steps. The pale stone of daylight

940930920910900880870860750

Bone, 9 steps. The warm light ink of the Vault

080100200250300500600650700

Ink, 5 steps. The dark text of daylight

900700500400300

Brass, 8 steps. The brand metal, and the only hue the product owns

200220250300400500600700

Green and red, 9 and 6. Outcome only, and never decoration

200250300500700800950975050
300400500700800950plum

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.

Surfaces, and the direction of depth inverts

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.

Vault, dark Daylight, light --bg-pagethe ground behind the device
#0f1013
#fcfaf4
--bg-canvasthe device, the cat-nav, the feed
#141619
#f5f3ed
--bg-slabthe outer stone, full page width
#191b1f
#eeece6
--bg-platewhat a control actually stands on
#121417
#f8f6f0
--bg-cardthe floating card
#141619
#f5f3ed
--bg-card-quieta card that is not asking
#15171b
#f3f1eb
--bg-surfacea raised panel
#1c1f24
#f8f6f0
--bg-controlthe box of a button or a field
#24282f
#fcfaf4
--bg-control-hoverthe same box, pointed at
#20242a
#f9f7f1
--bg-control-brassthe same box saying it is the brass one: a tinted ground, not a tint
--border-hairline-brassthe hairline with the same brass in it, and the edge half of the same pair
--bg-chipthe chip's own ground
#1b1e23
#f5f3ed
--bg-wellsunk, not raised: a field, a prompt
#0d0f12
#fdfbf5
--bg-dockthe mobile bet dock
#15171b
#f3f1eb
--bg-pressedthe one state every control shares
#191b1f
#f3f1eb

Every text role on every ground it can stand on

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

bg-pagebg-platebg-cardbg-controlbg-well text-primary
Aa15.44
Aa14.97
Aa14.71
Aa12.01
Aa15.58
text-muted
Aa7.06
Aa6.85
Aa6.73
Aa5.49
Aa7.13
text-icon
Aa10.86
Aa10.53
Aa10.35
Aa8.44
Aa10.96
icon-quiet
Aa7.06
Aa6.85
Aa6.73
Aa5.49
Aa7.13
text-chip-hover
Aa15.44
Aa14.97
Aa14.71
Aa12.01
Aa15.58
text-brass
Aa8.98
Aa8.71
Aa8.56
Aa6.98
Aa9.06
text-brass-lit
Aa11.68
Aa11.33
Aa11.13
Aa9.08
Aa11.78
text-brass-chip
Aa13.20
Aa12.80
Aa12.57
Aa10.26
Aa13.31
icon-brass
Aa8.98
Aa8.71
Aa8.56
Aa6.98
Aa9.06

Daylight, light

bg-pagebg-platebg-cardbg-controlbg-well text-primary
Aa15.78
Aa15.24
Aa14.85
Aa15.78
Aa15.92
text-muted
Aa7.67
Aa7.41
Aa7.22
Aa7.67
Aa7.74
text-icon
Aa5.38
Aa5.19
Aa5.06
Aa5.38
Aa5.43
icon-quiet
Aa4.59
Aa4.43
Aa4.32
Aa4.59
Aa4.63
text-chip-hover
Aa15.78
Aa15.24
Aa14.85
Aa15.78
Aa15.92
text-brass
Aa7.40
Aa7.14
Aa6.96
Aa7.40
Aa7.46
text-brass-lit
Aa7.40
Aa7.14
Aa6.96
Aa7.40
Aa7.46
text-brass-chip
Aa7.40
Aa7.14
Aa6.96
Aa7.40
Aa7.46
icon-brass
Aa3.40
Aa3.28
Aa3.20
Aa3.40
Aa3.43

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.

Green and red are outcome semantics. Brass is the brand

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.

Vaultsolid, ink on itfill, text on itquiet face, text on it YES
YES6.42
YES8.31
YES8.01
NO
NO4.64
NO6.70
NO6.15
brass
Act8.05
Act6.53
Act10.96
won / lost
WON6.42
WON8.31
LOST6.70
Daylightsolid, ink on itfill, text on itquiet face, text on it YES
YES6.42
YES6.51
YES7.69
NO
NO4.64
NO6.57
NO8.05
brass
Act8.05
Act5.60
Act6.21
won / lost
WON6.42
WON6.51
LOST6.57

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.

Vault, darkDaylight, light
12345
12345

The brass ladder, six rungs, and the trap under 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.

Vaulton bg-pageon bg-plateon bg-card tint-brass-06
    06
    06
    06
tint-brass-09
    09
    09
    09
tint-brass-16
    16
    16
    16
tint-brass-30
    30
    30
    30
tint-brass-45
    45
    45
    45
tint-brass-60
    60
    60
    60

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.

Thirty-seven roles have one value, and that is not thirty-seven holes

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:

Class Roles Verdict Brass, 4
color-action color-action-lit color-action-pressed color-trust
Correct, and it is the point of the theme. The ground inverts and the action does not: brass is the brand, and a brand that changed colour in daylight would not be one
Outcome and result solids, 18
outcome-yes outcome-no and their fills and lines, result-won and its fills and lines
Correct. A filled YES button is a coloured object, not ink on a stone. Its text roles DO invert and are declared in both, which is the pair working exactly as designed
Ink on a coloured ground, 4
text-on-brass text-on-yes text-on-no text-on-photo
Correct, and it follows from the row above. If the ground is constant the ink on it must be too, or the pair breaks in one theme
On a photograph, 3
scrim-photo line-on-photo line-on-photo-hover
Correct. A photograph is not a theme surface. It is the same photograph in both
Course chrome, 8
chrome-bg chrome-border chrome-text chrome-muted chrome-accent chrome-pressed swatch-dark swatch-light
Out of scope, and it should be out of the file. This is the roadmap sidebar the course pages carry, not the product. It is deliberately fixed so the stand does not flip with the exhibit
The remaining 3
chart-line-glow mask-solid mask-mid control-knob line-brass-strong tint-brass-60
Named as candidates here on 2026-08-07, settled on 2026-08-08 by reading where they are used, and ONE OF THE TWO WAS A HOLE AFTER ALL. control-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.

Fifteen roles had a class here and no swatch, and the page never said so

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.

Vault, dark Daylight, light --bg-brand-markthe plate under the X and Apple marks
#f2eee4
#211f19
--text-on-photoa heading over the event photograph, on the scrim it is measured against
Aa#fff
Aa#fff
--text-strongthe label of a quiet button on hover
Follow event#fff
Follow event#211f19
--color-action-litthe second stop of every action gradient
#d9b968
#d9b968
--color-action-presseda flat brass ground held down
#a9822f
#a9822f
--color-trustthe trust bar, the USDC 1:1 line, the push banner
#c7a24e
#c7a24e
--outcome-yes-linethe YES edge on a position row. It was the tinted YES button's border until 2026-08-16, when the button took the system hairline; position.css is the reader that keeps it
YES#3f7d55
YES#3f7d55
--outcome-no-linethe NO edge on the same row, same date, same move
NO#8f4841
NO#8f4841
--outcome-no-figurea NO probability read as a number
38%#e79087
38%#863228
--chart-linethe price line on the event chart
#42d18a
#3f7d55
--result-won-linethe WON chip border
WON#3f7d55
WON#3f7d55
--result-lost-linethe LOST chip border
LOST#8f4841
LOST#8f4841
--border-hairline89 border declarations, the default edge
edge#2b2f38
edge#acaaa4
--border-brassthe inset hairline of card, plate, thumb, badge
edgebrass 30%
edgebrass 45%
--border-brass-hoverthe hover border of the quiet button family
edgebrass 45%
edgebrass 60%

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.

What this decides

  1. 1The ink roles clear the floor, and the page says so with numbers rather than with a claim. See the summary under section 04 for the exceptions and what they are.
  2. 2There is no theme hole, and the two candidates named here were wrong. Settled in the consolidation by reading where each is used: 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.
  3. 3The rule about both themes gets its real wording. "A role whose ground inverts must be declared for both", not "every role", which is what the current sentence reads as and what the file's own header claims.
  4. 4The eight course-chrome roles leave 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.
  5. 5Nothing here changes a value. That is step 3e, and this page is the picture it will be compared against.