Every convention in DESIGN.md, rendered with the real tokens
Corner’s working look borrows Cloudflare’s palette: Tangerine as the one accent, black type, white grounds, hairline borders. If something looks wrong here, it is wrong everywhere, which is the point.
00 — Brand, working
Corner, and Coach Calvin
Corner is the product. Coach Calvin is the voice. The mark is a corner bracket, open to the top right, with a stroke a fifth of its height; the wordmark is Inter 700 at −0.02em, baseline-aligned, a gap of half the mark height.
Lockup on the card ground
Mark brand-400, wordmark ink-900. Three sizes: 20, 28 and 36px.
On ink-950, and on brand-400
On dark it stays brand-400. On brand-400 it becomes ink-950 — orange on orange is nothing.
Coach Calvin
Coach Calvin
Bot display name, sender of every note
A brand-400 disc with the mark in ink-950, at 24 / 32 / 40px.
The mark alone
Holds down to 16px, which is the favicon. Never outline, round or skew it; clearspace is half the mark height on all sides.
01 — Colour
Generated, not picked
The ramps are converted from Cloudflare's anchors in OKLCH by design/generate-palette.mjs, walked down a fixed lightness scale with a chroma bell, and gamut-clipped. The outlined stop is the anchor, exactly. Never hand-edit a ramp.
brand
Tangerine at 400, exactly — an orange anchors light
50
#FFF3EB
100
#FFE4D2
200
#FFCDAD
300
#FFA96F
400
#F6821F
500
#D76C00
600
#B85C00
700
#9A4C00
800
#813F00
900
#6E3400
950
#3C1900
ink
Cloudflare's type black at 900 — carries a whisper (OKLCH C 0.006) of the orange hue
50
#F8F7F6
100
#EEECEB
200
#DEDCDB
300
#C6C4C3
400
#A9A6A5
500
#918E8C
600
#787472
700
#605C5A
800
#4A4745
900
#313131
950
#1E1B19
danger
Raspberry at 600 — a different hue, so orange never has to mean dangerous
50
#FFF2F2
100
#FFE1E3
200
#FFC9CD
300
#FFA2AC
400
#FF6F86
500
#EA4E6C
600
#CE2F55
700
#B81545
800
#9D0037
900
#87002E
950
#4B0015
success
600 clears AA on white (5.04:1)
50
#DFFFE5
100
#BBFDCA
200
#A5EDB7
300
#84D69A
400
#61BC7C
500
#41A562
600
#07803F
700
#00793A
800
#006530
900
#005627
950
#002E12
warning
Lemon's hue, re-lit for text — 600 is 4.59:1 on white
50
#FFF4DD
100
#FFE7B3
200
#F3D695
300
#DDBC73
400
#C4A14F
500
#AC882E
600
#947000
700
#7F5F00
800
#6A4F00
900
#5A4300
950
#302200
info
Blueberry's hue, re-lit for text — 600 is 4.59:1 on white
50
#F1F6FF
100
#E0EAFF
200
#C7D9FF
300
#A1C0FF
400
#78A2FF
500
#5285FF
600
#366BF5
700
#2455DB
800
#1742C2
900
#0E34AD
950
#010D71
The orange rule
The anchor is a ground and a mark colour, never a type colour. In dark mode the relationship holds unchanged: brand-400 on ink-950 is the same 6.63:1, so there is one orange for both themes.
State never reads as brand
--success, the flat token, on the card ground
--warning, the flat token, on the card ground
--info, the flat token, on the card ground
--danger, the flat token, on the card ground
Four flat aliases flip with the theme — 600s in light, 400s in dark — each with a tinted `*-surface` ground and a `*-surface-foreground` verified against it. A component never hand-picks a ramp stop per theme.
Contrast, verified rather than assumed
| Pairing | Where | Ratio | Grade |
|---|---|---|---|
| ink-950 on brand-400 | the primary button | 6.63 | AA |
| brand-600 on white | links | 4.60 | AA |
| brand-700 on brand-50 | highlight text | 5.66 | AA |
| ink-900 on white | body | 13.01 | AA |
| ink-600 on white | muted — the muted token, AA by design | 4.63 | AA |
| ink-400 on ink-950 | dark muted | 7.08 | AA |
| brand-400 on ink-950 | dark primary — the same orange | 6.63 | AA |
| white on danger-600 | the one solid state slab | 5.05 | AA |
| white on brand-400 | nowhere — the rule that gets broken most often | 2.58 | FAIL |
| ink-600 is the muted token and clears AA by design | 4.63 | AA | |
ink-500 and lighter are for icons, borders, placeholders and disabled states only. The generator prints every one of these on each run, so a regression is visible rather than discovered later.
Chart series, in fixed order
brand
info
success
warning
violet
02 — Typography
Two faces, no display face
Inter for UI, JetBrains Mono for anything the user compares against yesterday. Tabular numerals are on globally wherever a number can sit beside another number — the single highest-value typographic decision in the system. Pick from this scale; don't invent sizes.
Why tabular
Tabular — a column you can scan
Proportional — the same four numbers
03 — The phone
Designed at 390px and grown
Bottom tab bar, flat cards, one orange bar marking the active tab, and Calvin's note where the evening's decisions get made. Content is capped at 640px for the phone-shaped views and 1024px for the ones that carry charts.
Today
Fri 12 Sep
Still to go
1,420kcal
95 g protein to go · target 2,450 kcal
Coach Calvin18:00
This week · 6 of 11 done
Morning note, 06:45
Coach Calvin06:45
Four lines. Report the number, move on.
Photo confirmation
Coach Calvin13:12
At most four buttons, the confirming action first, verbs only, 44px tall.
04 — Components
The numbers that must not drift
Buttons 32 / 36 / 44. Badges 20px pills with a 6px dot. Inputs 40px, 44 under a finger. Table rows 44px, list rows 48px. Radius 6px; pills are the only fully-round thing.
Buttons — variants
Primary is brand-400 with ink-950 text at 600. Destructive renders soft on danger-surface, carries a warning icon, sits behind confirmation and is never the dominant button in a view.
Badges
20px, 12px/600. State badges carry the 6px dot because colour is never the sole carrier of meaning; the two that label rather than report pass `dot=`.
Alerts
A `*-surface` ground, a 25%-alpha state border, a solid state icon. Never a raw vendor error.
Form
Needs a number between 3 and 60.
40px controls, 44 under a finger, labels 13/500. Errors arrive through `aria-invalid`, which draws the danger line and announces the state from one attribute. A ticked box fills brand-400 with an ink-950 tick — never a white one.
Stat cards
Trend weight
100.8kg
0.4 kg this week
Scale read 101.3 today
Fat mass
28.1kg
0.6 kg this week
Lean 72.7 kg, holding
TDEE estimate
3,050kcal
Medium confidence · 16 days
Protein hit rate
79%
22 of 28 days
13px muted label, 32/600 mono value, a delta with an arrow glyph in a state colour, the raw reading as the caption. Trend before scale, always — and there is no “bad” tone to reach for.
Plan slots
A 40px ring, ink-200 track, brand-400 progress, the count in mono beneath. Complete fills solid brand-400 with an ink-950 tick. Never a red ring for missed — a slot that is behind is a slot with an unfilled ring, not an accusation.
Empty state, skeleton, toast
Nothing logged yet
Send a photo of your first meal and Calvin will take it from there.
An empty state says what to do next, never “No entries”. Toasts sit bottom-centre on phones and bottom-right on desktop; they are one of the three things allowed a shadow.
05 — Calvin's voice
Report the number, move on
Most of what the user reads arrives as a message from Calvin, so the copy rules carry more weight here than in a data tool. Never punish. Sentence case. Verbs on buttons. Units on numbers. Trend before scale.
| Say | Never |
|---|---|
| 1,420 still to go | Only 1,420 left |
| 2,610 in. Trend still down. | You went over by 160 |
| Zone 2 today, you slept 5h40 | Poor sleep detected |
| Log it | Submit |
| Send a photo of your first meal | No entries |
The word “still” is fine. The word “only” is not. No red numbers for going over, no streak-breaking guilt, no raw vendor errors.
06 — Charts
The trend is the headline
Trend line in chart-1 at 2px; scale readings as faint dots behind it; the week's target dashed; drinking days as warning bands, so the correlation with a stalled week shows itself rather than being explained.
Weight, last 30 days
Weigh-ins
| Date | Source | Scale | Trend | Fat % |
|---|---|---|---|---|
| Fri 12 Sep | Withings | 101.3 | 100.8 | 27.9 |
| Thu 11 Sep | Withings | 100.6 | 100.7 | 28.1 |
| Wed 10 Sep | Manual | 100.9 | 100.7 | — |
| Tue 9 Sep | Boditrax | 100.4 | 100.7 | 27.6 |
| 7-day change | −0.9 | −0.4 | −0.5 | |
44px rows, 13px muted headers, right-aligned mono numerals, a sticky aggregate footer.