Brand kit
Rendered by the product, from the product's own stylesheet. If a swatch here is wrong, the interface is wrong the same way.
The mark
A container with one route leaving it — a diagram of the
product rather than a metaphor for it. The wordmark is constructed on a stem grid rather than set
in a typeface: stem 11, x-height 44, round letters
drawn as true circles, sidebearing 12 measured from each letter's widest part. Never re-set it in a font, never stretch it, and keep the mark in
--brand with the wordmark in --ink.
Colour
Every value is a custom property and nothing below the token block names a colour directly, which is what makes both themes real rather than one being an inversion of the other.
GROUND
--paper
The page
--raise
Cards, inputs — a step toward the reader
--sunk
Code, console output — a step away
--rule
Borders that divide
--hair
Borders inside a thing
INK AND ACCENT
--ink
Body text
--dim
Secondary — cool, biased toward the accent
--brand
Links, primary controls, the mark
STATE
Three colours meaning one thing each, kept separate from the accent so that "brand" and "good" are never confused.
--permit
Allowed, running, verified
--hold
Waiting on a person or a machine
--refuse
Refused, failed, destructive
Type
Two faces, two jobs. A grotesque for anything a person wrote; a monospace for anything a machine did — hostnames, policy, sequence numbers, states, keys. This product's content is machine output, and setting it in the same face as the prose is what made every page read as undifferentiated grey. No webfont: a font from a CDN is a third party who can stop your sign-in page from rendering.
Agents you can leave running
17px lede — the sentence under a heading
15px body, 1.6 line height, measure capped near 66 characters
13px mono — allow gmail.googleapis.com read
11PX LABEL, .13EM TRACKED
Components
One primary per view. Destructive is never filled — a red button large enough to be the obvious target is a trap.
The dot on a waiting pill breathes while something is genuinely in progress, and only then. Motion that never stops stops meaning anything.
A shape where the content will be, rather than the word "Loading" — so the page does not jump when the data lands.
Motion
One curve and three durations. A system with five of each reads as five different products. The curve leaves fast and arrives slowly, which is what makes an interface feel answerable rather than springy — nothing here should look like it is enjoying itself.
| Token | Value | For |
|---|---|---|
| --ease | cubic-bezier(.2,.7,.2,1) | Everything |
| --fast | 110ms | Pressed, focused, hovered |
| --base | 200ms | A message arriving, a card lifting |
| --slow | 340ms | Content entering the page |
All of it is decoration, so all of it stops under
prefers-reduced-motion. Someone who asked their system to stop moving things asked
for a reason; the interface still works, it simply arrives already there.
Voice
Say what happened and what happens next. Never announce a state without saying whether the reader should wait or act — "stopped", in red, next to nothing else, was accurate and useless. Name things the way a person would: an agent is waiting on you, not blocked pending approval. Errors say what went wrong and what to do. No exclamation marks, no apologies, no congratulating anyone for filling in a form.