It looks like eight colours. It is 960 specified values — each with a single job, a rule about where it may appear, and a proof that it can be read.
Our primary is one colour. On its own it can do exactly one thing: be that colour. It cannot be a page background, because text on it would be unreadable. It cannot be a hairline divider, because it is far too heavy. It cannot be what a button turns when your cursor arrives, because there is nothing for it to turn from.
So one colour becomes thirteen, from the palest wash to the deepest ink. Each rung is a separate decision, and each exists because some part of an interface needs exactly that weight and no other.
Twelve of those are the industry convention. The thirteenth — 250 — is ours. Between “barely tinted” and “a surface you can see” the convention leaves a gap: the stripe on alternating table rows, the wash behind a hovered item, the tint on a selected cell. All three want a surface that is present without being a panel.
This is the part that surprises people. The thirteen steps are not thirteen pretty variations to pick between by eye. Each is assigned, and using the wrong one is a mistake in the same way a misspelling is a mistake.
Step 400 exists so a button visibly changes when you hover it. Step 800 exists so tabbing through a form shows you where you are. Step 900 is the only rung allowed to be a filled button, and 1100 the only one allowed to be quiet secondary text. Nobody has to remember this — the system enforces it by only offering the right one.
Pick a colour and you make one decision. Build a ladder and you make thirteen — once, correctly, forever.
Three groups. The foundation is the paper everything is printed on. The brand colours are identity and nothing else — they mean whatever we decide they mean. The four status colours arrive with a meaning already attached: red is not chosen because it is red, it is chosen because it is the one people already read as stop.
Every family is the same thirteen rungs. What differs is the job, not the construction.
the surface everything else sits on
identity — they carry no meaning of their own
each one means something before it is a colour
A customer running Odin under their own identity can repoint all eight. They supply a hue and a chroma; the thirteen rungs are generated the way ours were, against the same contrast targets, and every screen follows — the neutral their surfaces are built on, the primary their main action uses, the red their errors are drawn in.
This works because the rules are written down and the values are not. A palette picked by eye cannot be handed to anyone: nothing in it says why 900 is the button and 1100 is the quiet text, so a tenant reproducing it would be guessing. A palette generated from targets carries its own instructions.
The two brand families are free. The four status ones can move but should not move far: a tenant is allowed to change the red their errors are drawn in, and a tenant who makes it green has changed what the product says, not how it looks.
A dark theme is not a light theme turned inside out. Our primary reads at 4.45:1 against the page it was made for. Drop that same value onto a near-black page and the relationships around it stop holding — the borders vanish, the hover state stops registering, the muted text turns to fog.
So the dark theme is its own thirteen, computed separately and tuned so every relationship that held in daylight still holds at night. The switch at the top of this page is real — press it and watch every value recalculate.
Read down any column. The two swatches are unrelated as colours — one pale, one near-black — and identical in purpose. Step 300 is a tinted surface whether the page is white or black. Step 900 is the filled button. Step 1200 is the text.
One rule flips entirely between the two. In daylight a button gets darker when you hover it. On a dark page that would be invisible, so it gets lighter instead. It is the kind of rule that is obvious once stated and invisible otherwise — nobody looks at a hover state and thinks about arithmetic, which is precisely why it belongs in the system rather than in someone's judgement.
Readable is not a matter of taste. There is a measurement for it — the ratio between text and the surface behind it — and an international standard for what counts as enough. Below 4.5:1, ordinary text becomes hard work for anyone with less than perfect vision, which over time is all of us.
Every combination the system permits was measured: each text value against each background it is allowed to sit on, in both themes. 208 checks, all 208 clearing the standard. Where a value failed, it was recomputed until it passed — not nudged by eye, but solved for the exact lightness that clears the bar while keeping the hue intact.
That includes the pairings nobody thinks to check. Muted text on a pressed surface — a hovered row carrying a secondary label — is the tightest combination the system permits, and it is measured like any other. A pairing that only fails in one state is still a pairing that fails.
A colour can look perfectly fine and measure as a failure. This one does — which is why looking is not the test.
Four points of lightness darker. Indistinguishable as a colour, decisive as a measurement.
The same sentence, on the same page colour, six times. Only the darkness of the text changes. Somewhere around the third line it stops being something you read and starts being something you decipher — and that line is roughly where the standard sits.
This is not a minority concern. The top two lines are unreadable for anyone with a screen in sunlight, anyone on a cheap laptop panel, anyone tired at the end of a day, and anyone whose eyes have started to change — which is everyone, eventually. Designing to the standard is not accommodating an edge case. It is refusing to build something that only works in ideal conditions.
Yellow is the awkward one. It holds its colour only while it is light, and a dark yellow is a brown. Nobody heeds a brown caution.
So warning stays light, and it is the one family in the system whose text runs dark on its solid rather than white — at 5.06:1, comfortably clear. It is the only exception, it is written down, and nobody building a screen needs to know. They ask for the colour that goes on a filled button and the system hands them the right one.
Underneath sits the part that makes the whole thing usable. Nobody designing a screen asks for “sienna, step nine”. They ask for the primary button fill — and the system knows which value that is, in this theme, on this display.
Which means the card below is built once. Every element in it asks for a role, not a colour. Change which family a role points at and everything follows — instantly, everywhere, without a single screen being redrawn.
Nothing in this card names a colour. It asks for a surface, a border, a fill, a label and a piece of quiet text — and the role answers.
one line changed · --color-primary: sienna
Eight families across, thirteen rungs down, every value labelled — and every one changing the moment you switch themes, because these are the same variables the product reads.
And each of those thirteen exists again as a transparent version — the same colour with the background showing through, for when an element sits on top of something unpredictable. A hover state over a photograph. A tinted chip on a coloured card.
These are the fiddly ones. Each is the faintest possible opacity that still lands on the intended colour, and the solution runs in opposite directions in the two themes — light composites downward, dark composites up. Every one is checked against the reference implementation: 72 of 72 exact.
That is 208 values per theme. Add the four extras each family carries — the ink for its filled buttons, its translucent panel fill, and two for progress indicators — and it is 240. Double it for the dark theme, double it again so wide-gamut screens get the richer version they are capable of, and you arrive at 960.
The primary did not change because anyone had tired of it. It changed because it was half a degree of hue from Anthropic's Claude, and a warm clay terracotta on a cream ground is already the most recognisable thing about that product's interface.
Measured in OKLCH, where hue is an angle you can subtract rather than a position in a hex string:
| Colour | Hex | L | C | H | Hue from Claude | |
|---|---|---|---|---|---|---|
| Claude, the logo | #de7356 | 67 | 0.140 | 36.1 | 2.6° | |
| Claude, the button | #d97757 | 67 | 0.131 | 38.8 | — | |
| Claude Code, the button | #c6613f | 61 | 0.138 | 39.0 | 0.3° | |
| Odin primary 900, before | #c96748 | 62 | 0.133 | 38.2 | 0.5° | |
| Odin primary 900, now | #bc5b12 | 58 | 0.146 | 50.2 | 11.4° |
The old primary sat 0.5° of hue and 0.002 of chroma from Claude's button. For proportion: Claude's own two buttons are further from each other than we were from either of them. We were not near their palette, we were inside it.
The new primary moves 11.4° toward the burnt end, drops nine points of lightness and gains chroma. Far enough to be a different colour at a glance rather than one you would have to measure — and it is the direction the pigment itself goes, since sienna is iron oxide burnt until the yellow in it turns.
Eight roles, and most of the values behind them changed. Twenty-three light-mode stops moved, every one because a measurement said so rather than because a colour was reconsidered. This section is here for the migration and comes down once it is done.
| Role | From | To | Why | ||
|---|---|---|---|---|---|
| primary | ember | sienna | The mark keeps its value. The fill sits darker so white text clears AA. | ||
| secondary | dusk | — | The role goes. Secondary is primary at low emphasis, not a second colour. | ||
| info | dusk | dusk | Unchanged. It stops doubling as the secondary button. | ||
| success | sage | sage | The solid moves off a lightness where neither white nor its own text could sit. | ||
| warning | amber | amber | Solid moves up, darkest step moves down, tints come back 35 per cent. | ||
| danger | crimson | crimson | Muted text only, to clear 4.5:1 on the app background. | ||
| accent-1 | midnight | midnight | Reordered and rotated ten degrees, so it stops reading as a second blue. | ||
| accent-2 | rose | rose | The solid moves down far enough to carry a label. | ||
| accent-3 | volt | — | Declared with no default. Volt stays generated for the persisted palette index. |
Secondary stops being a colour. It is primary at low emphasis, in the same four variants — solid, tinted, neutral, transparent. That is why it kept reaching for the info tokens and put the variant names out of step with the rest of the system.
Accent-3 is declared with no Odin default. Volt stays generated, because a palette index is persisted against it, but it holds no role.
The point of all of it is that none of it is visible. Someone building a new area of the product does not choose colours, does not check ratios, does not think about dark mode or wide-gamut displays or what happens on hover. They ask for what they mean, and the answer is already correct.