Open this before building a screen. Every token below is shown at the size and on the ground it is actually used on, and the law that governs it is printed beside it — not collected in a list at the end, because the moment someone is about to break a rule is the moment they are looking at the thing.
The ruling ledger is DESIGN.md; it wins any disagreement with this page.
Read it →
Five identity pastels. Each holds one job across the entire product, forever. That fixed assignment is what lets forty screens be derived from eight.
Four verdict states, three of them colours. They stand on paper or white, never on a pastel, and they are never borrowed for a rating, a category, a chart or a tab.
A pastel is never chosen because a card needed a colour. If four study-set cards are four different pastels for variety, the system is already broken — categories are told apart by icon and text.
#F6D9B2
Goals and the path — the journey. Goal cards, goal nodes, the star.
#F2BDC3
Speaking — your voice, the hard modality. The coaching panel in every speaking state.
#CECBF7
Conversation and people. Mei’s scene, the partner turns, anything with a human in it.
#D7E8F6
Listening. Listen-and-choose exercises, model playback regions.
#BCEBD9
The library — study sets, atoms, saved things. Also the world outside the phone frame.
The fourth is the expensive one to forget. Ink is not “correct” — ink is “we did not score this.” The learner skipped the syllable, the mic missed it, or the model returned nothing. A cleared part rendering the same as an unsaid one is a false green, and an invisible one.
#25845F · #D9F0E6
The judged part landed. Outside the analyzer it is also aggregate success — goal complete, take stored, progress fill.
#E8368F · #FDE2EF
Recognisable, a repair remains. Magenta, not amber — amber went brown on warm paper and collapses into red under red-green colour blindness, on the one distinction our trust rests on.
#D44749 · #FDE4E4
A different sound or contour came out. Never used for an operational failure — that is what alert is for.
ink #111015 · locked #F1EFE9
No verdict at all. Ink type, and the bar left on the empty locked track — the absence has to be visible as an absence, which any grade colour would hide.
#B4261E
Something in the room or the wire went wrong. Tool surfaces and system failure only — never on a learner’s score.
Tools have to be able to say capture failed, stop the session. Reaching for FIX red to say it is the most natural mistake in the system and the most damaging: a verdict colour appearing outside a verdict destroys the one rule that makes the analyzer readable. So the alert is a deeper, more desaturated red — unmistakably not the analyzer’s.
Set side by side the difference is obvious, and it has to survive being seen one at a time, months apart, in a bright classroom.
#FBF8F3The learner page ground.#F0ECE4Structural regions — and the tool ground.#F1EFE9The empty track: not earned, not scored, not yet landed.#111015Type, and every active action.#6F6A68Eyebrows and metadata. Not for body text on colour.ink @ 10%The one hairline. Cards and rows use it, nothing heavier.Every row below is set at its real size. 12 is the floor, and it does not move — not for a tool, not for a dense row, not to save a scroll. Pinyin and Chinese are learning content, not metadata: they never shrink to preserve a no-scroll layout; the screen grows instead. Hanken Grotesk for Latin and pinyin, Noto Sans SC for Chinese.
.eyebrow · 12 · 700 · .11emmuted on colour.Three surface levels and nothing else: flat pastel panels for structural regions, white cards with a hairline and one subtle low blur for contained content, black fills for the current action. Expressive, rotated geometry is allowed in exactly two places — onboarding’s tiles and the home goal card. Analyzer panels, tone graphs and dense controls stay regular, always.
.row, .button, .notice..panel, .card..pill, .stat..icon-btn.Mobile gutter is 18 on a learner screen and 14 on a tool screen — that tightening, plus rows in place of cards, is the tool register. There is no colour the tools own.
These live in src/app.css. A thing earns a place there by appearing on three or more
screens; everything else is Tailwind utilities in the markup, where you can see it while you read the screen.
White, one hairline, one low blur. Contained content — the thing you are looking at. This block is one.
A flat structural region, no shadow. Takes a pastel when it is identifying an activity; stays panel when it is just ground.
Ink by default. It takes an activity tint inside that activity’s lesson — never a verdict colour, because a lesson position is not a score.
.on is black, because black is what a control turns when it is active. The three verdict pills are the analyzer’s vocabulary and are only ever used to report a verdict — a track chip, a category or a count never wears one.
Minimum height 52, so it clears the 44 touch floor with room. .mint is the one pastel button and it is library-only — adding a set to your shelf. Everywhere else, the primary action is black.
39px drawn, in a header row that carries the rest of the 44. Back, help, play.
Teacher, rater and admin. Same tokens, same components, tighter spacing, a higher information rate —
an operator reads their screen fast, in a bright room, with a student waiting. This section sits on
panel because that is the tool ground, and the ground plus a header that states the mode in words
is the whole mark. The retired slate-blue staff accent is gone: a sixth colour with one job is a sixth
thing to keep straight, and a roster of student names was never mistakable for a learner screen.
locked.alert, the one place a red appears outside a verdict.Never a placeholder doing a label’s job: a placeholder disappears exactly when the teacher looks up to check what they typed.
Saved is green and it is not an exception: green already carries aggregate success outside the analyzer, and a durability receipt is exactly that — the bytes landed. It says nothing about the speech.
Pending is ink on locked. A take on the phone has not landed, and no screen may say “saved” over audio that is still on a device.
Failed is alert, never FIX red — and it means the bytes did not land, never that the speaking was bad. Capture is not scoring; errors are data and are never marked.
There is no fourth receipt and no discarded state. A bad take’s only escape hatch is a re-record: aborted and false-start takes are corpus data, and no operator can throw one away.
data-grade and data-rollA syllable is scored as three independent parts — initial · final · tone — and the character reports the
worst of them. Rather than scatter that across a dozen classes, data-grade on any element tints its
pinyin and its drawn diacritic together, and data-roll on the syllable drives the grade bar,
the verdict word and the character. One attribute, so
a screen cannot disagree with itself.
good. The bar and the word go green; the character stays ink.
close. Only the tone mark is magenta — the initial and final were fine.
fix. The initial is red, the character follows the worst part.
unjudged. Ink type, a broken mark, and the bar left on the empty track.
This asymmetry is the whole design. Colouring eleven good syllables green to mark two problems buries the two problems — but making CLEAR ink is worse, because ink already means “we did not score this”, and the product would be quietly claiming credit for syllables the mic never heard. So green stays and the emphasis is bought elsewhere: only a part with work left in it colours its character, and the eye lands on the two problems because they are the only characters wearing colour.
The characters join the green in exactly one situation: every part judged and clear. That is not a verdict on a component, it is the aggregate over all of them — so a perfect take is the one time the whole target lights up, and it earns the moment. A take with a dropped syllable has not earned it and is excluded by the mechanic itself.
Rendered from src/app.css — nothing on this page is a redrawn copy, so it cannot drift from
what the screens actually get. If a token here looks wrong, the fix is in that file and this page follows.
Rulings and their reasons live in DESIGN.md.