Visual tone — pure white, flat rows, one Tag, a colour budget
📄
Proposal, round 5 — pure white, flat rows, a rebuilt block, one TagRound 5 (2026-10-09): owner rulings — pure white, flat rows, rebuilt block, top-down type, one Tag, compact row, hero-only glyphs; web deferredOwner 2026-10-09: "if a brand block leads the home tab, the list items below should be more subtle … transition to flat layouts with more toned-down navigation and items, like the club detail pages … T1 should be pure white … the brand blocks should be better designed; their alignment, actions, distribution of components within the blocks are all out of place … top components should have the most weight … chips and badges are essentially the same component … the session card has too many visual attention seekers … T5: I like the alternative (tiers of information) … web after mobile." Three lanes (H flat rows and blocks, I tags and hierarchy, J emoji and glyph tiers) measured dozens of peer frames (32 list frames in lane J alone) and the design systems of Seed, Toss, Polaris, Carbon and GOV.UK; every number is in docs/design/research/list-surfaces-spec-2026-10-09.md. Colour values come from the colour ledger and do not change: round 5 changes where colour and weight may sit.What the rulings changed
•T1 Pure white, flat rows under the block: one canvas, #FFFFFF, on hubs, lists and details; the row is flat (no card, no fill, no divider, spacing only) in the club-detail grammar; 홈 compact row 48 tile / 72 pitch, 번개 찾기 discovery row 72 photo / 96 pitch; list scopes become underline tabs in ink.
•T0 The block, rebuilt: one frame (margin 16, radius 16, padding 16, left-stacked, chevron 16 at α0.8, no pill, no qualifier chip, no button inside) and three anatomies of 120, 146 and 124pt; the numeral leads, labels are 14/700 at α0.8.
•T2 A top-down ladder: the time at 16/600 first, the venue 14/500, the meta 13/500 in secondary, no bold below L1; the 18/700 promotion of round 4 is deleted.
•T3 One passive Tag (8px rectangle, two tiers, seven tones) and one Choice control (outlined white pill, ink-filled with a check when selected); facts are MetaSegments text.
•T4 The compact row: five objects (photo, time, venue, meta line, one trailing element) against 12 to 14 today; the bookmark and the bar move to the detail page (D6), the live ribbon becomes a trailing danger Tag (D7).
•T5 Glyphs only at the hero tier: three glyph-led facts in lucide strokes, text-only rows beneath; the 21-glyph vocabulary shrinks to four concrete glyphs.
•T7 and T8 stay: the specimen grid now shows the underline tabs on list surfaces and the segmented control only as a settings or wizard control; the neutral weak Tag fill is gray100 #F0F5F1, the control track $borderSubtle. The 1440 frame leaves the board: web adaptation follows the mobile grammar.
Carried from rounds 3 and 4
•Round 4 (research-adjusted, 11 values changed, 2 claims dropped): control track #DFE7E1, hairline #CAD4CC, meta 13/500 in $textSecondary, a second non-colour cue on every control state, success teal, warning a bright fill with ink, court trio, tiers and series re-derived for colour-vision deficiency. Round 5 changes none of these values.
•Round 3 (T7, the colour budget): brand for one action and one block, controls in ink, content hues for tier, level, court and club.
Open for the owner (colour ledger §4 and spec §6)
→D1 Success turns teal. #00796A is the hue of the shipped 모집 중 accent badge, so open and paid would share a colour. Recommended: success teal (paid, confirmed, win); 모집 중 moves to the brand-weak tint, an invitation tied to the primary action.
→D2 Computed hexes need eyes. #6B4A00, #8D3D00, #83971F, #014B40 and #9A0F3A are computed, not judged; a device look is owed before tokens ship.
→D3 Court colours change meaning. Clay becomes brown-orange and grass olive so neither equals error red or brand green; hard becomes cyan. The court dot is a category code.
→D4 The dark block #015228 is carried by chroma (1.8:1 against the card). If it does not read on an OLED device, the dark hub drops the block and keeps the numeral.
→D5 Tertiary text only at 16px/500 and above; the 13 to 14px tertiary sites move to secondary: a sweep, not a token change.
→D6 The bookmark leaves the row (it exists on the detail header). This overturns the 2026-09-08 "bookmark on every card" ruling; fallback: a 22px outline glyph on the photo’s top-right corner (T4 alternative).
→D7 The live ribbon becomes a trailing danger Tag with the pulse dot on compact lists. This touches the 2026-09-08 carve-out; the full-bleed band stays on the detail anchor.
Eight questions on final-state phones, T0 to T5, T7 and T8 (T7 sits after T4 because it restates the chrome of T0 to T4; T8 follows it because it extends the budget to status and content colours); T6 is a Now note. Nothing is built until the owner marks it.
Recommended pathT0 — The hub block, rebuilt: one frame, left-stacked, pressable, no button inside
1Owner 2026-10-09: "the brand blocks should be better designed; their alignment, actions, distribution of components within the blocks are all out of place … top components should have the most weight." Measured against 12 peer blocks (Toss 토스뱅크 header, 카카오뱅크, Monzo, Cash App, 당근페이, 테니스프렌즈): margin 15.5 to 16.3 in 5 of 7 contained blocks, radius 16.8 to 17.1, padding with a median near 17, left-stacked in 8 of 12, a numeral and a meta line, at most one action; blocks that hold numeral, meta and action measure 138 to 149pt (16 to 18% of the frame).
2What was wrong in the round-4 draft (shown at the left of the anatomy panel): 첫 시작 with a clock glyph pushed to the right and bottom-aligned (padding-bottom 4) against a numeral on another baseline, a qualifier pill beside the date, radius 20, no cue that the block is pressable; 기록 carried a qualifier pill with a tier dot in the eyebrow and nothing on the right; 프로필 a pill under a 28pt name and a wrapping row of two icon-and-text facts. All removed.
3Rebuilt: one frame (margin 16, radius 16, padding 16, left-stacked, solid $primary, white ink, no gradient, illustration or shadow) and three anatomies of 120, 146 and 124pt. The top component carries the most weight: the date is the eyebrow, the count (36/700) or the ELO (40/700) is the answer, the labels are 14/700 at alpha 0.8 (#CCE2D7, 4.76:1; at 70% it is 4.01:1 and fails AA), lime only as the progress fill (4.64:1 on the block). The block is pressable: a 16px chevron at α0.8 ends the eyebrow row (Toss 토스뱅크 header) and nothing sits inside, so the one coloured action on a screen stays the FAB or the CTA band. 7 of 9 peers with a container do hold a button, so this is a budget choice; the only action target is the 프로필 block’s 32px ghost-inverse edit icon.
4It must read as the screen’s answer, not as an ad. NN/g eye-tracking found a coloured rectangle on a white page “totally ignored” when it looked like a promo, and a right rail holding 25% of the content drew 0.8% of fixations (banner blindness); so a plain numeral and plain labels, no gradient, no illustration, no button. Rule: exactly one filled brand object larger than 2.5% of the frame per screen. The salience mechanism (Wolfe and Horowitz 2017) is also why the tab bar, the tabs and the Choices are not brand.
5Alternative: a pale mint block (#DCF3E4, L* 93.8, C* 11.3; 카카오뱅크’s #E1F3FF hero is L* 94.9). The page stays lighter and the green stays ink, but the block adds shape without mass: L* 94 against the canvas’s 100, where the solid block is L* 40.
Owner's word needed → A solid green day block on 홈 — yes?
The day is the block (120pt): the date as the eyebrow with the chevron, the count a 36/700 numeral with 개 on its baseline, 첫 시작 left under it. Below it a section header and two compact rows (T1). Measured: 24.2% saturated, 99% of it brand-hued; outside the block 0.1% and 0%.
기록 (146pt): ELO | NTRP in the eyebrow, the 1,482 numeral at 40/44, the optic progress fill on its alpha 0.2 track (4.64:1 on the block), the caption last. The scope row is underline tabs; the stat strip and the ranking are flat sections. Measured: 18.9% saturated, 98% of it brand-hued; outside the block nothing is saturated.
프로필 (124pt): avatar 56, name 24/700 with the level Tag beside it over one sub line, the 32px ghost-inverse edit icon at the top-right, the trust row last. Today 프로필 is ground="canvas" already; the other hubs join it. Measured: 14.0% saturated, 99% of it brand-hued; outside the block nothing is saturated.
Block anatomy, 1:1 (the phone’s 288pt inner width), pt values
Round 4 draft, withdrawn
9월 19일 (토)내일
일정2개
첫 시작 09:00
107pt · radius 20
Datea qualifier pill sits beside it
Count일정 + 36 + 개 on one baseline
First startpushed right, bottom-aligned by 4, with a clock glyph
Actionnone; nothing says the block is pressable
홈 · day
내일, 9월 19일 (토)
2개
첫 시작 09:00
120pt · 14% of 844
Eyebrow14/20 · 700 · α0.8 with the chevron 16 at the row end; 4 below
Numeral40 row: 2 at 36/40 · 700, 개 at 15/600 on the baseline; 4 below
Meta14/20 · 700 · α0.8, left under the numeral, no glyph
Progress6 high: optic fill on a white α0.2 track; 8 below
Caption14/20 · 700 · α0.8
프로필 · identity
김
김민준3.5
강남 테니스 클럽3년 차
신뢰 등급신뢰 멤버
124pt · 15% of 844
Row 56avatar 56 + 12 + name 24/32 · 700 with the level Tag beside it over one sub line 14/500 · α0.8; 12 below
Actionone 32px ghost-inverse edit icon, top-right: the block’s only action target
Row 24trust tier 14/700; the chevron opens the tier explainer
Shared frame: margin 16 · radius 16 · padding 16 on all sides · left-stacked · solid $primary (dark #015228) with white ink · no gradient, illustration or shadow · a chevron 16 at α0.8 marks it pressable · no pill, no qualifier chip, no button inside · 24 to the section header (or the first row), then 8 to the first row. Peers: margin 15.5 to 16.3 in 5 of 7 contained blocks; radius 16.8 to 17.1; blocks holding numeral, meta and action measure 138 to 149pt (카카오뱅크 138, Monzo 139, 테니스프렌즈 141, 당근페이 149).
⚖️The 10 to 18% band is measured on peers, not a target (ledger 6, 24): hubs carry 12.1 to 18.4% saturated area (Monzo 18.4, USTA 17.8, Wise 14.4, 플랩 13.6, 테니스프렌즈 12.1), lists at most 2.5% in 9 of 17 Korean apps. The rule that replaces it is a count: exactly one filled brand object above 2.5% of the frame per screen (here the block; on a detail page the CTA band). Measured on this board’s phones (C*ab above 30, headless render): 홈 24.2%, 기록 18.9%, 프로필 14.0% against 2.7% on today’s 홈. Outside the block 홈 paints 0.1%, 기록 0.0% and 프로필 0.0% (the red bell badge, status hues, the optic Tags and the tier dots). The 홈 phone shows two rows, so its block is 24% of that frame; at a full 844pt viewport the same 120pt block is 14%. A hub with content area in other hues (photos, charts, medals), which these mocks do not draw, would read lower in brand share.
Recommended pathT1 — Pure white, flat rows under the block
1Owner 2026-10-09: "if a brand block leads the home tab, the list items below should be more subtle … transition to flat layouts with more toned-down navigation and items, like the club detail pages … T1 should be pure white." One canvas, #FFFFFF, on hubs, lists, feeds and details; the block is the only coloured surface. The row is flat: no card frame, no fill, no divider, separated by spacing alone, in the club-detail grammar (FlatColumn: $4 rail, $6 section seam, a full-bleed hairline between SECTIONS and never between peers).
2Why it fits the peers: two-line rows with a 31 to 36pt leading visual have a 59 to 65pt pitch (Toss 59, 카카오페이 60, Monzo 63, 카카오뱅크 63 and 65; median 62.6), and our club-detail rows are already 64. Today’s session card is about 147pt plus a 16pt gap, 163 in all, which matches only commerce thumbnail rows (당근 147, 배민 162). The 홈 compact row is a 48 tile (r8), time 16/600, venue 13/500 in secondary, ONE trailing element (6/8 at 13/700, or one Tag when a state outranks it); 72 pitch, 4 objects, 0 glyphs. The 번개 찾기 discovery row is a 72 photo, time 16/600, venue 14/500, a meta line 13/500 via MetaSegments (format | band; the NTRP prefix drops on rows because the band is a number range beside the format and the detail prints it in full), one trailing element; 96 pitch, 5 objects.
3Navigation follows: the scope control of every list surface becomes underline tabs in ink (Toss, 카카오뱅크, 배민, Playtomic use ink tabs); the segmented pill stays a form control in settings and wizards. 홈 drops its 이번 주 / 다음 주 / 전체 row: the block answers "tomorrow" and 전체 보기 opens 경기. Section actions are secondary with a chevron; the one coloured action stays the FAB or the CTA band.
4Honest limits: peer pitches come from curated App Store frames at ±4%; every thumb-led peer list (당근 116, Naver 88, 배민 121) draws a hairline, and we follow the two-line median instead (no hairline), because our Divider law keeps dividers between sections only; if the owner wants a hairline on the 96pt discovery row it is one line in the row. 72 and 96 are the smallest peer-supported sizes, not optimums; nothing was viewed on a device.
Owner's word needed → Pure white with flat rows under the block — yes?
홈: the block, 24, a section header, 8, then compact rows (48 tile, time 16/600, venue 13/500 secondary, one trailing element, 72 pitch). 4 objects per row. Measured: 24.2% saturated, 99% of it brand-hued; outside the block 0.1% and 0%.
번개 찾기: underline tabs in ink, day groups, discovery rows (72 photo, time 16/600, venue 14/500, meta 13/500 via MetaSegments, one trailing element, 96 pitch). 5 objects per row. Measured: 1.1% saturated, 0% of it brand-hued.
basement withdrawn 2026-10-09 — rows are flat, so no card step is needed.
Gate that locks it: the page ground is #FFFFFF on every screen, the hairline on white is 1.4 to 1.8:1 (#CAD4CC is 1.52), every control fill is at least 1.2:1 on white, and a passive Tag may sit at 1.10:1 because its label carries it (today the ground is #F8FBF9 and fills measure 1.09 to 1.18, so the current theme fails and the canary proves the gate bites).
Recommended pathT2 — Top-down ladder: time first at 16/600, then venue 14/500, then meta 13/500
1Owner: "top components should have the most weight." In 8 of 9 peer session rows the first body line is the heaviest; Seed steps 16 to 13 and Material 3 16 to 14 between the headline and the supporting text; our ListRow is 14/600 over 14/500 with no size step, and today’s card is a 16/600 venue over a block of equal 14/500 lines, so time, format, tier and fee weigh the same. Round 4’s fix (promote one mid-card fact to 18/700) put the heaviest line SECOND, an inversion no peer has, and is deleted. NN/g: users read about two words per item and scan the left edge; on a day-grouped list the venue repeats down a day and the times never do, so the time leads (3 of 4 grouped peers).
2The ladder: L1 16/24 · 600 $text (the time, or the name of a club or a person) → L2 14/20 · 500 $text (the venue; 13/18 · 500 $textSecondary on the compact row) → L3 13/18 · 500 $textSecondary via MetaSegments. No bold below L1; 4px title to sub; 12px row padding; ONE trailing element at 13/700 (a count) or a Tag. On an ungrouped list (a club’s 일정) the day prefixes the time.
3Floors, per role: text 4.5:1 on its real ground, and at 14px and below weight 500 or more (or 6.5:1 at weight 400). 13/400 is the weakest cell in the system and stays withdrawn: Pretendard’s Hangul stem measures 0.85 device px at 1x (ledger 8). $textTertiary #626C64 is restricted to 16px/500 and above; the 13 to 14px sites move to secondary (D5: a sweep, not a token change). Size has the strongest record for drawing the first glance; no head-to-head study of weight against size exists.
Owner's word needed → Time first at 16/600, then venue 14/500, then meta 13/500 — yes?
Four facts at one weight under a 16/600 venue; the count shouts as loud as the time
Recommended
강남 라켓클럽
다가오는 일정
전체 보기
9/19 (토) 09:00올림픽공원 테니스장
복식3.0–4.0
4/8
9/19 (토) 19:00대치유수지체육공원
복식3.0–4.0
참석
9/26 (토) 14:00양재 시민의숲
단식3.0–3.5
2/4
클럽 정보
정기 모임
매주 토요일
멤버
42명
활동 지역
강남구
L1 16/24 · 600 $text
L2 14/20 · 500 $text
L3 13/18 · 500 $textSecondary
A club’s 일정 in the flat column: L1 the day + time at 16/600, L2 the venue 14/500, L3 the meta 13/500 in $textSecondary, one trailing element, 4px between lines, 12px row padding. 5 objects per row. Measured: 0.0% saturated, 0% of it brand-hued.
The type roles on every recommended phone, and the contrast of each on white (WCAG 2)
Role
Size / weight
Token
On white
L1 · the time, or the name of a club or a person (the heaviest line)
16/24 · 600
$text #0D100E
19.13
L2 · the venue on a discovery row
14/20 · 500
$text
19.13
L2 · the venue on a compact row; L3 · the meta line (MetaSegments)
13/18 · 500
$textSecondary #4F5851
7.38
The one trailing element: count, amount, Tag
13/700 count · 15/700 amount · Tag 12/600
$text · Tag tones
19.13
Section header and its action
17/24 · 700 · action 13/600
$text · $textSecondary
19.13 · 7.38
Day header and its qualifier
15/700 · 13/500
$text · $textSecondary
19.13 · 7.38
Block eyebrow, meta, caption (on the brand block)
14/20 · 700
white at α0.8, #CCE2D7
4.76 on #006D37
Tertiary: 16px and up, or non-essential strings only
Recommended pathT3 — One Tag: chips and badges are the same component, selection is a Choice
1Owner: "chips and badges are essentially the same component." Today there is one Badge (75 files, 12 variants, 4 used) plus at least 9 hand-drawn pill labels, and the same mint recipe paints passive labels and a selected control; about 19 of 39 neutral badges print a fact. Seed, Polaris, GOV.UK and Carbon bar clickable tags, and six of seven design systems have exactly two label tiers; NN/g names non-clickable items that look like buttons as a failure.
2Recommended: ONE passive `Tag`. An 8px rectangle in two tiers (weak: tinted, for repeated rows; solid: over photos), seven tones (neutral, brand, success, warning, danger, info, level), 22px (sm) or 26px (md), 12/18 at 600 weak and 700 solid, a label of 1 to 2 words, at most 2 per object. A fact is never a Tag: it is MetaSegments text. Selection is a different thing and stays a control: `Choice`, an outlined white pill, 1px $border and a 14/500 label at rest, ink-filled with an inverse 700 label and a leading check when selected (the check is the second cue). The shape cue is the whole distinction: a tinted rectangle is a label, an outlined pill is a control.
3Filters and scopes on list surfaces are underline tabs, not chips; Choice remains for the rest (menu, range and input modes absorb DropdownChip, RangeChip and RemovableChip). The migration map is in the spec: Badge to Tag, 19 fact badges to MetaSegments, SelectionChip (58 files) to Choice, with five lint rules each carrying a known-positive canary.
Owner's word needed → One passive Tag (8px, two tiers) and one Choice control — yes?
Today: a mint filter chip that is selected, mint pills that are only facts, a tier range pill, a status pill: the same recipe means a label and a control
Choices at the top (the selected one ink-filled with a check); flat club rows with a name at 16/600, a meta line of region | band | members via MetaSegments and, only where it applies, one Tag (모집 중, the brand-weak tint). 3 objects per row (avatar, name, meta line), 4 with the Tag. Measured: 5.5% saturated, 4% of it brand-hued. The saturated share is the three 56px club avatars in their identity hues, a content colour with the monogram printed on it.
Before
강남 라켓클럽
올림픽공원 테니스장
모집 중
👤내 상태
참석
📅일시
9월 19일 (토) 19:00
📍장소
대치유수지체육공원
🎾종목
복식 번개
🎯대상
NTRP 3.0–4.0
💰참가비
8,000원
Session detail: chips on standing and tier, a pale status chip
Recommended
강남 라켓클럽
올림픽공원 테니스장
모집 중
9월 19일 (토) 19:00
대치유수지체육공원
8,000원
내 상태
참석
종목
복식번개
대상
NTRP 3.0–4.0
Tier is a value in MetaSegments text and the hero carries its three glyph facts (T5); only 모집 중 (brand, solid tier) and 참석 (weak success with a check) are Tags. Measured: 0.1% saturated, 100% of it brand-hued.
Tag: 7 tones x 2 tiers, 8px rectangle, 12/18, sm 22px and md 26px
Tone
Weak
Solid
Weak fill / ink
Ink
Solid fill / ink
Ink
For
neutral
호스트
호스트
#F0F5F1 / #4F5851
6.69
#4F5851 / #FFFFFF
7.38
role, pending, metadata
brand
모집 중
모집 중
#C8EED2 / #006D37
5.12
same
5.12
모집 중, invitation (D1)
success
완납
완납
#CBEBE5 / #014B40
7.95
#00796A / #FFFFFF
5.33
완납, 참석 확정, win
warning
마감 임박
마감 임박
#F5B301 / #0D100E
10.32
same
10.32
마감 임박, 입금 필요
danger
취소됨
취소됨
#B8332E / #FFFFFF
5.91
same
5.91
LIVE with the pulse dot, 취소됨, overdue
info
안내
안내
#E8DCFB / #4B0082
9.91
#4B0082 / #FFFFFF
12.95
informational
level
3.54.0
#C6E84B / #0D100E + 1px $border
13.71
same
13.71
NTRP / tier numeral, a 22px circle
sm 22md 26outline
참석마감 임박LIVE
Passive: no onPress, no aria-pressed. At most 2 Tags per object; 3 or more in one container is a lint finding. A fact is never a Tag (MetaSegments text). The outline variant is for neutral on a tinted surface. The neutral weak fill is gray100 #F0F5F1 at 1.10:1 because the label carries it, where control tracks (#DFE7E1, 1.26:1) must stay visible.
Choice: the one selection control, a full pill, 32px with 44px hit slop
하드Resting: 1px $border outline, 14/500 $text
전체 145Selected: ink fill, inverse 700 label, leading check
클레이Disabled
강남구Menu mode (replaces DropdownChip)
NTRP 3.0–4.0Menu, selected value
모집 중Removable (replaces RemovableChip)
The shape cue is the whole difference: a tinted 8px rectangle is a label, an outlined white pill is a control; the selected pill adds a check so colour is never the only carrier. List scopes and filters on list surfaces are underline tabs, not Choices.
Recommended pathT4 — The compact row: five objects, one accent only on exceptions
1Owner: "the session card has too many visual attention seekers … drop the bottom row." Inventory of today’s card for a browse viewer: tile, venue, bookmark, three emoji and three texts, fee, status word, bar, count = 13 objects (+1 corner chip) in a card of about 130pt, against a peer median of about 7; three accents compete beside the photo (emoji colour, badge fill, bar fill). 0 of 11 peers draw a capacity bar in a row and 4 of 11 show a save glyph.
2Recommended: five objects — a photo (the venue’s OWN photo, else the map tile in the same box, 72px with r8), the time, the venue, one meta line, one trailing element. The compact 홈 row drops the meta line (4). Gone from the row: the bookmark (it exists on the detail header; D6 overturns the 2026-09-08 "bookmark on every card" ruling), the capacity bar (the detail hero stat), the price (canon already puts it on the detail cost row; the card is drift), the court dot, weather and host role. The live ribbon becomes a trailing danger Tag with the pulse dot (D7 touches the 2026-09-08 carve-out; the full-bleed band stays on the detail anchor). The only accent a row may carry is that one trailing Tag, and only on exceptions: LIVE, then 참석 / 예비 / 호스트, then 마감 임박.
3Alternative (the D6 fallback): the same row with the bookmark kept as a 22px outline glyph on the photo’s top-right corner — a sixth object, no text, and it reads on any photo with a dark underlay. A photo leads a row only when it is the venue’s own: NN/g eye-tracking found look-alike thumbnails draw 0.9 fixations against 4.4 on text, so generic court photos would add load, not speed (ledger 13). 10 of 19 category session cards are photo-led (descriptive). Optic level Tags stay where a level belongs to a person: under the avatars on the roster.
Owner's word needed → Five objects per session row; bookmark and bar move to the detail page — yes?
번개 찾기 today: the live card wears a full-bleed band; every card repeats a bookmark, three emoji, a fee, a status word and a bar. 13 objects per card, plus the band.
Five objects per row: the live session’s trailing slot holds a danger Tag with the pulse dot (D7), the others hold their count; the bookmark, bar, price and court dot are gone (D6). The today dot in the day header is optic with an edge. Measured: 0.7% saturated, 0% of it brand-hued.
Same row with the D6 fallback: a 22px outline bookmark on the photo’s top-right corner, filled when saved. Six objects. Measured: 0.7% saturated, 0% of it brand-hued.
Recommended
강남 라켓클럽
올림픽공원 테니스장
9월 19일 (토) 09:00
올림픽공원 테니스장
무료
참가자6/8
민
민준3.5
서
서연3.0
지
지호4.0
하
하은3.0
도
도윤3.5
수
수아3.5
Roster: a 22px optic level Tag with a hairline edge under each avatar, empty spots outlined; 6 of 8 reads as slots. The hero carries the three glyph-led facts (T5). Measured: 1.1% saturated, 0% of it brand-hued.
Objects per row: 12 to 14 today, 5 on the discovery row
Today’s card (browse viewer): 13 objects, plus 1 corner chip on the tile1 tile2 venue3 bookmark4 to 6 three emoji (clock, ball, target)7 to 9 three texts (time, format, tier)10 fee11 status word12 capacity bar13 countWeight steps in view: 600 title, 500 facts, 600 badge, 600 count, the bar fill, the emoji colour: three competing accents beside the photo. Peer median is about 7 objects (Smash 6, 당근 6 to 7, Flap 7, Spond 7, Greenlight 7, Band 8, Munto 8, TennisFriends 9); 0 of 11 peers draw a capacity bar in a row.
The discovery row at 1:1: 5 objects
1
20:002
반포 한강3
남복3.0–4.0
4
6/85
1 photo or map tile 72 · 2 time 16/600 · 3 venue 14/500 · 4 meta 13/500 via MetaSegments · 5 the one trailing element (count, or a Tag when a state outranks it). The compact row on 홈 drops the meta line: 4 objects. Height 96pt against about 163.
Where each leaver goes
bookmarkthe detail header, where it already exists (D6)
capacity barthe detail hero stat
pricethe detail cost row (canon already says so; the card is drift)
live ribbona trailing danger Tag with the pulse dot (D7); the band stays on the detail anchor
format and tier chipsone text line of MetaSegments
court dot, weatherthe venue row and the detail
host rolea Tag, only when it is the trailing element
fact glyphsnone in rows; three at the hero (T5)
Recommended pathT7 — Colour budget: brand for one action and one block; controls in ink; content gets its own colours
1Owner 2026-10-09: other major apps use colour to draw attention and restrain from it when it is not necessary; we use too much of the primary colour everywhere and not enough colour on content. Lane D measured 72 app rows and our own frames. It is right about hue and role, wrong about quantity: our saturated area is 1.4% of the frame against a peer median of 5.1%, but 96% of it is one green (peers’ own brand hue: 50% raw, 61% of solid fills), 10 of our 11 frames carry a single hue family (peers: median 2, Korean mainstream 3), and 66% of our 307 brand-token sites are chrome: the segmented pill, tab bar, chips, bookmarks, section actions, avatars and empty-state discs, where 12 of 17 inspected peers use ink or neutral.
2We fill with mint where peers fill with grey: pale-green fills are 3.5 to 4.2% of our hub frames against a peer median of 0.9% (neutral grey 0.04% against 5.2%). Meanwhile the content palette is built and unspent: optic has no consumers, the five tier hues never reach the tier chip, club identity hues fall back to mint, and brand #006D37 and success #14804A are ΔE 6.7 apart, so one green means brand, selection and paid at once (T8).
3Recommended: six roles. Brand solid for the screen’s one action and one hero block; brand as text only for inline links, underlined (brand against ink is 2.95:1, under the 3:1 of WCAG technique G183, and NN/g: underline is essential when the link colour is green). Ink and neutral for every control state, each with a second cue besides colour: the list scope is an UNDERLINE TAB (a 2px ink underline + a 700 ink label against 500 secondary), the active tab-bar item a filled glyph + ink + 600 label, the Choice ink-filled with a check, the saved bookmark outline to filled, the section action $textSecondary with its chevron (a link styled as static text costs +22% time and +25% fixations in NN/g’s tests; the chevron is the signifier). The segmented control survives only as a settings or wizard form control. Semantic colours for status only, optic for level, content hues small and on identity.
4Honest limits: no study shows ink controls are faster than brand ones; the case is the salience budget and peer practice (Seed’s segmented control and chips, Carbon, Apple HIG: “prefer a monochromatic tab bar when content is colourful”, “prefer filled symbols”). Our current brand chip does not fail WCAG 1.4.1 (it has weight and a border), so this is a budget move, not a conformance fix. The focus ring stays 2px $primary with offset. Alternative: keep the brand on the active tab only (4 of 17 inspected peers: 테니스프렌즈, Monzo, Spond, TeamSnap); everything else neutral as recommended.
Owner's word needed → Brand for one action and one block, controls in ink, content colours for tier/level/court/club — yes?
Today's 기록: green pill on a mint track, green tab, green ELO and stat numerals, mint tier chips and avatars, green section link, green progress on mint. Measured: 3.3% saturated, 100% of it brand-hued.
Controls in ink, each with a second cue: underline tabs for the leaderboard scope, a filled ink tab glyph with a 600 label, optic level Tags with an edge, tier dots beside their printed names, neutral avatars, a $textSecondary link with its chevron, an underlined brand inline link. The ELO block is the only brand solid (this screen has no primary action). Measured: 18.6% saturated, 96% of it brand-hued; outside the block 0.5% and 24%.
Same phone, but the active tab keeps the brand on its filled glyph and label: one more brand touch per screen. Measured: 18.7% saturated, 96% of it brand-hued; outside the block 0.6% and 37%.
The six roles, with the final hexes (ledger §2)
Aa
1 · Brand solid#006D37 · $primary, white ink 6.48:1 (Lc 87); dark #70D5A2, block #015228Exactly one filled brand object over 2.5% of the frame per screen: the hero block, or the one action (the FAB, the CTA band). Toggle on, selected date disc.
Aa
2 · Ink#0D100E · $text · $textSecondary #4F5851 (7.38:1) · dark ink #DFE4DCEvery control state, each with a second cue besides colour: underline tab, Choice, saved bookmark, numerals, section action. $textTertiary only at 16px/500 and above.
3 · Fill, line, tint#DFE7E1 · $borderSubtle, the control fill (1.26:1) · #CAD4CC · $border, the hairline (1.52) · #F0F5F1 · gray100, the passive neutral Tag fill (1.10)One control fill, one line, one passive tint. The Tag fill is gray100 because its label carries it (every peer row pill sits at L* 96). $surfaceStrong #E5EBE7 and the 1.26:1 line are retired.
4 · Semanticsuccess #00796A / text #014B40 · warning fill #F5B301 (ink) / text #6B4A00 · error #B8332E · info #4B0082Status only: paid, confirmed, win, deadline, cancelled, validation. Never a control. One meaning per colour, a glyph and a label on every Tag (T8).
3.5
5 · Optic, level#C6E84B · $optic, $text on it 13.71:1 (Lc 84)The level Tag (1px hairline edge on white: Lc 19 there), the today dot, the progress fill inside the ELO block. Never ground, never text, never beside amber.
6 · Content huestier #B2580B #8E8E93 #D9A00F #9B59B6 #9A0F3A · court #089AC3 #8D3D00 #83971F · series #3566B8 #8D3D00 #83971F #883170 #089AC3Small, on content identity, always beside a printed name: a tier dot (8px+), a court dot (12px, on venue rows and the detail), a chart series, a calendar dot, a club avatar hue. Never on a control.
Every control and content part the phones use, at size: today, then recommended
Before, today
전체
내 경기
Segmented control · pill-nav.tsx: the scope control of every list surface today
Choice · selected: ink fill + inverse 700 label + leading check; resting: 1px $border outline
호스트모집 중
Neutral Tag · tag.tsx: weak fill gray100 #F0F5F1 + $textSecondary (6.69:1); the control track stays $borderSubtle #DFE7E1
Saved bookmark · save-button.tsx, now on the detail header: outline to filled
전체 보기
Section action · section-header.tsx: secondary + the chevron (the signifier)
점수는 경기 결과로 바뀌어요. 계산 방식 보기
Inline link, in a sentence: brand + underline (brand vs ink is 2.95:1, under G183's 3:1)
DE
강남
Person and club avatars · avatar-bubble.tsx, club-logo-bubble.tsx
Empty-state disc · empty-state.tsx
Capacity bar · progress-bar.tsx, now the detail hero stat: recruiting brand, 75% $warningText, full info
골드실버
Tier chip · tier-badge-utils.tsx, tier.config.ts: the tier name is always printed
3.5
Level Tag and court dots · new $levelChipBg, visual-accents.ts (the court dot lives on venue rows and the detail, not the session row)
⚖️The gate, check:color-contrast extended (ledger §3, revised for one canvas and the Tag). Asserted on the theme files; the canaries fail on today’s values.1 · Canvas: every screen’s page ground is #FFFFFF (hubs, lists, feeds, details); the hairline on white is 1.4 to 1.8:1 (#CAD4CC is 1.52). Today fails: the page ground is #F8FBF9.2 · Control fills (segmented track, progress track, empty-state disc, input fills) at least 1.2:1 on white (#DFE7E1 is 1.26). A passive weak Tag fill may sit at 1.08 to 1.2:1 because its label carries it (gray100 is 1.10); every peer row pill is L* 96.3 · Tag and Choice: every Tag text pair at least 4.5:1 on its own fill (weakest: brand 5.12, neutral weak 6.69); a Choice label at least 4.5:1 on white and white on the selected ink fill at least 7:1 (19.13); every Tag and Choice state has a second non-colour cue (glyph, check, weight, underline).4 · Text at least 4.5:1 on its real ground; text at 14px or below additionally needs weight 500 or more (or 6.5:1 at weight 400). That is what APCA’s size table actually asks of small text, and it is why meta is 13/500, block labels are 14/700 and Tag text is 600. APCA Lc is reported, not gated: it is in no standard yet (WCAG 3 draft, 2026-04).5 · The block: white on $primary at least 4.5:1 (6.48) and any alpha label (and the chevron) at 0.8 or more; lime on the block at least 3:1 (4.64; 3.04 on its own α0.2 track).6 · CVD separation (Machado 2009) at the FILL / ICON level: brand, $success, $warningFill, error, info and optic pairwise ΔE2000 at least 11 under protan and deutan and at least 20 normal. Final set: every pair at least 13.4 / 14.7 / 17.2; brand against error 61.0 / 6.5 / 11.3, where the protan 6.5 is documented as irreducible for any red and carried by glyph + label. Text-level colours are held to at least 10 under deutan (warning text against error 10.3). Today fails: success against brand is 6.7.7 · Court trio, tier set, series set: pairwise ΔE2000 at least 11 under deutan and tritan; court and series colours at least 3:1 on white. Tier dots are decorative beside the printed tier name, so gold #D9A00F (2.34:1) is the one listed exception. Today fails: bronze against grandmaster 3.4 under deutan, clay against grass 1.7 under protan.8 · Dark: card against canvas 6 ≤ ΔL* ≤ 12; every status text on the dark card at least 4.5:1. Today fails: dark error is Lc 40.5.9 · Hue-family census of the theme: at most 7 families among the roles allowed on one viewport.The six rules that replaced the proportion targets:1 · Exactly one filled brand object larger than 2.5% of the frame per screen.2 · Brand solid otherwise only on the primary action and on toggles.3 · Every control state has a non-colour second cue (weight, fill, glyph, check, underline).4 · Inline links are underlined.5 · Every colour code has a glyph or a label (WCAG 1.4.1).6 · At most 7 hue families per viewport (Healey 1996).The interior-design colour-split rule, the saturated-area bands, “at least 2 hue families” and “brand share at most 70%” are not rules: they are peer medians or folklore (ledger 24). Measured on the T7 phones: today 3.3% saturated, all of it brand green; recommended 18.6%, of which the ELO block is the brand and everything outside it is 0.5% saturated, 24% brand-hued (optic Tags and tier dots); alternative 18.7%, outside the block 0.6% and 37% brand-hued (the tab glyph).
Recommended pathT8 — Semantic palette: one meaning per colour, visible to every eye
1Today the status set is one dark family: success #14804A, warning #946200, error #B8332E and the 모집 중 accent #007B6B all sit at L* 40 to 47, with no glyph. For a normal eye 완납 and 모집 중 are ΔE 11.6 apart and brand #006D37 is 6.7 from success (one green meaning brand, selection and paid at once). Through a deuteranope’s eyes (Machado 2009, severity 1.0) warning against error is ΔE 4.2: 마감 임박 and 취소됨 turn the same olive. WCAG 1.4.1 allows colour plus a label, but a 총무 scanning for who has not paid should not have to read.
2Success turns teal: $success #00796A for fills and icons (white on it 5.33:1) and $successText #014B40 for small text (10.09:1); against brand ΔE 13.4 / 14.7 / 17.2 (normal / protan / deutan), against error 55.5 / 16.2 / 24.2. A ΔE of 20 against brand is not reachable while staying green and legible (the best inside the text window is 11.3 at hue 170, 14.1 at 180), so the rule is Okabe–Ito parity at the fill / icon level (at least 11 under protan and deutan, at least 20 normal; Okabe–Ito’s own palette scores 21.7 / 12.3 / 11.5) plus a glyph and a label on every Tag. Polaris (#047B5D / #014B40), Seed positive (#079171) and Wise make the same hue shift for a green brand.
3Warning gets a bright fill with ink text: $warningFill #F5B301 (L* 77.1, ink on it 10.32:1) against error at L* 42.4 is 3.19:1 and ΔE 46.4 / 40.4 / 29.5, where a 20 L* gap between text-capable mid-tones gives only 2.0 to 2.2:1. Polaris #FFB800, Atlassian #FBC828, Carbon #F1C21B and Wise #FFD11A do the same. $warningText #6B4A00 is the small-text tier (8.06:1). Error stays #B8332E (dark #FA9B94, Lc 60 on the dark card; today’s #D4736E is Lc 40.5). Warning and danger have one rendering each, the fill, because a pale tint would give the lightness back.
4D1: #00796A is the hue of today’s 모집 중 accent badge, so open and paid would share a colour. Recommendation: 모집 중 moves to the brand-weak tint (mint #C8EED2 = $primaryMuted, brand text 5.12:1) because it is an invitation tied to the primary action, not an outcome. The alternative, success as a check glyph on a neutral Tag with green left to brand, is weaker for scanning paid rows.
5Second row, the content hues. Today’s court trio collides with the brand and the semantic set (grass #006633 is ΔE 2.3 from brand, clay #B84A2E 6.6 from error, clay against grass 1.7 under protan) and gold #D4A017 is 2.38:1 on white. Final: the trio is series 5, 2 and 3 (#089AC3, #8D3D00, #83971F; min ΔE 42.6 / 27.4 / 20.0 against 36.2 / 1.7 / 15.7), tiers bronze #B2580B, gold #D9A00F, grandmaster #9A0F3A (min 25.1 / 22.7 / 18.1 against 14.8 / 10.0 / 3.4). Documented, not misses: brand against error is 6.5 under protan whatever the red (the best red found is 9.2), carried by glyph and label (its 11.3 under deutan passes the gate’s 11); gold #D9A00F stays as the documented 2.34:1 exception, because a scan of every gold that reaches 3:1 (best #C87C00, 3.31:1) is only 10.2 ΔE from bronze under CVD, so the tier rule is CVD separation first and the tier name is always printed beside its dot (decorative marks sit outside WCAG 1.4.11); clay against error is 7.4 / 7.5, so a court dot never sits beside an error mark.
Owner's word needed → Success turns teal, warning gets a bright fill with ink text, 모집 중 moves to the brand tint — yes?
Today: green success, amber warning, teal 모집 중 and a red 취소됨 in pale fills, no glyphs. 취소됨 is grey (muted) in code today; both columns draw the error pair so the red role compares like for like.
Weak tier: 완납 a teal tint with #014B40 text and a check; 입금 필요 and 마감 임박 the #F5B301 fill with ink and a glyph; 취소됨 #B8332E with white and a cross; 모집 중 the brand-weak mint. Rows are the flat ladder (16/600 over 13/500, spacing only) and the scope is underline tabs. Measured: 2.9% saturated against 1.8% today, none of it brand-hued: the amber and red fills are the loud part of this screen, by design.
Why the old set collapses: the same drawing through Machado 2009 (severity 1.0)
Normal visionDeuteranopia (no green cones)Protanopia (no red cones)Status badges, today
모집 중마감 임박입금 필요완납취소됨
모집 중마감 임박입금 필요완납취소됨
모집 중마감 임박입금 필요완납취소됨
Recommended Tag, weak tier
모집 중마감 임박입금 필요완납취소됨
모집 중마감 임박입금 필요완납취소됨
모집 중마감 임박입금 필요완납취소됨
Recommended Tag, solid tier
모집 중마감 임박입금 필요완납취소됨
모집 중마감 임박입금 필요완납취소됨
모집 중마감 임박입금 필요완납취소됨
Court dots, today
하드
클레이
잔디
하드
클레이
잔디
하드
클레이
잔디
Court dots, recommended
하드
클레이
잔디
하드
클레이
잔디
하드
클레이
잔디
Tier dots, today
브론즈
실버
골드
마스터
그랜드마스터
브론즈
실버
골드
마스터
그랜드마스터
브론즈
실버
골드
마스터
그랜드마스터
Tier dots, recommended
브론즈
실버
골드
마스터
그랜드마스터
브론즈
실버
골드
마스터
그랜드마스터
브론즈
실버
골드
마스터
그랜드마스터
Status colours, the four distinct colours (success, warning, error, 모집 중) compared as ΔE2000 normal / protan / deutan. Today (text colours): minimum 11.6 / 10.2 / 4.2, the deutan floor is warning against error. Recommended, at the fill / icon level ($success, $warningFill, $error, $primary): every pair at least 13.4 / 14.7 / 17.2 except brand against error, 61.0 / 6.5 / 11.3: the protan 6.5 is documented for any red and carried by the glyph and the label, and the deutan 11.3 passes the gate’s 11. Text-level colours are not stand-alone codes and are held to at least 10 under deutan (warning text against error 10.3). The solid tier on a photo uses the same fills, so it separates the same way.
Recommended pathT5 — Glyph tiers: three glyph-led facts at the hero, text-only rows beneath
1Owner: "T5: I like the alternative (tiers of information)", and "inline icons are minimised in peers; icons are action targets." Lane J counted 32 peer list frames: 21 carry no fact glyph, 27 at most one, none a colour emoji; where peers do use a glyph it is monochrome in the meta text’s own grey (Munto, Greenlight, USTA). Hero or detail blocks carry 2 to 4 glyph-led facts (Meetup 2, Spond 3, TeamSnap 4) because the hero has no label word; Korean activity apps (소모임, 스마트스코어) use label words below it.
2Recommended: the hero (session detail top, club identity block) carries THREE glyph-led facts — when (date and time on one line), where, fee — as lucide strokes at 2.0 in the label’s colour, 16px in a 20px slot, concrete objects only. Every row beneath is label + value text, with MetaSegments where short facts share a line; the only mark a row may carry is a status Tag. The glyph vocabulary shrinks from 21 emoji to 4 concrete strokes (date, place, fee, capacity); weather keeps its emoji as the one documented exception, with an accessible name.
3Why not system emoji in the hero: vendor drawing varies (🎾 is a racket on most Android sets and a ball only on Apple, WhatsApp and Toss Face), colour is far above the brand (p90 chroma 78 to 95 against 46), GlyphSlot hides nothing from screen readers, and two Android clipping fixes were already spent on decoration. Toss built its own flat set rather than use system emoji. Not supported by research, so not claimed: that inline glyphs slow scanning, or that object emoji hurt comprehension (the cited divergence studies are about faces).
Owner's word needed → Glyphs only at the hero tier, three facts, lucide strokes; rows text-only — yes?
Before
👤내 상태
참석
📅일시
9월 19일 (토) 19:00
📍장소
대치유수지체육공원
🎾종목
복식 번개
🎯대상
NTRP 3.0–4.0
💰참가비
8,000원
Emoji in a 20px slot on every fact row: platform-dependent, saturated, 21 kinds
Recommended
강남 라켓클럽
올림픽공원 테니스장
모집 중
9월 19일 (토) 19:00
대치유수지체육공원
8,000원
내 상태
참석
종목
복식번개
대상
NTRP 3.0–4.0
The hero tier: three facts led by lucide strokes at 2.0 in the label’s colour; the rows below are label + value, MetaSegments where facts share a line, one status Tag. Measured: 0.1% saturated, 100% of it brand-hued.
The glyph vocabulary: 21 emoji today, 4 strokes at the hero tier
Stay (concrete objects)
일시when
장소place
참가비fee
정원capacity
Stroke 2.0, 16px in a 20px slot, the colour of the label. 🔒 becomes one stroke state mark. Weather keeps its emoji (the emoji is the data) with an accessible name; every decorative glyph gets aria-hidden.
At least 12 of the 21 are metaphors for abstract facts (level, deadline, approval, type, style, ELO, rotation, cancel policy, notice, recurrence, match rules, status); NN/g: only home, print and search are near-universal. The row beneath a hero already has its label word, so a glyph there is redundant.
web adaptation follows the mobile grammar — /wireframes/web-app-2 waits. The 1440 frame of rounds 2 to 4 leaves this board; stage S7 redraws it on the flat rows, the underline tabs and the rebuilt block.
T6 — Web-only fixes (Now, no question)
1Font: Tamagui text emits the family "Pretendard-Regular" but the web build registers the face as "pretendard" and binds it only to html and body, so every Tamagui Text was rendering in the system font (Segoe UI or Malgun Gothic on Windows: thinner Hangul, weights 500 and 600 collapsing). Fixed 2026-10-06 (3f2ce2e7, an @font-face under the emitted name plus a contract test); nothing to decide.
2Body background: apps/web/app/globals.css paints --tm-bg #F5F8F5 while $background is #F8FBF9, a second near-white 1 L* apart. Unified 2026-10-06 so the web frame reads the theme’s $background (S1 sets the one canvas to #FFFFFF, so the web body follows it).
3Hover: pressable dims to 0.85 opacity, which on a white row pulls it toward the canvas and reads paler. It becomes a tonal settle inside the element (no press band, no shadow); the 2px $primary focus ring stays.
Spec — tokens, contrast pairs, flat rows, block anatomy, Tag and Choice with the migration map, glyph tiers, the gate, usage rules (incl. the six-role budget), staged plan S1 to S7
Tokens, light, classic-grass (L* is CIELAB lightness)
Token
Today
L*
Proposed
L*
$background on every screen (hubs 홈, 기록, 프로필; lists 경기, 클럽; every pushed screen)
#F8FBF9
98.3
#FFFFFF: ONE canvas for hubs, lists, feeds and details. ShellGround "tinted" retires, so the five tab roots and every pushed screen are "canvas". There is no second ground and no card step to maintain
100
$card
#FFFFFF
100
unchanged (the same value as the canvas). A Card is a genuine lifted figure only: a hairline box on white, never the page’s primary flow
Hub block fill, $primary
#006D37
39.9
unchanged, white ink 6.48:1; radius 16 (was 20); dark block #015228 (existing colorsDark.primary.dark); alternative pale block #DCF3E4
93.8
$optic
#C6E84B (motif only)
87.1
same hex; level Tag (1px $border edge on white), today dot, progress fill inside the ELO block
87.1
Control track and fill = $borderSubtle
none: the segmented track is $surfaceTertiary #E5FAEA
96.4
#DFE7E1 (1.26:1 on white): segmented track (settings and wizards only), empty-state disc, progress track, input fills
90.9
Neutral Tag fill, gray100 (new role)
$badgeNeutralBg #F0F0F3 and the `surface` Badge variant
94.9
#F0F5F1 (1.10:1 on white; $textSecondary on it 6.69:1). Passive labels need not stand out as shapes: the label carries them, and every peer row pill sits at L* 96
96.1
$surfaceStrong (round 2), $controlTrack (round 3)
none
retired: #E5EBE7 is one more near-white; the control fill is $borderSubtle, the passive tint is gray100
92.5
$surfaceTertiary
#E5FAEA
96.4
alias of $borderSubtle #DFE7E1
90.9
Hairline = $border
$borderSubtle #DFE7E1, 1.26:1
90.9
#CAD4CC (1.52:1 on white); the 1.26:1 hairline role is retired
84.0
$primarySubtle
#E5FAEA
96.4
retired from controls (T7). The brand-weak tint for 모집 중 is the existing $primaryMuted #C8EED2 (1.26:1)
90.8
Control state colour (tab, Choice, saved bookmark, numerals)
$primary #006D37
39.9
$text #0D100E; section actions $textSecondary; each state has a second non-colour cue (weight, check, fill, underline)
4.4
$success / $successText
#14804A
47.1
#00796A (fill, icon, large figure; dark #75D1C6) / #014B40 (small text)
45.2 / 27.9
$warning
#946200
45.7
$warningText #6B4A00 (dark #E1B75C) / $warningFill #F5B301 with ink text (dark #E2B13C)
34.0 / 77.1
$error
#B8332E, dark #D4736E
42.4
light unchanged; dark #FA9B94 (Lc 60 on the dark card, was 40.5)
Tag 8 ($2) in both tiers, no 12; Choice full pill; hub block 16; cards 16; photo and map tile in a row 8
Every proposed value is the research ledger’s final (docs/design/research/colour-system-research-2026-10-09.md §2); round 5 moves none of them. The pure-white rule replaces the two-ground ladder of rounds 1 to 4: lists are flat rows with no card step, so nothing needs a lifted white sheet on a tinted canvas. Round 1’s darker ink tiers and the round 3 $controlTrack stay withdrawn. Dark mode is spec-only on this board (the phones draw light mode): card #1A201B on canvas #090C0A is ΔL* 8.4, inside 6 to 12; the dark block #015228 is carried by chroma (1.8:1 against the card), so D4 is a device check on OLED; if it does not read, the dark hub drops the block and keeps the numeral.
Contrast, every pair (WCAG 2 ratio, APCA Lc magnitude)
Pair
WCAG today
APCA today
WCAG proposed
APCA proposed
$text on the page ground (today #F8FBF9, proposed white)
18.37
103
19.13
106
$textSecondary on the page ground (L2 and L3, 13/500)
7.08
83
7.38
86
$textTertiary on the page ground (16px/500 and up only)
5.24
74
5.46
77
$primary on the page ground
6.22
79
6.48
82
white on the $primary block
6.48
87
#CCE2D7 label on the block (white at alpha 0.8; the chevron too)
4.76
65
$optic fill or numeral on the $primary block
4.64
63
$optic fill on its own white-alpha-0.2 track (#338A5F)
3.04
51
white edit glyph on the ghost-inverse fill (white alpha 0.18, #2E875B)
4.44
76
$text on the optic level Tag
13.71
84
$optic on white (the Tag carries a 1px $border edge)
1.40
19
$primary on the brand-weak tint #C8EED2 (모집 중 Tag, 12/600)
5.12
66
$successText on its weak tint #CBEBE5
7.95
77
$successText on white
10.09
93
white on $success #00796A (solid tier)
5.33
81
$text on $warningFill #F5B301
10.32
69
$warningText #6B4A00 on white
8.06
88
white on $error #B8332E (danger Tag, LIVE)
5.91
84
$info on its weak tint #E8DCFB
9.91
80
$textSecondary on the neutral weak Tag fill gray100 #F0F5F1
6.69
79
white on the neutral solid Tag fill #4F5851
7.38
90
$text on the control track #DFE7E1
15.17
90
$textSecondary on the control track (unselected segment label, 14/500)
5.85
70
white on the ink-filled Choice
19.13
107
Choice resting label $text 14/500 on white; outline $border on white
19.13 / 1.52
underline tab: 2px ink under the active label; idle label $textSecondary
19.13 / 7.38
gray100 #F0F5F1 fill on white (passive Tag; the label carries it)
1.10
$borderSubtle control fill #DFE7E1 on white
1.14 (neutral fill today)
1.26
$primaryMuted #C8EED2 (brand-weak tint) on white
1.26
hairline on white
1.26 ($borderSubtle)
1.52 ($border #CAD4CC)
white pill on the #DFE7E1 track (settings, wizards; plus a $border hairline)
1.26 (selection rides on the 700 label)
capacity fill on the #DFE7E1 track: $primary / $warningText / $info
4.89 / 3.26 / 2.34 / 4.67 / 8.39 (gold is the documented exception: tier dots are decorative beside the printed name)
court dot on white: hard / clay / grass
3.27 / 7.50 / 3.28
series 1 to 5 on white
5.61 / 7.50 / 3.28 / 7.72 / 3.27 (marks, so the 3:1 floor)
The non-text pairs are the point: the passive Tag fill 1.10, the control fill 1.26, the hairline 1.52, the Choice outline 1.52. A passive label is carried by its text, a control by its second cue (weight, check, fill, underline). APCA is reported as a ranking gauge beside WCAG 2, never as the sole pass or fail.
Documented exceptions and placement notes (ledger §2, §3)
Gold #D9A00F is 2.34:1 on white: the one listed exception to gate 7’s 3:1. A scan of every gold that reaches 3:1 on white (best #C87C00, 3.31:1) is only 10.2 ΔE from bronze under CVD, so no gold passes both. Tier dots are decorative beside the printed tier name (WCAG 1.4.11 does not apply to a decorative mark), so the tier rule is CVD separation first, and the name is always printed.
Brand against error is 6.5 under protan, irreducible for any red (the best red found is 9.2) and carried by glyph + label; its 11.3 under deutan passes gate 6’s 11. Text-level colours are held to at least 10 under deutan with a label: warning TEXT #6B4A00 against error is 10.3. The warning FILL (46.4 / 40.4 / 29.5) is what separates it, which is why warning has one rendering.
Gate 4 at small sizes, all clear: the unselected segment label on the track is 5.85 at 500, the 모집 중 Tag text 5.12 at 600, the block’s #CCE2D7 label 4.76 at 14/700, white on $success and on $error 5.33 and 5.91 at 600, links 5.95 at 16px/500 and above.
Placement note, not a gate: clay #8D3D00 against error is 7.4 / 7.5 and against brand 10.1 / 10.8 (protan / deutan), so a court dot never sits beside an error or brand mark. The court dot leaves the session row (spec §2) and lives on venue rows and the detail.
Flat rows (spec §2): pure white, spacing-separated, in the club-detail grammar
Row
Leading
L1
L2
L3
Right edge
Pitch
Session, compact (홈 under the day block)
venue tile 48 × 48, r8; map tile with a 1px inset $border unless the venue’s own photo
start time 19:00, 16/24 · 600 $text, tabular
venue 13/18 · 500 $textSecondary
—
6/8 at 13/700 $text, or one SessionStatusBadge Tag when a state outranks it (live → 참석, 예비, 호스트 → 마감 임박)
72 (today ≈ 163)
Session, discovery (번개 찾기, club 일정)
venue photo 72 × 72, r8 (map fallback)
start time 16/24 · 600 tabular (day prefix only on ungrouped lists)
venue 14/20 · 500 $text
복식 | 3.0–4.0, 13/18 · 500 $textSecondary via MetaSegments (the NTRP prefix drops on rows: the band is a number range beside the format, and the detail prints it in full)
6/8 or one Tag; the bookmark only as a 22px outline on the photo’s top-right corner if kept (D6)
96 (today ≈ 163)
Club
ClubAvatar 56
club name 16/24 · 600
서초구 | 3.0–4.0 | 42명, 13/18 · 500 $textSecondary
—
none in 내 클럽; one Tag (모집 중) in discovery
80 (today’s card 16 to 21 objects)
Dues
PersonRow avatar 40
member name 16/24 · 600
only what varies (부분납, note) 13/18 · 500
—
amount 15/700 tabular $text, or one Tag (완납, 입금 필요)
64
Member
avatar 40
name 16/24 · 600
role or NTRP 13/18 · 500
—
one Tag (호스트) or nothing
64
Common: page rail 16; row padding 12 vertical; leading to text gap 12; title to sub gap 4; 0 inline glyphs; at most one accent (a Tag, only on exceptions); the right edge holds ONE element, a count, an amount, a Tag or a chevron, never two; rows are Pressable with the inner settle, no band, no card frame, no fill, no divider (spacing only; the Divider law stands: a hairline separates sections, never peers). What leaves the row: price (the detail cost row; canon already says so, and session-card-v2.tsx:53-61 against :473-485 is drift to fix), the capacity bar (the detail hero stat), the live ribbon (a danger Tag with the pulse dot in the trailing slot; the band stays on the detail anchor, D7), the bookmark (the detail header, D6), format and tier chips (one text line), the court dot, weather, the host role (a Tag only when it is the trailing element). Element count: 5 (photo, L1, L2, L3, trailing) on the discovery row, 4 on the compact row, against 12 to 14 today and a peer median of about 7; a 600pt list shows about 6 rows instead of 4.6. Navigation on list surfaces: the scopes (이번 주 / 다음 주 / 전체; 내 경기 / 번개 찾기 / 저장됨 / 테니스장; the leaderboard scope) become underline tabs in ink (Toss, 카카오뱅크, 배민, Playtomic); the segmented pill leaves list surfaces and stays a form control in settings and wizards; the tab bar’s active tab is a filled glyph with an ink label; section actions are $textSecondary plus a chevron; the one coloured action is the FAB (or the CTA band on detail pages).
Block anatomy (spec §3): one shared frame, pressable, no button inside
Block
Rows top to bottom
Height
홈 day
eyebrow 14/20 · 700 α0.8 "내일, 9월 19일 (토)" + chevron · 4 · numeral row 40: 2 at 36/40 · 700 with 개 at 15/600 on the baseline · 4 · meta 14/20 · 700 α0.8 "첫 시작 09:00" left-aligned, no glyph
120 (14% of 844)
기록 ELO
eyebrow "ELO | NTRP 3.5–4.0" 14/700 α0.8 + chevron · 4 · numeral 1,482 at 40/44 · 700 · 12 · optic progress fill 6 on an α0.2 track · 8 · caption "마스터까지 518점" 14/700 α0.8
146 (17%)
프로필 identity
avatar 56 + 12 + name 24/700 with the level Tag beside it over "강남 테니스 클럽 | 3년 차" 14/500 α0.8 · edit icon button 32 ghost-inverse top-right (the one action target) · 12 · trust row 24: "신뢰 등급 | 신뢰 멤버" 14/700 + chevron
124 (15%)
Frame: margin 16, radius 16, padding 16 on all sides, solid $primary with white ink (dark #015228), no gradient, no illustration, no shadow; left-stacked; a trailing chevron 16 at α0.8 on the eyebrow row marks it pressable (Toss 토스뱅크 header); no pill, no qualifier chip in the eyebrow. Seam to the list: 24 to the section header (or the first row when there is no header), then 8 to the first row. Labels inside the block: 14/700 or 15/600 white; secondary at α 0.8 or more; lime only as the progress fill or a numeral of 28/700 or more. The 프로필 block’s second row (the trust tier, with its chevron into TrustTierInfoSheet) is lane H’s row 2 and what brings the block to 124pt; spec §3’s prose omits it.
Tag tiers (spec §4): ink on fill, weak fill on white
Tone
Weak fill / ink
Ink on fill
Fill on white
Solid fill / ink
Ink on fill
For
neutral
#F0F5F1 / $textSecondary #4F5851
6.69
1.10
#4F5851 / white
7.38
role, pending, metadata; outline for neutral on a tinted surface
brand
$primaryMuted #C8EED2 / $primary
5.12
1.26
same
5.12
모집 중, invitation (D1)
success
#CBEBE5 / $successText #014B40
7.95
1.27
$success #00796A / white
5.33
완납, 참석 확정, win
warning
$warningFill #F5B301 / $text
10.32
1.85
same
10.32
마감 임박, 입금 필요
danger
$error #B8332E / white
5.91
solid
same
5.91
LIVE (+ pulse dot), 취소됨, overdue
info
#E8DCFB / $info
9.91
1.31
$info / white
12.95
informational
level
$optic #C6E84B / #0D100E + 1px $border
13.71
1.40
same
13.71
NTRP / tier numeral, a 22px circle
Tag (packages/ui/src/tag.tsx): passive, no onPress and no aria-pressed or aria-selected; tones neutral, brand, success, warning, danger, info, level; tiers weak (tinted; repeated rows) and solid (over photos; strong attention) plus outline for neutral on tinted surfaces; sizes sm 22px (12/18 + 2 × 2) and md 26px (12/18 + 2 × 4); radius 8 ($2) in both tiers; level is a 22px circle with a numeral; font 12/18, 600 weak and 700 solid, tabular when numeric; label 1 to 2 words, adjectives, at most 10 characters; at most 2 Tags per object and 3 or more in one container is a lint finding; facts are never Tags (MetaSegments text); alignSelf defaults to center. Warning, danger and brand have one rendering because their separation under colour-vision deficiency is carried by the fill. Gate note: the 1.2:1 fill floor binds control tracks; a passive weak Tag fill may sit at 1.08 to 1.2:1 because the label carries it.
Today
Becomes
Badge (75 files, 12 variants, 4 used)
Tag
≈ 19 neutral badges that print a fact
MetaSegments text
Badge size xs (33 files)
Tag sm (22px)
TierBadgeChrome
a neutral Tag with the tier dot on the hero and profile; plain text in rows
ReliabilityPill, MarkerPill, ContextChip, five hand-drawn pills
Tag
GlyphFactRow density="chip"
deleted (facts are inline text; glyphs live at the hero tier only)
SelectionChip (58 files)
Choice: resting 1px $border outline on $background, 14/500 $text, full pill 32 / 40 with 44 hit slop; selected = $text ink fill, $textInverse 700 label, leading check
DropdownChip, RangeChip, RemovableChip
Choice modes: menu, range, input
ActionPill
Button size="sm" tone="tonal"
CornerCountBadge
CountBadge (kept)
TogglePill
SwitchTrack
Filters and scopes on list surfaces
underline tabs in ink (the segmented control stays a settings and wizard form control)
Both Tag and Choice read one geometry module. Lint (each with a known-positive canary fixture): tag-is-passive, no-hand-drawn-label, no-hand-rolled-pressable-pill, choice-needs-handler, tags-per-container.
Glyph tiers (spec §5)
Tier
Where
Facts rendered as
Decoration
Hero
session detail top, club identity block
THREE glyph-led facts: when (date and time on one line), where, fee. Lucide strokes 2.0 at the label’s colour, 16px in a 20px slot; concrete objects only (calendar, map pin, banknote). No glyph for level, ELO, rotation, policy or rules: label + value rows instead. 2 to 4 is the peer band (Meetup 2, Spond 3, TeamSnap 4).
none beyond the three glyphs
Rows
every list row, every row beneath the hero
text: label + value, facts that share a line as MetaSegments with MetaBreak. The status Tag may carry a check or a pulse dot (status glyphs are allowed because they are the state, not decoration).
0 glyphs
Vocabulary
SESSION_FACT_GLYPH
shrinks from 21 to 4 concrete glyphs (date, place, fee, capacity); 🔒 becomes a single stroke state mark; weather keeps its emoji as the one documented exception, wrapped with an accessible label
every decorative glyph gets accessibilityElementsHidden / aria-hidden
Not supported by research, so not claimed: that inline glyphs slow scanning (no study; the quiet row rests on Apple’s “prefer text in lists”, peer practice and the colour budget), or that emoji hurt comprehension for object glyphs (the Miller 2016 and Tigwell 2016 divergence studies are about faces). The case against system emoji in the hero is vendor drawing (🎾 is a racket on most Android sets), colour (p90 chroma 78 to 95 against the brand’s 46), screen readers (GlyphSlot hides nothing) and two Android clipping fixes already spent.
The gate (scripts/check-color-contrast.mjs)
Canvas: every screen’s page ground is #FFFFFF (hubs, lists, feeds, details); the hairline on white is 1.4 to 1.8:1 (#CAD4CC is 1.52). Today fails: the page ground is #F8FBF9.
Control fills (segmented track, progress track, empty-state disc, input fills) at least 1.2:1 on white (#DFE7E1 is 1.26). A passive weak Tag fill may sit at 1.08 to 1.2:1 because its label carries it (gray100 is 1.10); every peer row pill is L* 96.
Tag and Choice: every Tag text pair at least 4.5:1 on its own fill (weakest: brand 5.12, neutral weak 6.69); a Choice label at least 4.5:1 on white and white on the selected ink fill at least 7:1 (19.13); every Tag and Choice state has a second non-colour cue (glyph, check, weight, underline).
Text at least 4.5:1 on its real ground; text at 14px or below additionally needs weight 500 or more (or 6.5:1 at weight 400). That is what APCA’s size table actually asks of small text, and it is why meta is 13/500, block labels are 14/700 and Tag text is 600. APCA Lc is reported, not gated: it is in no standard yet (WCAG 3 draft, 2026-04).
The block: white on $primary at least 4.5:1 (6.48) and any alpha label (and the chevron) at 0.8 or more; lime on the block at least 3:1 (4.64; 3.04 on its own α0.2 track).
CVD separation (Machado 2009) at the FILL / ICON level: brand, $success, $warningFill, error, info and optic pairwise ΔE2000 at least 11 under protan and deutan and at least 20 normal. Final set: every pair at least 13.4 / 14.7 / 17.2; brand against error 61.0 / 6.5 / 11.3, where the protan 6.5 is documented as irreducible for any red and carried by glyph + label. Text-level colours are held to at least 10 under deutan (warning text against error 10.3). Today fails: success against brand is 6.7.
Court trio, tier set, series set: pairwise ΔE2000 at least 11 under deutan and tritan; court and series colours at least 3:1 on white. Tier dots are decorative beside the printed tier name, so gold #D9A00F (2.34:1) is the one listed exception. Today fails: bronze against grandmaster 3.4 under deutan, clay against grass 1.7 under protan.
Dark: card against canvas 6 ≤ ΔL* ≤ 12; every status text on the dark card at least 4.5:1. Today fails: dark error is Lc 40.5.
Hue-family census of the theme: at most 7 families among the roles allowed on one viewport.
The current theme must fail the canaries marked “today fails”: that proves the gate bites. The token-agreement test also pins every theme field, so tokens.ts cannot drift again.
Usage rules (S1 to S7 enforce these)
One canvas, #FFFFFF, on every screen: hubs, lists, feeds and details. Rows are flat: no card frame, no fill, no divider between peers, spacing only; sections are divided by the FlatColumn full-bleed hairline (the Divider law).
One bounded $primary block per hub: margin 16, radius 16, padding 16, left-stacked, white ink, pressable with a chevron, no button, no pill, no qualifier chip; it holds the screen’s answer as a 36 to 40/700 numeral; exactly one filled brand object above 2.5% of the frame per screen.
$optic is small and numeric: the level Tag (with a 1px $border edge on white), the today dot, a progress fill on the block. Never ground, never a button, never text, never beside amber.
Control fills (segmented track, progress track, empty-state disc) are $borderSubtle; the passive neutral Tag tint is gray100; the line is $border; selection is ink with a second non-colour cue; $primarySubtle is retired from controls.
One label, one selection. A Tag is passive (status, role, level): an 8px rectangle in two tiers, a label always and a glyph on success, warning and danger. A Choice is the selection control: an outlined white pill, ink-filled with a check when selected. A fact, a region or a term is never a Tag. List scopes are underline tabs in ink; the segmented control exists only as a settings or wizard form control.
The ladder is top-down: L1 16/600 $text (the time, or the name), L2 14/500 $text or 13/500 $textSecondary, L3 13/500 $textSecondary via MetaSegments; no bold below L1; one trailing element; 4px title to sub, 12px row padding. A day-grouped list leads with the time.
Glyphs have tiers: three glyph-led facts at the hero (lucide strokes 2.0 in the label’s colour), text-only rows beneath; emoji only for weather, with an accessible name.
$textTertiary only at 16px/500 and above or on non-essential strings; the 13 to 14px sites read $textSecondary.
Six-role colour budget (T7). Brand solid ($primary + white): the screen’s one action and one hero block, toggle on, selected date disc; brand as text: inline links, underlined. Ink and neutral ($text, $textSecondary, $borderSubtle, $border, gray100): every control state, including the active tab, the underline tab, the Choice, the saved bookmark, section actions, person avatars, progress tracks and numerals. Semantic ($success, $warning, $error, $info): status only. Level accent ($optic + $text): the level Tag, the today dot, the fill inside the ELO block. Content hues (tier slots, court hues, club identity hue, medal tones, $series1 to $series5): small, on content identity, always beside a printed name, never on a control. A teal Tag means paid, confirmed or won; a green button means do this.
One meaning per colour (T8): success is teal, warning is the bright fill with ink, danger is #B8332E, 모집 중 is the brand-weak tint; every colour code has a glyph or a label, and at most 7 hue families appear in one viewport.
No shadow and no elevation (Android flattening). A Card is a genuine lifted figure only: a hairline box on white, never the page’s primary flow.
Staged plan, one PR per stage, each ships on its own (S6 after S2 and S3)
Stage
Change
Components touched
Gate or check
S1 Tokens, semantic palette, CVD gate
One canvas: $background #FFFFFF everywhere (ShellGround "tinted" retires; 홈, 기록, 프로필, 경기 and 클럽 are all "canvas"; --tm-bg reads it); the control fill $borderSubtle #DFE7E1 ($surfaceStrong retired, $surfaceTertiary aliases it); the hairline $border #CAD4CC; the neutral Tag fill gray100 #F0F5F1; the semantic palette ($success #00796A / $successText #014B40, $warningText #6B4A00 / $warningFill #F5B301 with ink text, dark error #FA9B94, dark success #75D1C6, dark warning #E1B75C / #E2B13C, dark ink #DFE4DC, dark block #015228); $levelChipBg / $levelChipText; the court trio, tier slots and $series1 to 5; status pairs re-derived. Ink tiers do NOT change.
classic-grass.theme.ts and the three sibling themes (own hue), tamagui.ts, tokens.ts, shell-ground.ts, main-tab-shell.tsx, the five tab screens’ ground props, shared-semantics.ts, status-badge-palette.ts, tier.config.ts, apps/web/app/globals.css, scripts/check-color-contrast.mjs
Gate assertions 1 to 9 added, each with a known-positive canary that fails on today’s values (page ground #F8FBF9, success against brand 6.7, bronze against grandmaster 3.4 under deutan, clay against grass 1.7 under protan, dark error Lc 40.5); colors_and_type.css regenerates
S2 Tag and Choice primitives, lint, migration
`Tag` (passive, seven tones, weak and solid, sm 22 / md 26, radius 8) and `Choice` (outlined pill, ink selected with a check; menu, range and input modes) in @twomore/ui, both reading one geometry module. Migrate Badge (75 files) to Tag, the ≈ 19 fact-printing neutral badges to MetaSegments, SelectionChip (58 files) to Choice; delete GlyphFactRow density="chip".
packages/ui (new tag.tsx, choice.tsx and their -visual.ts), badge.tsx, selection-chip.tsx, chip-label-visual.ts, dropdown-chip.tsx, range-chip.tsx, removable-chip.tsx, glyph-fact-row.tsx, tier-badge-utils.tsx, the status badges and their call sites
Lint: tag-is-passive, no-hand-drawn-label, no-hand-rolled-pressable-pill, choice-needs-handler, tags-per-container, each with a known-positive canary fixture (on day one no-hand-drawn-label reports the 6 label sites and none of the pressable ones)
S3 Flat rows, underline tabs, block components
The compact and discovery session rows, the club row, the dues row and the member row (no card frame, no fill, no divider, one trailing element); `UnderlineTabs` replaces PillNav on list surfaces (PillNav stays for settings and wizards); `HubBlock` (one frame, three anatomies, pressable chevron, no button, the edit icon on the identity block); 홈 drops the scope row.
session-card-v2.tsx and session-compact-row.tsx, venue-tile.tsx, club-card.tsx, dues-row, person-row.tsx, the PillNav call sites, new underline-tabs and hub-block in packages/ui, home-feed-screen.tsx, views/home-week-view.tsx, records/cards/hero-card.tsx, profile/profile-screen.tsx
Screenshot rung: exactly one filled brand object above 2.5% of the frame per screen; a node test pins the row pitch (72, 96, 80, 64) and the element count per row
S4 Type ladder sweep
cardBody 14/400 to 14/500; cardMeta 14/500 to 13/500 in $textSecondary; L1 16/600 on every row; no bold below L1; the 13 to 14px $textTertiary sites move to $textSecondary (D5).
text.tsx, text-grammar.ts, list-row.tsx, fact-row.tsx, session-fact-rows.tsx, and the tertiary sweep
Lint: no color override on cardMeta; $textTertiary below 16px is a lint error (known-positive canary)
S5 Glyph vocabulary and hero facts
SESSION_FACT_GLYPH from 21 emoji to 4 lucide strokes (date, place, fee, capacity); the hero carries three glyph-led facts at stroke 2.0 in the label’s colour; every row beneath is label + value text with MetaSegments; 🔒 becomes one stroke state mark; weather keeps its emoji with an accessible name; decorative glyphs are aria-hidden.
Lint: no emoji literal in a fact row; every decorative glyph is hidden from assistive technology
S6 Controls to ink, with second cues
Tokens first ($levelChipBg / Text, tier slots, $series1 to 5), then the controls: active tab = filled glyph + $text + 600 label, saved bookmark outline to filled, avatar default tone = neutral, empty-state disc neutral, section-header action = $textSecondary with its chevron, inline links brand + underline, tier chip = white + tier-hue dot, progress default track = $borderSubtle with the recruitment tone as fillColor, stat numerals $text, the optic level Tag under roster and profile avatars. Ships after S2 and S3 (Choice and the block), or hubs go colourless.
apps/mobile/app/(tabs)/_layout.tsx, avatar-bubble.tsx, empty-state.tsx, section-header.tsx, progress-bar.tsx, tier-badge-utils.tsx, sessions/save-button.tsx, stat-grid.tsx and tone-colors.ts, selection-card.tsx, apps/web/components/app-nav.tsx, and the theme files for the new tokens
Lint: $primary and $primarySubtle only in an allow-listed set of brand roles (asserted by role, with a known-positive canary), never in a Pressable state style; every control state has a second cue
S7 Web
After S1 to S6 ship, the web adaptation follows the mobile grammar: one toolbar row per tab root, one-line session rows, detail pages with a breadcrumb strip and an inspector. The /wireframes/web-app-2 board waits and is redrawn on the mobile grammar first.
A wireframe iteration with the owner before code; the web screenshot rung (screenshot-web.mjs --width)
All seven stages are token or JS-only (RemoteImage, lucide, the shell ground prop and the tab bar’s tabBarActiveTintColor already ship), so each goes out by OTA; none needs a native build. S1 alone changes the tone and palette of the whole app, S2 its label and selection grammar, S3 its lists and hubs, S6 its colour grammar: roughly 150 to 210 of the 307 brand-token sites retarget, nine primitives carrying the call-site multipliers. The three sibling themes (melbourne-blue, new-york-hardcourt, paris-clay) get the same moves from their own hues in S1; the numbers above are classic-grass only. Venue photos exist on the entity (photoUrls) but their coverage is not measured here; the cascade falls back to the map tile, so the row is correct without them. D2: the computed hexes #6B4A00, #8D3D00, #83971F, #014B40 and #9A0F3A were computed, not judged; this board renders them, and a device look is owed before tokens ship. Peer pt values are read from curated App Store frames at ±2pt on sizes and ±4% on pitches; 테니스프렌즈 is layout evidence only (its labels are 9 to 10pt); nothing was viewed on a device; 72 / 96 and 48 / 72 are the smallest peer-supported sizes, not optimums.
Spec (grounding, anatomy)
Round 5 (2026-10-09): owner rulings — pure white, flat rows, rebuilt block, top-down type, one Tag, compact row, hero-only glyphs; web deferred (docs/design/research/list-surfaces-spec-2026-10-09.md): T1 one white canvas with flat spacing-separated rows and underline tabs in ink, T0 the hub block rebuilt (margin 16, radius 16, padding 16, left-stacked, pressable chevron, no button; 120, 146 and 124pt), T2 the top-down ladder (time 16/600, venue 14/500, meta 13/500), T3 one passive Tag and one Choice control, T4 the five-object compact row (bookmark and bar leave the row, live becomes a trailing Tag), T5 three glyph-led facts at the hero and text-only rows, T7 the colour budget with underline tabs on list surfaces, T8 the semantic palette; open items D1 to D7; the staged plan S1 to S7 is all OTA. Round 4 (2026-10-09): research-adjusted values (colour-system-research-2026-10-09.md). Round 3: T7. Rounds 1 and 2: white, with one block. T6 lists the web-only fixes already done. Reuses the web-app kit, so it is registered after it.