Lane D — colour usage: does TwoMore spend its brand colour too freely?
Status: Reference · Date: 2026-10-08 · Evidence appendix for visual-benchmark-2026-10-08.md (§6 colour budget). Frame, crop and script paths refer to the session scratch directory (not committed).
Date: 2026-10-08 · Author: lane D (colour usage) · Evidence for docs/design/research/visual-benchmark-2026-10-08.md §1–§3 · English only · all paths below are relative to the repo unless they start with bench/ (scratch dir …/scratchpad/bench/). Repo files were read, never modified.
Owner hypothesis under test: "Other major apps use colour to draw attention and restrain from using colour when not necessary. We are perhaps using too much of the primary colour everywhere and not using enough colours to accentuate contents."
0. Verdict in one screen
The hypothesis is right about hue and role, wrong about quantity. We paint a quarter of the peer median of saturated area, but one green does every job and almost none of it is spent on content.
| Claim inside the hypothesis | Measured | Verdict |
|---|---|---|
| "We use too much primary colour" (as area / object count) | Saturated area on our hub and list frames: median 1.4% (range 0.1–3.4%) vs peers median 5.1% (IQR 2.6–9.3, n = 72 app rows). Large solid saturated objects per frame: ours 0–1 (plus the CTA band on detail pages; 4 saved-bookmark glyphs on the 테니스장 list) vs peers median 2.5 (IQR 1.0–3.7). | Refuted. We paint about a quarter of the peer median. |
| "…everywhere" (as hue exclusivity) | Brand green is 96% of our saturated pixels (median of 11 frames, range 65–100; 100% of solid fills). Peers' own brand hue is 50% raw / 61% solid (n = 11 apps with a known brand hue); the top hue of any peer is 52% raw / 69% solid (n = 72). 57 of 72 peers carry ≥ 2 hue families at ≥ 10% of their saturated pixels; 10 of our 11 frames carry exactly one. | Confirmed. One hue does every job. |
| "…everywhere" (as role) | 307 brand-token sites in shipped UI: 66% chrome/controls, 23% content accents, 10% decoration. Of all content-accent token sites, 88% are brand green (72 of 82); the court / medal / accent / Saturday-blue palette has 10 direct sites in scope. 12 of 17 peers I inspected render tabs, segments and selected chips in ink or neutral; none fills a selected segment solid brand on a brand-tinted track. | Confirmed. Brand colour sits on controls, where peers keep ink. |
| "…not enough colours to accentuate content" | 10 of 17 inspected peers show ≥ 3 hues in content objects (pictograms, per-account discs, per-calendar dots, level/category chips). Our token palette has the content hues (optic, court surfaces, medals, ELO tiers, club identity hues) but only profile.png shows any (orange + yellow = 29% of its saturated pixels, the 업적 row); optic has 0 consumers. | Confirmed. The palette exists and is unspent. |
| Side effect worth knowing | Pale-tinted fills: ours are all green (hub median 2.3% of the frame, 17–21% on the two detail frames with the map placeholder); peers' median is 0.9% tint and 5.2% neutral grey. Brand #006D37 and success #14804A are ΔE76 7.5 apart, so green is brand, selection and "paid/confirmed". | New finding: "restrained colour" also means peers fill with grey and we fill with mint. |
The six numbers to remember: 96% (our brand-hue share) vs 50%/61% (peers' brand-hue share) · 1.4% vs 5.1% (saturated area) · 1 vs 2 (hue families ≥ 10%, median) · 66% of brand sites are chrome · 12 of 17 peers use ink/neutral for selection · 0 consumers of optic.
1. Hue budget, measured
1.1 Method (and three traps I hit)
- Script:
bench/sample.py --hues <frames> [--crop top,bottom](saturated = C*ab > 30; family by CIELAB hue angle). Raw outputs are saved next to this file:bench/lane_d/sample_hues_{ours_full,ours_crop9,kr,cat,glob_m}.jsonl. The coordinator recalibrated the buckets mid-task (red 350–40, orange 40–75, yellow 75–105, lime 105–130, green 130–185, teal 185–245, blue 245–292, violet 292–350); everything here was re-run after that change.bench/lane_d_hues.pyimportssample.HUE_BUCKETS, so it follows any further recalibration. Brand green#006D37= 151°, optic#C6E84B= 115° (lime), Toss blue 284°, 당근 orange 52°. - Two views per frame. Raw = every saturated pixel (what
--huesprints; photos, text and icon strokes included). Solid-fill = saturated pixels whose ±2 px neighbourhood is uniform (≤ 8 levels per channel): pills, buttons, bars, blocks, dots, discs; photo texture and glyph strokes drop out (bench/lane_d_hues.py). A family "counts" when it holds ≥ 10% of the frame's saturated pixels. Per-app rows pool frames with equal weight. - Peer inputs (72 app rows = 68 distinct apps; Toss, Playtomic, Spond and TeamSnap appear in two crop sets):
crops/*.png(Korean, 18 apps),b_crops/*.png(category, 35 apps), global = the insetcrops_c/m_*.pngcrops plusgmail_*(19 apps; Apple's ownappledev/appstoredropped). The un-insetcrops_c/<app>_N.pngfiles still contain the App Store's saturated marketing background (Monzo coral, Playtomic blue, Cash App green, Wise lime), so they are not used;bench/lane_d/peers_per_frame.tsvhas them in itsglobrows, ignore those and usebench/lane_d/glob_m_per_frame.tsv. - Our frames:
ours-phone/*.png(780×1688),ours-1440/*.png;ours-dark/*excluded from the headline (peers are light-mode). The dev-tools pill and CTA band matter: on the venue-detail and session-detail frames the Next "1 Issue" pill (#CA2A30, 0.5% of the frame) is 18–20% of the saturated pixels (10% on the clubs frame), and the green CTA band (#006C36) is 3.4–4.5% of the frame on four phone frames. Cutting the bottom 9% removes both (and, on tab roots, the tab bar):sessions_*.png5.8% → 0.1%,courts.png6.2% → 0.7%,directory-venues_*.png6.4% → 0.7%; tab-root frames barely move (root 1.3 → 1.4, records 2.5 → 2.6) because their tab icon is tiny. Headline numbers use the cropped values; both are in the table.
1.2 Our frames
clubs (phone) is dominated by a clay-court photo (orange 45–77% of its saturated pixels), so it is shown but excluded from the medians.
| frame (dir) | crop | sat % | top hue (%) | 2nd hue (%) | families ≥10% | green 130–185° % | solid-fill sat % | solid top (%) |
|---|---|---|---|---|---|---|---|---|
| 클럽 clubs (photo hero) (phone) | full | 14.1 | orange 45 | green 34 | 3 | 34 | 7.46 | green 57 |
| 클럽 clubs (photo hero) (phone) | bottom 9% cut | 9.0 | orange 77 | blue 11 | 2 | 1 | 2.58 | orange 97 |
| 테니스장 list (phone) | full | 6.2 | green 97 | yellow 1 | 1 | 97 | 5.38 | green 100 |
| 테니스장 list (phone) | bottom 9% cut | 0.7 | green 77 | yellow 7 | 1 | 77 | 0.26 | green 100 |
| 코트 상세 venue detail (phone) | full | 6.4 | green 81 | red 18 | 2 | 81 | 5.17 | green 84 |
| 코트 상세 venue detail (phone) | bottom 9% cut | 0.7 | green 100 | red 0 | 1 | 100 | 0.35 | green 100 |
| public share page (phone) | full | 1.4 | green 97 | yellow 2 | 1 | 97 | 1.18 | green 100 |
| public share page (phone) | bottom 9% cut | 1.6 | green 97 | yellow 2 | 1 | 97 | 1.30 | green 100 |
| 프로필 profile (phone) | full | 1.1 | green 65 | orange 22 | 2 | 65 | 0.17 | green 98 |
| 프로필 profile (phone) | bottom 9% cut | 1.2 | green 65 | orange 23 | 2 | 65 | 0.18 | green 98 |
| 기록 records (phone) | full | 2.5 | green 98 | orange 1 | 1 | 98 | 1.53 | green 100 |
| 기록 records (phone) | bottom 9% cut | 2.6 | green 99 | orange 0 | 1 | 99 | 1.67 | green 100 |
| 홈 root (phone) | full | 1.3 | green 97 | yellow 1 | 1 | 97 | 1.09 | green 100 |
| 홈 root (phone) | bottom 9% cut | 1.4 | green 98 | yellow 1 | 1 | 98 | 1.20 | green 100 |
| 일정 상세 session detail (phone) | full | 5.8 | green 80 | red 20 | 2 | 80 | 4.92 | green 83 |
| 일정 상세 session detail (phone) | bottom 9% cut | 0.1 | green 99 | red 1 | 1 | 99 | 0.00 | green 100 |
| 클럽 clubs, photo hero (1440) | full | 3.8 | green 91 | lime 5 | 1 | 91 | 1.09 | green 99 |
| 클럽 clubs, photo hero (1440) | bottom 9% cut | 3.4 | green 91 | lime 4 | 1 | 91 | 1.19 | green 99 |
| 경기 matches (1440) | full | 0.8 | green 84 | blue 5 | 1 | 84 | 0.53 | green 100 |
| 경기 matches (1440) | bottom 9% cut | 0.9 | green 84 | blue 5 | 1 | 84 | 0.58 | green 100 |
| 기록 records (1440) | full | 3.0 | green 97 | yellow 1 | 1 | 97 | 1.57 | green 100 |
| 기록 records (1440) | bottom 9% cut | 2.8 | green 96 | yellow 1 | 1 | 96 | 1.61 | green 100 |
| 홈 root (1440) | full | 1.2 | green 91 | yellow 2 | 1 | 91 | 0.88 | green 100 |
| 홈 root (1440) | bottom 9% cut | 1.4 | green 92 | yellow 2 | 1 | 92 | 0.97 | green 100 |
Reading it: outside the photo frame our saturated pixels are 65–100% brand green; the one frame below 90% is profile.png, where the 업적 (achievements) row adds orange 22% + yellow 7% (bronze/gold medal tones) and the red 4% is the avatar photos. Families ≥ 10%: 1 on 10 of 11 frames (profile has 2).
1.3 Peers, per app (frames pooled)
Columns: saturated % (raw), top and second hue family with their % of the saturated pixels, number of families ≥ 10% (raw); the same for solid fills. Per-frame rows: bench/lane_d/peers_per_frame.tsv, bench/lane_d/glob_m_per_frame.tsv. Photo-led apps (당근, 무신사, 컬리, 오늘의집, 크림, 올리브영) inflate the raw families, so read their solid columns.
| set | app | frames | sat % (raw) | top hue % | 2nd hue % | families ≥10% | solid-fill sat % | solid top % | solid families ≥10% |
|---|---|---|---|---|---|---|---|---|---|
| category | ace | 2 | 2.3 | blue 95 | yellow 4 | 1 | 0.00 | blue 100 | 1 |
| category | atpwta | 2 | 12.3 | violet 92 | red 5 | 1 | 3.65 | violet 99 | 1 |
| category | 밴드 | 3 | 3.8 | violet 25 | green 23 | 4 | 0.89 | green 43 | 2 |
| category | baseline | 3 | 8.1 | blue 85 | violet 10 | 2 | 1.98 | blue 89 | 2 |
| category | 클래스101 | 2 | 2.7 | orange 50 | red 32 | 3 | 0.43 | orange 68 | 2 |
| category | CourtMate | 3 | 2.2 | lime 91 | orange 4 | 1 | 0.74 | lime 100 | 1 |
| category | espn | 2 | 2.6 | blue 54 | red 38 | 2 | 0.56 | blue 84 | 2 |
| category | 플랩풋볼 | 3 | 10.1 | lime 63 | green 29 | 2 | 4.70 | lime 91 | 1 |
| category | fotmob | 3 | 2.3 | blue 71 | red 13 | 2 | 0.03 | blue 100 | 1 |
| category | 프립 | 3 | 5.2 | blue 31 | violet 28 | 4 | 1.25 | blue 43 | 3 |
| category | 골프존 | 3 | 4.4 | blue 88 | red 5 | 1 | 2.27 | blue 99 | 1 |
| category | 그린라이트 | 2 | 2.0 | blue 34 | red 29 | 3 | 0.34 | blue 89 | 2 |
| category | Heja | 1 | 6.3 | blue 60 | green 36 | 2 | 3.13 | blue 65 | 2 |
| category | 김캐디 | 3 | 7.1 | lime 46 | green 22 | 3 | 1.56 | lime 77 | 2 |
| category | Meetup | 2 | 11.1 | red 53 | green 14 | 4 | 0.98 | red 84 | 2 |
| category | 문토 | 3 | 3.4 | teal 29 | orange 19 | 5 | 0.88 | teal 54 | 3 |
| category | nrc | 3 | 26.8 | orange 55 | teal 36 | 2 | 21.28 | orange 57 | 2 |
| category | onefootball | 3 | 3.2 | red 42 | blue 24 | 3 | 0.06 | lime 57 | 2 |
| category | Playtomic | 3 | 8.1 | violet 26 | blue 22 | 4 | 1.81 | violet 72 | 2 |
| category | ralli | 2 | 8.7 | lime 36 | yellow 36 | 3 | 2.41 | lime 54 | 2 |
| category | rallyracket | 2 | 4.8 | lime 46 | green 42 | 2 | 0.62 | green 61 | 2 |
| category | rallyup | 3 | 4.4 | yellow 58 | lime 21 | 3 | 1.01 | yellow 100 | 1 |
| category | runday | 2 | 1.8 | violet 85 | orange 10 | 2 | 0.34 | violet 98 | 1 |
| category | smartscore | 3 | 17.1 | lime 44 | blue 42 | 2 | 4.45 | blue 75 | 2 |
| category | 스매시 | 2 | 2.5 | blue 41 | orange 35 | 4 | 0.60 | orange 51 | 2 |
| category | 소모임 | 3 | 5.1 | blue 61 | yellow 14 | 2 | 1.08 | blue 96 | 1 |
| category | 숨고 | 2 | 1.5 | orange 23 | green 21 | 5 | 0.02 | violet 72 | 2 |
| category | Spond | 3 | 4.7 | lime 37 | red 33 | 4 | 1.33 | red 49 | 3 |
| category | strava | 2 | 6.1 | orange 91 | yellow 5 | 1 | 0.07 | orange 78 | 2 |
| category | swingvision | 2 | 7.2 | green 47 | orange 42 | 2 | 2.61 | green 52 | 2 |
| category | teamsnap | 2 | 12.7 | blue 92 | lime 4 | 1 | 3.41 | blue 100 | 1 |
| category | 테니스프렌즈 | 3 | 6.6 | lime 91 | green 8 | 1 | 3.54 | lime 95 | 1 |
| category | tennispal | 2 | 2.8 | lime 85 | red 6 | 1 | 0.56 | lime 98 | 1 |
| category | usta | 2 | 35.2 | blue 100 | violet 0 | 1 | 28.01 | blue 100 | 1 |
| category | utr | 2 | 1.1 | blue 35 | green 32 | 3 | 0.02 | green 87 | 2 |
| global | airbnb | 4 | 14.2 | orange 50 | red 35 | 2 | 5.00 | red 54 | 2 |
| global | cashapp | 4 | 2.4 | green 83 | orange 9 | 1 | 1.47 | green 98 | 1 |
| global | duolingo | 3 | 5.6 | blue 33 | yellow 32 | 2 | 4.70 | blue 35 | 2 |
| global | gmail | 3 | 1.0 | blue 64 | green 23 | 3 | 0.68 | blue 69 | 2 |
| global | klarna | 2 | 4.5 | red 98 | yellow 1 | 1 | 3.92 | red 99 | 1 |
| global | linear | 2 | 2.1 | violet 86 | orange 3 | 1 | 1.69 | violet 97 | 1 |
| global | monzo | 3 | 10.5 | red 60 | orange 22 | 2 | 6.48 | red 76 | 1 |
| global | notion | 4 | 0.9 | blue 48 | red 15 | 4 | 0.39 | blue 66 | 3 |
| global | Playtomic | 2 | 6.5 | red 36 | orange 19 | 4 | 1.45 | red 66 | 2 |
| global | revolut | 2 | 3.8 | orange 99 | yellow 1 | 1 | 2.21 | orange 100 | 1 |
| global | robinhood | 2 | 12.3 | yellow 59 | lime 25 | 3 | 11.61 | yellow 60 | 3 |
| global | Spond | 3 | 10.6 | red 39 | lime 19 | 4 | 7.32 | red 48 | 3 |
| global | spotify | 2 | 6.8 | orange 29 | violet 23 | 5 | 2.91 | violet 47 | 3 |
| global | stripe | 3 | 1.3 | green 72 | violet 23 | 2 | 0.79 | green 88 | 2 |
| global | teamsnap | 3 | 3.8 | blue 59 | orange 19 | 2 | 1.78 | blue 68 | 2 |
| global | 토스 | 4 | 3.1 | orange 24 | lime 20 | 5 | 0.57 | yellow 31 | 4 |
| global | uber | 2 | 0.1 | green 58 | yellow 31 | 2 | 0.06 | green 69 | 2 |
| global | venmo | 4 | 14.7 | blue 67 | violet 13 | 2 | 10.75 | blue 71 | 2 |
| global | wise | 4 | 23.8 | lime 49 | green 40 | 2 | 18.91 | lime 52 | 2 |
| KR | 29CM | 3 | 1.1 | orange 58 | yellow 25 | 2 | 0.13 | orange 75 | 2 |
| KR | 배민 | 4 | 17.8 | violet 50 | green 17 | 3 | 11.48 | violet 61 | 2 |
| KR | 쿠팡 | 4 | 3.8 | green 30 | yellow 21 | 3 | 0.84 | green 42 | 3 |
| KR | 당근 | 4 | 2.8 | orange 76 | blue 8 | 1 | 1.67 | orange 87 | 1 |
| KR | 카카오뱅크 | 4 | 2.8 | yellow 44 | orange 42 | 2 | 2.01 | yellow 47 | 2 |
| KR | 카카오맵 | 3 | 14.0 | teal 49 | yellow 18 | 3 | 8.68 | teal 74 | 1 |
| KR | 카카오페이 | 5 | 6.1 | blue 41 | red 22 | 4 | 4.39 | blue 46 | 4 |
| KR | 카카오톡 | 3 | 8.8 | blue 55 | lime 29 | 2 | 5.51 | blue 84 | 1 |
| KR | 크림 | 4 | 9.4 | orange 54 | red 24 | 3 | 7.04 | orange 63 | 2 |
| KR | 컬리 | 4 | 13.7 | violet 43 | orange 30 | 3 | 5.88 | violet 84 | 1 |
| KR | 밀리의서재 | 3 | 5.9 | red 32 | orange 18 | 5 | 1.71 | violet 28 | 4 |
| KR | 무신사 | 4 | 5.2 | orange 70 | red 12 | 2 | 3.38 | orange 82 | 1 |
| KR | 네이버 | 4 | 10.0 | violet 35 | green 35 | 2 | 6.51 | green 41 | 2 |
| KR | 네이버지도 | 4 | 8.0 | orange 41 | yellow 23 | 3 | 2.16 | yellow 36 | 5 |
| KR | NOL | 4 | 7.5 | blue 34 | violet 24 | 4 | 4.74 | blue 33 | 3 |
| KR | 오늘의집 | 4 | 1.5 | blue 50 | orange 35 | 2 | 0.84 | orange 45 | 2 |
| KR | 올리브영 | 4 | 2.5 | red 79 | orange 7 | 1 | 1.49 | red 91 | 1 |
| KR | 토스 | 4 | 2.9 | orange 22 | red 21 | 5 | 0.58 | yellow 29 | 5 |
1.4 The statement the owner wants tested
| Brand-hue share of saturated pixels (raw / solid) | Hue families ≥ 10% (median / mean) | Saturated area (raw median) | |
|---|---|---|---|
| TwoMore (11 frames, bottom 9% cut) | green 96% / 100% (range 65–100 / 98–100) | 1 / 1.1 (10 of 11 frames = 1) | 1.4% |
| Peers with a known brand hue (n = 11: 토스 blue, 당근 orange, 배민 violet, 네이버 green, 밴드 green, 카카오뱅크 yellow, 카카오톡 yellow, 테니스프렌즈 lime, Monzo coral, Wise lime+green, Cash App green) | median 50% / 61% (range 8–91 / 7–98) | — | — |
| All 72 peers, top hue family | median 52% / 69% | raw 2 / 2.56, solid 2 / 1.98; ≥ 2 families: 57 of 72 raw, 42 of 64 solid | 5.1% (IQR 2.6–9.3) |
| Korean mainstream (18) | top family 46% / 47% | raw 3 / 2.78; 16 of 18 have ≥ 2 | 6.0% |
| Category (35) | top family 54% / 84% | 2 / 2.46 | 4.8% |
| Global (19) | top family 59% / 67% | 2 / 2.53 | 4.5% |
Per-peer brand-hue shares (raw / solid): 토스 18 / 23 · 당근 76 / 87 · 배민 50 / 61 · 네이버 35 / 41 · 밴드 23 / 43 · 카카오뱅크 44 / 47 · 카카오톡 8 / 7 · 테니스프렌즈 91 / 95 · Monzo 60 / 76 · Wise 88 / 95 · Cash App 83 / 98.
Honest limits of the test: (1) three peers are as single-hued as we are, 테니스프렌즈 (95%), Wise (95%) and Cash App (98%), so "one hue" is not wrong in itself; the difference is where that hue sits (§3: a lime identity card and a FAB, an ink-black CTA, a green ring) rather than on every control. (2) Raw numbers carry photos and pictograms; the solid view removes them and still gives a peer median of 2 families. (3) Our own saturated area is low, so any fix is a re-allocation, not a reduction.
Two supporting measures (scripts bench/lane_d_blobs.py, bench/lane_d_tint.py; outputs bench/lane_d/blob_stats.txt, tint_stats.txt):
- Large solid saturated objects per frame (components ≥ 90 px² at 390 px width): peers median 2.5; ours: 홈 1 (segmented pill), 기록 1 (pill), 프로필 0, 코트 상세 1 (
#006D37, 612 px², the filled 저장 취소 disc), 테니스장 list 4 (the four saved-bookmark glyphs, 110 px² each), 일정 상세 0 once the CTA band is cut. With the band: the CTA band alone is 3.4–4.5% of the frame on 4 of 4 phone frames that carry it; the median peer's entire solid-fill saturated area is 1.7%. - Pale-tint vs neutral-grey fills (flat fills, C* 6–30 and L* ≥ 80 vs C* < 6 and L* 80–97.5): ours on hubs median 2.3% pale tint (홈 3.5, 기록 4.2, 프로필 2.3, 1440 홈 1.7, 기록 3.6, 클럽 1.6, 경기 0.0; all green-hued) and 0.04% neutral grey; on 코트 상세 / 일정 상세 the pale-green map placeholder is 17.4% / 20.9%. Peers (n = 72): median pale tint 0.9%, median neutral-grey fill 5.2% (IQR 1.1–10.9). Only 6 of 72 peers exceed 5% tint, and each is one bounded hero block (오늘의집 15.1, 컬리 10.3, 네이버지도 10.3, 카카오뱅크 8.6, 배민 7.9, Klarna 5.9).
2. Where our brand colour goes: code census by role
Scope: packages/ui/src, packages/features/*/src, packages/app/src/presentation (1,310 non-test .ts/.tsx files; 168 reference a brand token), comment lines dropped, the dev panel (19 sites, not shipped UI) listed separately. Tokens counted: $primary, $primarySubtle, $primaryMuted, $primaryText, theme.primary, colors.primary, primary[…]; $brand and $iconActive do not exist in the code. Lines classified by pattern rules plus 32 manual overrides (bench/lane_d/census_classify.py; the classified list is bench/lane_d/census_classified.json). Counts are token sites (lines), a lower bound for impact because shared primitives multiply them (§2.2).
2.1 Counts per role
| Role | Sites | Share of 307 | Sub-roles (count) | Representative site |
|---|---|---|---|---|
| (a) chrome / controls | 203 | 66% | selection / active / toggle state (107); link-style text and nav icons (56); UI control primitives (27: chips, search, pickers, action circles, toggle); button/pill/toggle fill (8); $primary selection borders (4); focus ring (1) | packages/ui/src/selection-chip.tsx:122-123 (selected = $primary border + $primarySubtle fill); packages/ui/src/button.tsx:79 (primary fill; defaultVariants.variant = 'primary' at :138); packages/ui/src/pressable-visual.ts:85 (web focus ring $primary); packages/ui/src/section-header.tsx:162 (link) |
| (b) content accents | 72 | 23% | content primitives: badge / avatar / meter / date tile / banner / stat tone (26); feature icons (17); meters, dots, series, team accents (22); numerals (7) | packages/ui/src/avatar-bubble.tsx:139,151 (initials: $primarySubtle bg + $primary text); packages/ui/src/progress-bar.tsx:90-112 (fill $primary, track $primarySubtle); packages/app/src/presentation/components/elo-fact-row.tsx:52 and packages/features/records/src/cards/hero-card.tsx:59 (ELO numeral and ring 1) |
| (c) status semantics | 188 | (separate) | $error 75, $success 29, $warning 24, badge-pair tokens 84 ($badgeWarning* 30, $badgeError* 17, $badgeSuccess* 12, neutral 10, muted 5, info 4, accent 3, premium 2, surface 1) | packages/app/src/presentation/components/status/status-variant-maps.ts:43-137 (every status → one of nine badge variants); packages/ui/src/badge.tsx:64-72 |
| (d) decoration | 32 | 10% | tint fills and panels behind content ($primarySubtle backgrounds, monogram letters, hero veils) | packages/ui/src/empty-state.tsx:78,84 (64 px disc, 101 EmptyState sites); packages/app/src/presentation/components/tier-badge-utils.tsx:55 (tier chip fill); packages/ui/src/summary-card.tsx:27 |
| Non-brand content palette (court / medal / accent / Saturday blue) | 10 in scope | — | $accentWarm 4 (team 2 in match-scoreboard.tsx:331,371), $calendarSat 4, $accentCool 1, $medalGold 1; court tokens reach the UI through visual-accents.ts (outside the census scope) | packages/app/src/config/visual-accents.ts:84-92 (court surfaces), :117-122 (medals) |
| Dev panel (excluded) | 19 | — | — | packages/app/src/presentation/components/dev-panel/scenarios-pane.tsx |
Brand-token sites (a+b+d) = 307; brand = 61% of all colour-token lines (307 of 307 + 195 semantic/content lines, 3 of them overlapping). Of the 82 content-accent sites (brand 72 + non-brand 10) 88% are brand green.
2.2 Components that paint brand green on every screen (call-site multipliers)
| Component | Where it paints brand | Call sites in scope | Proof |
|---|---|---|---|
| Tab bar active icon + label | tabBarActiveTintColor: primary | 5 tab roots | apps/mobile/app/(tabs)/_layout.tsx:62; ours-phone/records.png (trophy + 기록) |
| Segmented control (PillNav / SegmentedTabs) | solid $primary sliding pill on $surfaceTertiary (#E5FAEA) track, label $primaryText | 13 PillNav + 7 SegmentedTabs; MainTabShell hosts it on the 홈, 클럽, 기록 and 메시지 roots (frames: 홈, 기록, 클럽 at 1440) | packages/ui/src/pill-nav.tsx:223,400 (indicator), :80,:164 (track), :140 (label), :192 (overflow-pill fill); packages/ui/src/main-tab-shell.tsx:120; ours-phone/root.png, records.png, ours-1440/clubs.png |
| Primary Button / BottomCtaBand | $primary fill; band is the screen's only bottom element | Button 63 (12 explicit primary, 27 outline, 4 ghost, 3 secondary, 2 destructive, ~15 default or dynamic = primary); BottomCtaBand 45 | packages/ui/src/button.tsx:79,138; ours-phone/sessions_*.png, courts.png, directory-venues_*.png, clubs_*.png |
| Selection chips / cards / checks / toggles | selected = $primary border + $primarySubtle fill + $primary 700 label | SelectionChip 106, SelectionCard 19, DropdownChip 23, TogglePill 17, SelectionCheck 2 | packages/ui/src/selection-chip.tsx:122-123; chip-label-visual.ts:24; selection-card.tsx:45-46; toggle-pill.tsx:40; selection-check.tsx:70-71; ours-phone/courts.png ("전체 145") |
| Initials avatars | default tone primary: pale-green disc + green initials | AvatarBubble 27 (6 neutral, 7 explicit primary, ~14 default) | packages/ui/src/avatar-bubble.tsx:160 (tone = 'primary'), :139,151; sessions_*.png ("D나"), profile.png ("DE") |
| Empty-state disc | $primarySubtle 64 px disc behind a stroke icon | EmptyState 101 | packages/ui/src/empty-state.tsx:78,84 |
| Links and section actions | $primary text and chevron | SectionHeader 25, HeaderTextAction 6, LinkifiedText 1, plus 56 link-style sites | packages/ui/src/linkified-text.tsx:68; header-text-action.tsx:58; section-header.tsx:162,165; courts.png ("제안 내역") |
| Bookmark / saved | saved icon $primary | SaveButton, session card, venue rows | packages/app/src/presentation/components/sessions/save-button.tsx:104,125; session-card-v2.tsx:392; courts.png (four green bookmarks) |
| Progress and rings | $primary fill on $primarySubtle track; ELO ring = primary + success + teal | ProgressBar 15, MiniBarMeter 3, CapacityRing 2 | packages/ui/src/progress-bar.tsx:112; hero-card.tsx:59-61; records.png |
| Stat numerals | tone="primary" → $primary | tone="primary" appears on 21 JSX sites (StatStrip, Well, AvatarBubble, rows) | packages/ui/src/tone-colors.ts:29; stat-grid.tsx:38; profile.png ("50+", "64%") |
| Focus ring (web) | outlineColor: '$primary' on every Pressable | all | packages/ui/src/pressable-visual.ts:85 |
| Web sidebar | active item $primary ink on $primarySubtle; "번개 만들기" primary button | every desktop screen | apps/web/components/app-nav.tsx:92,111,120,250,260; ours-1440/root.png |
2.3 Every colour token we have for content, and where it is used
| Token (value, hue) | Defined | Consumers | State |
|---|---|---|---|
primary ladder #E5FAEA … #006D37 … #022711 (151°) | packages/ui/src/tokens.ts:30-41, classic-grass.theme.ts:30-44 | 307 sites (§2.1) | Overloaded: control, number, icon, ring, text, selection |
optic #C6E84B (115°, L* 87) | tokens.ts:47 | none in packages/ or apps/ (only wireframes docs/twomore_design_system/wireframes/visual-tone.jsx and seed logos) | Unused |
courtGreen #006633, courtClay #B84A2E (42°), courtHard #247BA0 (245°) | tokens.ts:89-91, classic-grass.theme.ts:45-49 | packages/app/src/config/visual-accents.ts:84-92 (surface accents), :100-106 (facility), :161-175 (signal categories) → VenueLink pill and tile (venue-link.tsx:75-98), venue card/row/hero/tile | Used on venue surfaces only |
accentWarm #8A5A12 on #FFF4D6, accentCool #4B0082 on #F2E8FF (314°) | classic-grass.theme.ts:78-81 | match scoreboard team 2 (match-scoreboard.tsx:331,371), indoor / shop / social / chat accents (visual-accents.ts), one settings icon | Sparse |
medalGold #D9A621, Silver, Bronze, Platinum | tokens.ts:99-102, shared-semantics.ts:32-37 | ACHIEVEMENT_TIER_COLORS (visual-accents.ts:117-122) → 프로필 업적 row (profile.png: bronze rings, gold trophy), podium (leaderboard-podium.tsx:44-47), king court border (court-card.tsx:231) | Used on profile / ranking only |
ELO tier palette #CD7F32 bronze, #8E8E93, #D4A017, #9B59B6, #E74C3C (+ dark variants) | packages/app/src/domain/rules/tier.config.ts:67-130 | groupTierColor → GroupTierDot (ranking-tab.tsx:416, row-cells.tsx:24, club-groups-section.tsx:253) and the TierRangeBadge dot | Dropped where it matters: TierBadgeChrome fills with $primarySubtle (tier-badge-utils.tsx:55), so every tier chip ("NTRP 3.0–3.5" in records.png, "신뢰 멤버" in profile.png) is the same mint whatever the tier |
| Club identity: 8 flag presets at 45° hue steps (152, 197, 242, 287, 332, 17, 62, 107) plus a custom hue | packages/ui/src/flag-medallion-presets.ts:132-139 (hueFlagPalette) | FlagMedallion, club-card previews, create-club step 8 | Built; the fallback logo bubble is brand tint (club-logo-bubble.tsx:45), not the identity hue |
Status: success #14804A (153°), warning #946200 (76°), error #B8332E (33°), info #4B0082 (314°); eight light/dark badge pairs incl. accent teal #007B6B on #E0F8F3 (179°), neutral #F0F0F3, muted #ECEEF0 | shared-semantics.ts:19-23, status-badge-palette.ts:52-61 | 188 sites; info badge for waitlist / approval | Consistent, but success is ΔE76 7.5 from brand and its tint #EBF8F1 is ΔE76 5.5 from $primarySubtle |
calendarSat #1565C0, $error for Sunday | shared-semantics.ts:27 | 4 calendar sites | Used (Korean calendar convention) |
Third-party kakao #FEE500, naver #03C75A | tokens.ts:94-96 | naver-badge.tsx:19 ($naver); $kakao has no consumer in the census scope | Partly used |
| Chart series | none: charts take resolved hex | ELO ring = primary, success, badgeAccentText (hero-card.tsx:59-61: two greens and a teal); outcome donut = success / error / textSecondary (outcome-visuals.ts:16); heatmap = primary / primarySubtle (activity-heatmap-card.tsx:126-128) | No categorical palette |
What the frames show (not what the tokens allow): ours-phone/records.png paints the segmented pill, the NTRP chip tint, the ELO numeral, the tri-ring, the progress bar, the "골드까지 200점" text, the three 개인 기록 icons, the heatmap cells and the active tab in one green. Colour cannot tell the owner which of those is a button.
3. Component colour matrix vs peers
Legend: BS brand solid · BT brand tint · N neutral (ink or grey) · S semantic (success / warning / error colours) · M multi-colour · – not visible in the frames on disk. Frame ids are crops/<app>_f<N> (Korean), b_crops/<app>_<N> (category), crops_c/m_<app>_<N> (global); published tokens are cited from docs/design/research/visual-benchmark-2026-10-08/lane-a-korean-consumer.md §2 and lane-c-platform-global.md §2. Composite sheets I looked at: bench/lane_d/sheets/*.png.
| Component | TwoMore (code + frame) | 토스 | 당근 | 카카오뱅크 | 배민 |
|---|---|---|---|---|---|
| Segmented control / selection | BS pill on BT track: pill-nav.tsx:223,400, track :80; ours-phone/root.png, records.png | N grey track + white pill (toss_f2 "현재가 | 평가금"); tabs ink underline | N ink-filled chip (danggeun_f1 "전체"); ink toggle (_f7) | N ink underline tab (kakaobank_f2 "통장"); outlined chips | N ink underline (baemin_f2 "전체") |
| Active tab (bottom / top) | BS icon + label: (tabs)/_layout.tsx:62; records.png | N ink underline (toss_f2, _f3) | N ink icon in capsule (danggeun_f1, _f2) | N ink icon in grey capsule (kakaobank_f0) | N ink underline (baemin_f2–_f5) |
| Primary CTA | BS full-width band, 3.4–4.5% of frame: button.tsx:79, BottomCtaBand; sessions_*.png, courts.png | BS blue CTA (lane C frames_c/toss/839333328_4, #4880EE; TDS blue500 #3182F6); none in crops | BS orange FAB "+ 글쓰기" (_f1, _f2); 결제 is ink (_f5) | – (이체 is grey) | BS violet block + violet icon buttons (baemin_f3, _f4) |
| Secondary buttons | N secondary fill + 1 px $borderStrong, outline (button.tsx:98-120); no frame | N grey #F2F4F6 "송금 / 내역" (toss_f0) | N grey "충전 / 송금", white outline "자세히" (danggeun_f5) | N grey pill "이체" (kakaobank_f0, _f2) | – |
| Links | BS text: linkified-text.tsx:68, section-header.tsx:162; "제안 내역" courts.png | BS blue "포인트 받기" (toss_f5) | N grey "더 많은 매물 보기" (danggeun_f7) | BS blue "비즈니스 알림", count "9" (kakaobank_f6, _f2) | BS violet / blue (baemin_f4, _f2) |
| Chips / filters | N outline resting; selected BT fill + BS border + BS 700 text: selection-chip.tsx:122-123; courts.png | – | N outline; selected ink fill (danggeun_f1, _f2) | N white outline pills (kakaobank_f0, _f2) | N outline tags (baemin_f2) |
| Status badges | S palette; RSVP confirmed BS (status-variant-maps.ts:63); "참석" mint in sessions_*.png | S red "66%" pill, N grey "방송중" (toss_f3, _f5) | BT orange "바로구매", N grey tags (danggeun_f1, _f2) | N outlined "계좌번호" (kakaobank_f3) | S red "30%" pills (baemin_f3) |
| Progress / meters | BS fill on BT track (progress-bar.tsx:112); ring BS + green + teal (hero-card.tsx:59-61) | – | – | – | – |
| Avatars / initials | BT disc + BS initials by default (avatar-bubble.tsx:160); "D나", "DE" | M 3D pictogram discs (toss_f5) | M pictogram discs, photos (danggeun_f5, _f7) | M per-account colour discs (kakaobank_f0, _f2) | M brand tiles (baemin_f5) |
| Section headers | N ink title + BS link (section-header.tsx:162; "개인 기록" records.png) | N ink; grey meta (toss_f0) | N ink bold (danggeun_f5) | N ink; grey "전체보기" (kakaobank_f2) | N ink (baemin_f2) |
| Icons: nav vs feature | nav N stroke ($iconMuted); feature BS stroke (personal-records-section.tsx:108; records.png) | nav N; feature M 3D (toss_f5) | nav N; feature M (danggeun_f5, _f7) | nav N; feature M (kakaobank_f0) | nav N; feature M tiles (baemin_f5) |
| Hero / identity block | no solid block; BT map placeholder 17–21% of detail frames (directory-venues_*, sessions_*) | N white cards on grey (toss_f0) | N white cards (danggeun_f5) | BT pale blue #E1F3FF 19.8% (kakaobank_f3) + yellow-gradient account card (_f0) | BS violet 26.2% (baemin_f3); BT mint banner (_f2) |
| Numerals | BS ELO / stat figures (hero-card.tsx:101, elo-fact-row.tsx:52; profile.png) + N | N ink big; S red gain (toss_f2) | N ink bold (danggeun_f5) | N ink bold (kakaobank_f2) | N ink; S red % (baemin_f3) |
| Dividers | N hairline $borderSubtle #DFE7E1 (C* 4, green-grey) | none, tone gap | N hairline #F3F4F5 (lane A) | N hairline (kakaobank_f2) | N hairline (lane A) |
| Component | 밴드 | 테니스프렌즈 | Playtomic | Monzo |
|---|---|---|---|---|
| Segmented control / selection | BS only on the selected date disc (band_3) and the poll check + bar, blue (band_6) | – | N navy-ink fill: selected date, duration, slot (b_crops/playtomic_4); tabs ink underline | BS blue-teal toggle ON (m_monzo_4 "Face ID") |
| Active tab | N ink filled icon (band_2 bottom bar) | BS green icon + label (tennisfriends_3 bottom bar) | N ink underline "Book" (playtomic_4) | BS blue-teal icon + label in grey capsule (m_monzo_0 bottom) |
| Primary CTA | BS green FAB (band_3); blue pill (band_2) | BS green FAB "+" (tennisfriends_3) | – | BS coral card with dark / white pills (m_monzo_0 "Add money") |
| Secondary buttons | N icon + text "밴드 만들기 | 밴드 가입" (band_2) | – | N white 1 px outline (playtomic_4 "90 min") | N dark translucent pills on the card (m_monzo_0) |
| Links | BS blue "Today" (band_3) | BS green list icons (tennisfriends_5) | BS blue "Playtomic Open Match", "+ Available" (playtomic_2, _3) | BS green "Last updated: Now" (m_monzo_4) |
| Chips / filters | BT green "참가신청서", violet "투표 중" (band_3, _6) | M lilac "총 3명", lime "Lv 2.0-4.5", grey "복식" in one card (tennisfriends_3) | N white 1 px; selected navy (playtomic_4) | – |
| Status badges | S red LIVE and count dots (band_2, _6) | N grey "마감"; S red text "차단 관리" (tennisfriends_3, _0) | – | S green check "24/7", green / red amounts (m_monzo_4, _0) |
| Progress / meters | BS blue vote bar + N grey (band_6) | N white bar on lime (tennisfriends_5) | – | S green savings bar (m_monzo_5) |
| Avatars / initials | photo squircles (band_2) | photo with cyan ring; brown monogram tile "민호" (tennisfriends_3) | photo + lime level chip with dark numeral (playtomic_3) | M merchant logos (m_monzo_0) |
| Section headers | N ink; grey "전체보기" (band_2) | N ink (tennisfriends_5) | N ink (playtomic_4) | N ink (m_monzo_0 "Activity") |
| Icons: nav vs feature | N ink stroke (band_2); calendar dots M (band_3) | nav N grey; feature BS green; danger S red (tennisfriends_5, _0) | N stroke; red heart S (playtomic_2) | nav N; feature M logos |
| Hero / identity block | – (photo squircles) | BS lime #CCFD01 identity card 12.1% (tennisfriends_5; lane B) | photo hero (playtomic_4) | BS coral balance card 13.8% (m_monzo_0; lane C) |
| Numerals | N ink; BS blue vote count (band_6) | N ink on lime "36.5 / 100" (tennisfriends_5) | N on lime chip "1.2" (playtomic_3) | N ink; S green money-in (m_monzo_0) |
| Dividers | N hairline | N hairline in white cards on grey | N hairline | N hairline / tone |
Row summary: where the nine columns spend BS on a control, it is a FAB, a CTA, one hero block or a toggle (당근, 밴드, 테니스프렌즈, Monzo, 배민); where they spend it on selection or tab state it is the minority (테니스프렌즈, Monzo). TwoMore is the only column that fills the selected segment, the tab icon, the chip, the avatar, the empty-state disc, the numeral and the progress bar with it.
Additional peers inspected for the tallies in §4 (not in the table for width): 카카오페이 (crops/kakaopay_f1, _f5: ink tab underline, ink capsule bottom nav, blue numerals, multi-colour pictograms), 네이버 (naver_f2–_f4: ink tabs, green only on the centre-tab ring and a few links, multi-colour pictograms), 크림 (kream_f3: ink tabs and bottom bar), 무신사 (musinsa_f2: all ink plus one red-orange promo block), 스매시 (b_crops/smash_1, _5: ink-bold active tab, neutral outlined filters, orange tier glyph), 문토 (munto_3, _4: ink-filled chip, red "마감임박" pill), Cash App (crops_c/m_cashapp_0, _2: ink-black pills, green ring), Spond (b_crops/spond_0, _1: blue solid "Attending", red-tint "Decline", orange / red / blue status icons, multi-colour group avatars), TeamSnap (crops_c/m_teamsnap_1, _3: blue header block with segmented tabs, green ✓ / red ✗ / grey ? RSVP squares, blue active tab).
4. Falsifiable observations
Each names the evidence and what would disprove it.
- One hue does every job. Median 96% (min 65, max 100) of our saturated pixels are brand green across 11 non-photo frames, 100% of our solid fills; peers' own brand hue is 50% raw / 61% solid (n = 11) and their top family 52% / 69% (n = 72). 10 of our 11 frames hold exactly one family ≥ 10%; 57 of 72 peers hold ≥ 2. Disproved by a representative set of our content screens, with seeded data, where a second hue exceeds 10% (today only
profile.png, via medals, and the photo hero ofclubs). - We do not use more colour than peers; we use it on different things. Median saturated area 1.4% vs 5.1%; large solid saturated objects 0–1 vs 2.5 per frame; 11 of 11 non-photo frames sit below the peer median. Disproved by any peer-set re-measure that puts the peer median below 2%.
- Brand sits on controls. 203 of 307 brand sites (66%) are chrome; 107 of those are selected / active states. In the 17 peers I inspected (토스, 당근, 카카오뱅크, 카카오페이, 네이버, 배민, 크림, 무신사, 밴드, 테니스프렌즈, Playtomic, 스매시, 문토, Monzo, Cash App, Spond, TeamSnap), 12 render tab, segment and selected-chip state in ink or neutral (토스, 당근, 카카오뱅크, 카카오페이, 네이버, 배민, 크림, 무신사, Playtomic, 스매시, 문토, Cash App), 4 colour the active tab icon/label only (테니스프렌즈, Monzo, Spond, TeamSnap), 1 mixes (밴드: ink tab bar, brand selected-date disc), and 0 fill a selected segment solid brand on a brand-tinted track (TeamSnap's segmented tabs sit inside a solid blue header block). Disproved by finding a shipping app with a brand-tint track + solid-brand pill.
- Peers fill with grey where we fill with mint. Pale-green flat fills: 3.5% (홈), 4.2% (기록), 17–21% (detail frames) vs peers median 0.9% tint and 5.2% neutral grey; ours neutral grey on hubs 0.04%. Only 6 of 72 peers exceed 5% tint, each a single bounded hero block. The segmented track (
$surfaceTertiary#E5FAEA, C* 11) is the largest repeat offender; our own near-neutral rungs exist (gray100 #F0F5F1C* 2.7,badgeNeutralBg #F0F0F3C* 1.5). - Our CTA band alone out-weighs a peer's entire solid-colour budget. Band
#006C36= 3.4% (일정 상세), 3.4% (코트 상세), 3.7% (클럽), 4.5% (테니스장) of the frame; the peer median of all solid saturated fills is 1.7%; the peers that spend more are bounded hero blocks (Monzo 6.5%, 배민 11.5%, Wise 18.9%), not a bottom bar. - Green means three things.
#006D37(brand, 151°) vs#14804A(success, 153°): ΔE76 7.5; tints#E5FAEAvs#EBF8F1: ΔE76 5.5. Inours-phone/sessions_*.pngthe "참석" chip and the "오늘" chip onroot.pngread as the same family; the ELO ring usesprimary+success+ teal (hero-card.tsx:59-61), two greens in one chart. Peers separate roles by hue (Toss blue vs#03B26Cgreen500; Monzo coral vs green money-in; 밴드 green FAB vs red / blue / violet states) or by ink (Cash App black buttons, green only for the ring and money-in). - The content palette is built and unspent.
optichas 0 consumers (tokens.ts:47); the ELO tier palette (5 hues,tier.config.ts:67-130) feeds group dots but the tier chip is brand tint (tier-badge-utils.tsx:55); club identity hues exist (flag-medallion-presets.ts:132-139) but the no-logo fallback is mint (club-logo-bubble.tsx:45); court-surface colours only reach venue pills. Peers use content colour in 10 of 17 (토스, 당근, 카카오뱅크, 카카오페이, 네이버, 배민, 밴드 calendar dots, 테니스프렌즈 chips, Monzo merchants, Spond status icons); seven stay near-monochrome (크림, 무신사, Playtomic, 스매시, 문토, Cash App, TeamSnap). - The nearest peer is mono-hued on purpose. 테니스프렌즈 is 95% lime in solid fills, yet it keeps brand colour for one identity card (12.1% of the frame), a FAB and the active tab, and uses grey for everything else including chips, and ball-yellow for level (
Lv 2.0-4.5). The same pattern is in Playtomic (lime level circle with a dark numeral, ink selection). This weakens "mono-hue is bad" and supports "brand on one block, ink on controls".
5. Colour budget proposal for TwoMore
Direction (from benchmark §3): white, with one block. Six roles, each tied to frames above; it narrows benchmark §3.2–§3.3 (one brand block, optic as the small numeric accent) with the control-state and content-colour findings of this lane. Token names are the ones that exist today unless marked new.
| Role | Colour | Where | Evidence |
|---|---|---|---|
Brand solid $primary #006D37 + $primaryText | the screen's one primary action and one hero block | Button primary / BottomCtaBand / web "번개 만들기"; the 홈 day block, 기록 ELO block, 프로필 identity block (benchmark §3.2); toggle ON, checkbox / radio, selected date disc | 당근 FAB, 밴드 FAB + date disc, 테니스프렌즈 FAB + lime card, Monzo coral card, 배민 한그릇 block |
Brand as text $primary | inline links only | LinkifiedText, HeaderTextAction, inline "더보기" | 토스 blue links, Playtomic / Spond / Monzo link colour |
Ink / neutral $text, $textInverse, $surfaceSecondary, $borderSubtle, $iconMuted | everything that is a control state | tab bar active (ink filled icon + ink label); segmented control = neutral track + white pill with hairline; selected chip = ink fill ($text bg, $textInverse label); section "전체 >" actions; stroke icons; person-initial avatars; progress track | 12 of 17 peers; Toss toss_f2, 당근 danggeun_f1, Playtomic playtomic_4, Munto, Cash App |
Semantic $success, $warning, $error, $info, $badge* | status only: paid / unpaid, live, closed, RSVP, deltas (+61 / −), validation | status badges, outcome donut, ELO delta (completed-row.tsx:171), calendar Sunday / Saturday | 토스 red / green gains, 밴드 LIVE red, TeamSnap RSVP squares, Monzo money-in |
Level accent optic #C6E84B (L* 87) + $text ink | level and rating only | NTRP / ELO-tier chip under avatars and in session rows, "today" dot, a progress fill in the ELO block | Playtomic #CCFC04 level circle, 테니스프렌즈 #CCFD01, 8 of 13 racket apps (benchmark §1.5) |
| Content accents (small, multi) | content identity, never chrome | court surface ($courtHard, $courtClay, $courtGreen) as a 12–16 px dot/tile on session and venue rows; club identity hue (FlagMedallion presets) on club monogram, calendar dots and club cards; tier hue (TIER_DEFINITIONS) on the tier glyph ring; medal tones on achievements | 밴드 calendar dots, 테니스프렌즈 chips, 카카오뱅크 account discs, Toss / 당근 pictograms |
5.1 What changes per component
| Component (file) | Today | Proposed | Token |
|---|---|---|---|
Segmented control (pill-nav.tsx:80,164,223,400) | BS pill on mint track | white pill (hairline) on neutral track, ink label; selected = weight + ink | track: new $controlTrack = gray100-class (C* ≤ 3) or $badgeNeutralBg; pill $card; label $text |
Tab bar (apps/mobile/app/(tabs)/_layout.tsx:62) | BS icon + label | ink filled icon + ink label; brand only for the unread dot | tabBarActiveTintColor: themeHex(theme,'text') |
Chips (selection-chip.tsx:122-123, chip-label-visual.ts:24) | BS border + BT fill + BS text | resting outline; selected ink fill + inverse label | $text / $textInverse (alias new $chipOn, $chipOnText) |
Selection card / dropdown chip (selection-card.tsx:45-46, dropdown-chip.tsx) | BS border + BT fill | 2 px ink border, white fill; keep BS only for the check mark | $text |
Avatar default (avatar-bubble.tsx:160) | tone primary | tone neutral (grey disc, $textSecondary initials); club monogram uses identity hue | $badgeNeutralBg / $badgeNeutralText |
Empty-state disc (empty-state.tsx:78,84) | $primarySubtle | $badgeNeutralBg disc, $iconMuted stroke | existing |
Section header action (section-header.tsx:162,165) | BS text + chevron | $textSecondary text + chevron (Toss "내 계좌 >", 카카오뱅크 "전체보기") | existing |
Stat numerals (stat-grid.tsx:38, tone-colors.ts:29, profile 50+ / 64%) | BS | $text; BS only for the single hero numeral inside the brand block (white on $primary) | existing |
Feature icons (personal-records-section.tsx:108, head-to-head-screen.tsx:156) | BS stroke | $iconMuted stroke (nav grammar); category-coloured only in entry tiles | existing |
Bookmark saved (save-button.tsx:104,125) | BS | ink filled (크림 SAVED, 당근 heart); not brand | $text |
Tier chip (tier-badge-utils.tsx:55) | $primarySubtle fill | white / neutral chip with a 6–8 px tier-hue dot or ring from TIER_DEFINITIONS | new theme slots tierBronze…tierRed aliasing the domain hexes |
Level chip (new use of optic) | none | 22 px circle / pill, optic fill, $text bold numeral | new $levelChipBg = optic, $levelChipText = $text |
Progress (progress-bar.tsx:112) | BS on BT track | BS only for the ELO goal inside the hero block; elsewhere status hue (capacity / recruitment tone via fillColor) on $borderSubtle track | existing |
| Chart series | none | five-hue categorical set built from existing hues | new $series1…5 → primary, courtHard, courtClay, accentWarm, accentCool |
| Success vs brand | ΔE76 7.5 | with brand off controls, green on a chip means "paid / confirmed / win" and on a button means "do this"; re-measure ΔE after the move, retire primarySubtle badges | — |
Scope of the edit: nine primitive files carry the call-site multipliers of §2.2 (pill-nav.tsx, selection-chip.tsx, chip-label-visual.ts, avatar-bubble.tsx, empty-state.tsx, section-header.tsx, (tabs)/_layout.tsx, tier-badge-utils.tsx, progress-bar.tsx) plus tone defaults in stat-grid.tsx / tone-colors.ts. Roughly 150–210 of the 307 brand sites retarget; I have not built or rendered any of this, so the number is an estimate from the §2.1 classification.
5.2 Targets a re-measure should hit (same script, same frames)
- Brand hue share of saturated pixels on hub frames ≤ 70% (peers' brand-hue median 50% raw / 61% solid; 테니스프렌즈 / Wise / Cash App at 88–98% are the upper bound).
- ≥ 2 hue families ≥ 10% on 기록, 프로필 and the club hub (peer median 2).
- Hub saturated area 3–8% (peer median 5.1%; the one block carries it), list frames ≤ 3%.
- Pale green-tint fills ≤ 1% on lists (peer median 0.9%), neutral-grey fills 3–6% (peer median 5.2%).
- Largest solid saturated object on a detail page is the CTA, ≤ 2.5% of the frame (peers' whole solid budget median 1.7%).
6. Caveats and side findings
- Frames are vendor-curated store art and our web rung (headless Chromium). Hue values carry ±2–3 levels of JPEG noise; family splits near a boundary (Wise
#9FE870at 130°, Duolingo green at 130°) can flip. Raw peer numbers include photos and 3D pictograms, which is why the solid view is shown beside it. - The brand-hue test uses 11 peers whose brand hue I could name and see; the 17-app ink / neutral tally is a visual read of the frames on disk, not an automated measure, and "–" cells mean "not visible", not "absent".
- Possible bug, not touched:
packages/features/clubs/src/dues/dues-attest-modal.tsx:162-169renders$primaryText(white in light,#0D100Ein dark) on a$primarySubtlebackground (#E5FAEAlight,#0A1F14dark): white on mint in light mode, about 1.1:1. - Mislabelled
--huesfamilies in the first version ofsample.py: before the recalibration it put Toss blue under "purple", Toss red under "orange" and optic lime under "green". Any lane that ran--huesbefore the recalibration message needs a re-run. ours-dark/*was measured but not used;ours-1440/matches.error.txtsits next to the matches frame (not investigated; the frame was measured as delivered).
7. Files
- This report:
bench/lane-d-colour-usage.md - Scripts:
bench/lane_d_hues.py(families, raw + solid),bench/lane_d_glob_m.py,bench/lane_d_blobs.py,bench/lane_d_tint.py,bench/lane_d/census_classify.py,bench/rerun_all.sh - Data:
bench/lane_d/{peers_app_summary,glob_m_app_summary,ours_frames,blobs,tint,census_classified}.json,peers_per_frame.tsv,glob_m_per_frame.tsv,census_primary_lines.txt,census_semantic_lines.txt,peer_table.txt,peer_stats.txt,brand_share.txt,blob_stats.txt,tint_stats.txt,ours_table.txt - Sheets I read:
bench/lane_d/sheets/*.png