← Cockpit Wireframes Pages by level Navigation map
Library — additions from the pages▾
Shipped app (old grammar)Components · 공유 키트상세 페이지 — 플랫 언어Session Card · 밴드 없는 카드
홈 · Home
Home · 홈
시작 · Entry
Onboarding · 시작
ArchiveStrip Map · 세션 스트립 전체 시나리오Headers · 헤더 아이콘Card Continuity · 카드 = 상세의 예고편Session Hero · 더 조용한 공유 카드 (제안)Session Share Card · 히어로 = 공유 카드Session List Frame · 날짜 섹션 · 패널 필터 · 2+2 카드Compaction — 일정 · 코트 · 스코어카드Compaction — 클럽 상세 · 홈Compaction — 기록 · 프로필Live anchor — 진행 중 밴드Density · 카드 밀도토스트 — 리디자인 제안Club List · 클럽Clubs · 클럽클럽 상세 — 여섯 탭 플랫운영 섹션 — 플랫 목표위저드 내비게이션 — 결정 보드드래프트 복원 — 무음 복원클럽 조 편성 — 모델 결정 보드게스트 → 회원 — 심사 흐름Page polish — titled postsWeb app — desktop layoutProduct questions — openWeb app — desktop adaptation, round 2Visual tone — pure white, flat rows, one Tag, a colour budgetLibrary — chrome, surfaces, rows, labelsLibrary — controls, overlays, states, formsLibrary — page patterns, scaling, motionLibrary — additions from the pagesEmpty-once — 프로필 빈 섹션Notification groups — 할 일 밴드 그룹화Profile Cockpit · 프로필 압축Session Detail — Redesign · 일정Venue Detail — Redesign · 테니스장Profile Detail — Redesign · 프로필Tournament Detail — Redesign · 대항전Applicant Detail — Redesign · 가입 심사Canvas Migration — 회비 · 멤버 · 규칙Record Detail — Redesign · 기록Venue Finder · 코트 찾기Venue Filters · 코트 필터 재구성Notifications · 알림Panel Placement · 하단 고정
Archived — superseded by Library — surfaces and content
Interactive kit · grounded in code

Library — additions from the pages

Flat v2 · Phase B · proposal

Component library additions from the page boards

The page boards drew every page only from the library and listed what it lacked. The library was extended first (sections 18.2, 19.3, 19.5, 20.2, 20.3, 21.2 and 22.2); this board draws those additions the way /wireframes/flat-v2-patterns drew section 14: one specimen block per component at 1:1 with its rulers, every applicable state, the content rules, the spoken label (B8), the motion with its reduced-motion variant (B7), the dark values and the 130% and 200% frames (B6). Scheme S2 Ball & Ink. The sibling boards are flat-v2-components (chrome, surfaces, rows), flat-v2-controls (controls, overlays, forms) and flat-v2-patterns (page components, scaling, motion, accessibility, platform).

How to read a specimen
  • Library the section text it draws, quoted; Anatomy the geometry at 1:1 with pt rulers (magenta lines and dashed boxes are board chrome, never part of a component) beside the behaviour table (press, hover, focus, disabled, loading, spoken label, motion, reduced motion, text scale, used by, replaces).
  • States every applicable state, and under it “Not drawn:” with the reason for each state that does not apply; Variants sizes and content rules (long names wrap and are never cut, 99+); Text size 130% and 200% where B6 changes the layout.
  • The closing panels: the DOM verification (grid, type scale, minHeights, 44 targets, hairlines and seams, contract section 5), the spec conflicts with a proposed fix each, and the coverage map from every library row to its specimen.
Rulings carried in
  • §15 every fixed height that holds text is a floor (heights here are minHeight), motion tokens and toast timings; §16 ActionSheet cap 88%, one HeaderTextAction per side;
  • §17 H1 a text action sits left of any icon, the overflow ⋯ is rightmost; §19.2 the Two-line Cell is min 68, Reactions replace the Reaction picker, the map band has the photo geometry;
  • the owner’s wizard-navigation LAW and required-marker LAW (2026-08-15); the Offline strip is 350 × 48 everywhere; heights that come from text lines (n × 22) are a listed exemption, spacing values are never exempt.
1Rows and cellstext rows, trailing controls, the figure column, the Cell variants, disclosure and answers
Text rowAn object in a list that has no picture: a document, a receipt, a history entry.
the PersonRow text block without leading media · L1 15/22·600 ink · L2 13/18·500 secondary · min 68 = 12 + 22 + 4 + 18 + 12 · one trailing element (Tag, figure or chevron), centred on line 1 · full-bleed, content on the rail · no divider
Library §20.2 “the PersonRow text block without leading media … Used for lists of documents, receipts and history entries (동의 이력, 제안 내역). A Cell is a label/value pair inside a settings group; a Text row is an object in a list.”
Anatomy
20370
마케팅 정보 수신 동의
동의함
2026. 9. 1. 동의
12trailing
12 pad
22 L1 · 15/22·600
18 L2 · 13/18·500
12 pad
Press
scale 0.98 · opacity 0.80 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Content
L1 and L2 wrap (a long document title takes two lines; it is never cut); the trailing element never shrinks; at most one trailing element
Selected / error / empty
none on the row: a Text row is a plain object; an empty list is the screen’s EmptyState, an error is the list’s Notice
Spoken
one stop: "마케팅 정보 수신 동의, 2026년 9월 1일 동의, 동의함": L1, L2, then the trailing text; the date is spoken in full
Motion
press: spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in / 150 ms out; fade 150 ms standard (0.2, 0, 0, 1) for skeleton → content
Reduced motion
the settle is dropped, the dim stays
Text scale
reading class, cap 2.0: both lines wrap; from 150% the trailing element moves under line 2 (left-aligned); the row grows, never clips; the chevron keeps its place at the end
Used by
동의 이력, 제안 내역, 회비 영수증 and history lists (list-b board)
Replaces
ListRow (packages/ui/src/list-row.tsx) in its avatar-less use, and the per-screen hand-built rows of the consent history and contribution lists
States
마케팅 정보 수신 동의
동의함
2026. 9. 1. 동의
rest Tag 동의함
마케팅 정보 수신 동의
동의함
2026. 9. 1. 동의
pressed Tag 동의함 · scale 0.98 · opacity 0.80
마케팅 정보 수신 동의
동의함
2026. 9. 1. 동의
hover Tag 동의함 · web: opacity .85 + pointer
마케팅 정보 수신 동의
동의함
2026. 9. 1. 동의
focus Tag 동의함 · web: 2px ink ring, offset 2
마케팅 정보 수신 동의
동의함
2026. 9. 1. 동의
disabled Tag 동의함 · opacity .5 · inert · aria-disabled
loading skeleton: two lines at 50% / 30%, pulse .6 ↔ .9
Not drawn: selected (a Text row is never a selectable choice), error (the list owns the error) and empty (the list owns the empty state).
Variants
9월 회비
30,000원
9월 17일 입금
figure a figure 15/22·700 tabular
개인정보 처리방침
시행일 2026. 9. 1.
chevron it opens the document
장소 정보 제안 내역
올림픽공원 테니스장
코트 수 수정
no trailing L1 and L2 only
마케팅 및 광고성 정보 수신에 대한 동의 및 철회 이력
동의함
2026. 9. 1. 동의
a long title wraps L1 on two lines: 12 + 44 + 4 + 18 + 12 = 90, the trailing stays on line 1
마케팅 정보 수신 동의
동의함
2026. 9. 1. 동의
dark the same markup, the dark values
Text size
마케팅 정보 수신 동의
동의함
2026. 9. 1. 동의
130% text trailing still on line 1
마케팅 정보 수신 동의
2026. 9. 1. 동의
동의함
200% text trailing under line 2 (from 150%)
Row trailing controlA row’s one trailing element may be a control: an IconButton or a small tonal Button, with its own focus stop.
IconButton: 20 glyph in a 44 target · sm tonal Button: 36 tall, hit slop 4 · the control centres on the row (text, figures and Tags ≤ 24 centre on line 1, §19.3) · the row’s press area excludes the control’s target and pressing the control never presses the row · two focus stops
Library §20.2 “a row’s one trailing element may be a control … It centres on the row (§19.3) and is its own focus stop … Spoken as two stops: the row’s composed label, then the control (‘정우진, 친구 추가’).” §19.3 PersonRow trailing alignment · §18.2 PersonRow trailing meta.
Anatomy
20370
정우진
NTRP 3.5
강남 라켓클럽
친구 추가
row press area
4012sm 36 · 16 / 16
12 pad
44 text block
L1 22 + 4 + L2 18 · avatar 40 centred
12 pad
Press
the control (row: .98 / .80 on the rest of the row): scale 0.97 · opacity 0.92 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Hit areas
the control’s 44 target is its own; the row’s press area is the row minus that target (pressing the control never presses the row, pressing the row never fires the control)
Alignment
controls taller than 24 centre on the row; text, figures and Tags (≤ 24) centre on line 1 (§19.3); one trailing element still
Control loading
the Button shows its spinner at full opacity, width held; the row stays pressable
After the action
친구 추가 → a disabled tonal "요청됨" (the answer is in the row, no toast needed)
Spoken
two stops: the row’s composed label "정우진, NTRP 3.5, 강남 라켓클럽", then the control "친구 추가" (name = the visible text; for an IconButton the aria-label)
Motion
press: spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in / 150 ms out; fade 150 ms standard (0.2, 0, 0, 1) for skeleton → content
Reduced motion
the settle is dropped, the dim stays
Text scale
the row text wraps; from 150% the control moves under line 2, left-aligned with the text; the Button is a label class (cap 1.5) and grows with its text, an IconButton stays 44
Used by
친구 찾기 results (L1: the row opens 공개 프로필, 친구 추가 keeps its own focus stop), 메시지 요청, member rows with a menu
Replaces
the static PersonRow trailing (a Tag only) and the per-screen rows that wrapped a Button in a Pressable row (nested pressables)
States
정우진
NTRP 3.5
강남 라켓클럽
친구 추가
row · rest
정우진
NTRP 3.5
강남 라켓클럽
친구 추가
row · pressed row .98 / .80, the control untouched
정우진
NTRP 3.5
강남 라켓클럽
친구 추가
control · pressed button .97 / .92; the row stays at rest
정우진
NTRP 3.5
강남 라켓클럽
친구 추가
control · hover web: opacity .85 + pointer
정우진
NTRP 3.5
강남 라켓클럽
친구 추가
row · focus stop 1: the ring around the row
정우진
NTRP 3.5
강남 라켓클럽
친구 추가
control · focus stop 2: the ring around the Button, offset 2
정우진
NTRP 3.5
강남 라켓클럽
친구 추가
control · loading spinner, width held
정우진
NTRP 3.5
강남 라켓클럽
요청됨
control · disabled after the request: 요청됨, .5
row · loading skeleton
Not drawn: selected (a row with a control is not a choice), error (a failed request is the Toast "요청하지 못했어요" and the Button returns to rest), empty (the list’s EmptyState).
Variants
정우진
NTRP 3.5
강남 라켓클럽
IconButton ⋯ 20 in a 44 target, its glyph on the rail
정우진
오후 2:14
복식
강남 라켓클럽
trailing meta 13/18·500 secondary text (a time, a rate) in place of a figure or Tag; centred on line 1 (§18.2)
정우진
참석
NTRP 3.5
강남 라켓클럽
Tag (≤ 24) centres on line 1, not on the row (§19.3)
남궁민수 (강남 라켓클럽 총무, 복식 선수)
NTRP 3.5
강남 라켓클럽
친구 추가
a long name wraps the control stays centred on the row
정우진
NTRP 3.5
강남 라켓클럽
친구 추가
dark
Text size
정우진
NTRP 3.5
강남 라켓클럽
친구 추가
130% text the Button grows with its label
정우진
NTRP 3.5
강남 라켓클럽
친구 추가
200% text under line 2, left-aligned
Figure columnRanked and scored lists right-align their figure in one column, so the digits line up.
figure 15/22·700 tabular, right-aligned, in a column as wide as the widest figure on the page · the level Tag (a run start only, BADGE-EXCEPTION) in a reserved 24 slot, 8 before the column · rank column 24 (1–99) / 32 (100–999) / 40 (1,000+), 13/18·600 tabular, right-aligned, 12 before the avatar
Library §20.2 Figure column · §20.1 L4 “the level Tag shows only where a level run starts, and the Podium always shows it. The Tag sits in a reserved slot so the figures align.”
Anatomy
20370
1
김민준
강남 라켓클럽
52경기
3.5
1,482
2
이서연
반포 테니스 모임
48경기
1,450
3
박지훈
강남 라켓클럽
61경기
998
24124012slot 248widest figure
68 row
12 + 44 + 12; avatar 40 centred
68
no Tag: the figure keeps its place
68
the Tag slot stays reserved
Press
scale 0.98 · opacity 0.80 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Column width
the grid is one subgrid of the list: the figure column is as wide as the widest figure of the page (auto), so a longer figure never pushes one row only
Rank width
24 while every rank is 1–99, 32 for 100–999, 40 from 1,000: the column fits the widest rank of the page and every row shares it
Level Tag
drawn only where a level run starts (the first row of 3.5, then 3.0 …); the slot is always reserved
Spoken
one stop per row: "1위, 김민준, ELO 1,482, NTRP 3.5, 강남 라켓클럽, 52경기"; the rank leads, the level Tag is read only when drawn
Motion
press: spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in / 150 ms out; fade 150 ms standard (0.2, 0, 0, 1) for skeleton → content
Reduced motion
the settle is dropped, the dim stays
Text scale
numeral class (figure, cap 1.5) and label class (rank, cap 1.5): up to 130% the grid holds; from 150% the figure and the Tag go under line 2 and the rank column stays; the rank column grows with its digits
Used by
순위표, 순위 pane, 멤버 순위, head-to-head tables
Replaces
LeaderboardRow (packages/app/src/presentation/components/leaderboard-row.tsx): the rank column was fixed at 32 and the Tag sat in the cluster, so figures drifted
Rank width
1
김민준
강남 라켓클럽
52경기
3.5
1,482
2
이서연
반포 테니스 모임
48경기
1,450
3
박지훈
강남 라켓클럽
61경기
998
1–99 24
118
김민준
강남 라켓클럽
52경기
3.5
1,482
119
이서연
반포 테니스 모임
48경기
1,450
120
박지훈
강남 라켓클럽
61경기
998
100–999 32
1,024
김민준
강남 라켓클럽
52경기
3.5
1,482
1,025
이서연
반포 테니스 모임
48경기
1,450
1,026
박지훈
강남 라켓클럽
61경기
998
1,000+ 40
States
1
김민준
강남 라켓클럽
52경기
3.5
1,482
rest the row opens the profile
1
김민준
강남 라켓클럽
52경기
3.5
1,482
pressed scale 0.98 · opacity 0.80
1
김민준
강남 라켓클럽
52경기
3.5
1,482
hover web: opacity .85 + pointer
1
김민준
강남 라켓클럽
52경기
3.5
1,482
focus web: 2px ink ring, offset 2
1
김민준
강남 라켓클럽
52경기
3.5
1,482
disabled opacity .5 · inert · aria-disabled
loading skeleton at the same columns
Not drawn: selected, error and empty belong to the list (its own EmptyState).
Variants
4
남궁민수 황보서연 (강남 라켓클럽 총무)
강남 라켓클럽
4.0
1,309
a long name wraps the figure stays centred on the row
1
김민준
강남 라켓클럽
52경기
3.5
1,482
2
이서연
반포 테니스 모임
48경기
1,450
dark
Text size
1
김민준
강남 라켓클럽
52경기
3.5
1,482
2
이서연
반포 테니스 모임
48경기
1,450
130% text the grid still holds
1
김민준
강남 라켓클럽
52경기
1,4823.5
2
이서연
반포 테니스 모임
48경기
1,450
200% text figure and Tag under line 2
Cell · two-line, reason line, lockedA Cell with a second line: a caption under its label, or the reason a field cannot change.
min 68 = 12 + 22 + 4 + 18 + 12 (§19.2: was 64) · label 15/22·400 ink · second line 13/18·500 secondary · value 15/22·500 at the right · locked: the reason line plus a 16 lock in tertiary in the chevron’s slot, not pressable, no pencil, no chevron
Library §18.2 Two-line Cell (corrected §19.2: min 68) · §19.3 Cell, reason line · §21.2 Cell, locked “the §19.3 reason line plus a trailing 16 lock glyph in tertiary, in the chevron’s slot. Not pressable.”
Anatomy
20370
경기 방식
시작 후에는 바꿀 수 없어요
1216
12 pad
22 label · 15/22·400
18 line 2 · 13/18·500
12 pad
Press
scale 0.98 · opacity 0.80 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Locked
not pressable: no dim, no settle, no hover, no focus stop; the label stays ink and readable (never .5, which would hide the reason); there is no pencil and no chevron
Reason
one line; secondary 13/18·500 (danger only when the field is blocked by an error); past two lines the copy is rewritten
Spoken
two-line / navigating: "정기 일정, 매주 토요일 오전 9시" (a button); locked: "경기 방식, 복식, 시작 후에는 바꿀 수 없어요" (text, not a control; the lock glyph is hidden); state stays out of the name
Motion
press: spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in / 150 ms out; fade 150 ms standard (0.2, 0, 0, 1) for skeleton → content
Reduced motion
the settle is dropped, the dim stays
Text scale
reading class: both lines wrap; from 150% the value goes under the label block; the cell grows (a floor), never clips; the 16 end glyph stays
Used by
the 일정 review (locked after start), 정기 일정 captions, 프라이버시 rows (the level’s meaning is the second line, D1), dates and captions
Replaces
CellRow / FactRow / ListRow (packages/ui/src/cell-row.tsx, fact-row.tsx) with a hand-built two-line layout; the disabled-opacity locked field
States
정기 일정
매주 토요일 오전 9시
rest two-line, navigates
정기 일정
매주 토요일 오전 9시
pressed two-line, navigates · scale 0.98 · opacity 0.80
정기 일정
매주 토요일 오전 9시
hover two-line, navigates · web: opacity .85 + pointer
정기 일정
매주 토요일 오전 9시
focus two-line, navigates · web: 2px ink ring, offset 2
정기 일정
매주 토요일 오전 9시
disabled two-line, navigates · opacity .5 · inert · aria-disabled
loading label 40%, second line 30%, value 20%
경기 방식
시작 후에는 바꿀 수 없어요
복식
locked · rest the reason line + lock
경기 방식
시작 후에는 바꿀 수 없어요
복식
locked · pressed nothing happens: inert, no dim, no settle
Not drawn: selected and error: a Cell shows its value, the form owns validation; empty: a Cell with no value is an unset field (the required marker, family forms).
Variants
정기 일정
매주 토요일 오전 9시
3개월
with a value value at the right, second line under the label
프로필 공개
클럽 멤버에게만 이름이 보여요
멤버만
Choice ▾ slot 프라이버시 row: the Choice opens a Sheet; the cell is not pressable (D1)
정기 일정 (매주 토요일 오전 9시, 올림픽공원 1번 코트)
다음 일정은 9월 19일이에요. 날짜가 바뀌면 멤버에게 알림이 가요.
long text wraps both lines wrap: 12 + 44 + 22 + 12 = 90
경기 방식
시작 후에는 바꿀 수 없어요
복식
locked · dark
정기 일정
매주 토요일 오전 9시
two-line · dark
Text size
정기 일정
매주 토요일 오전 9시
3개월
130% text value still at the right
정기 일정
매주 토요일 오전 9시
3개월
200% text the value under the label block
경기 방식
시작 후에는 바꿀 수 없어요
복식
200% text locked: the reason wraps, the lock stays 16
Cell · edit, trailing slot, label Tag, Task CellOne Cell grammar for every settings and review row: what sits at its end tells the user what a tap does.
min 48 · label 15/22·400 secondary (ink 500 for a Task Cell) · exactly one trailing slot: value text · Tag sm (the Cell stays 48) · Choice ▾ sm 36 · sm tonal Button or IconButton · Switch · a count Tag before the chevron · the end glyph is 16 tertiary: chevron (navigates), pencil (edits in a Sheet), never both · a label Tag sm sits 8 after the label
Library §21.2 Cell, edit (EDIT-PENCIL: the shared EditCue, 16 tertiary, “never beside a chevron, spoken ‘장소, 수정’; opens the field’s bottom edit Sheet”) · Cell trailing slot (“Only one per Cell”) · Cell, label Tag · §19.3 Cell, Tag value · §18.2 Task Cell (“a count Tag before the chevron”).
Anatomy
20370
테니스장
올림픽공원
labelslot (one)816
48 min
22 line centred · pad 12
Press
a navigating or editing Cell presses as a row; a Cell whose slot is a control presses nothing but the control: scale 0.98 · opacity 0.80 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Slot rule
only one trailing slot per Cell; a Tag and a Choice never share a Cell; the slot controls (Choice, Button, IconButton) are their own focus stops and the Cell around them is not pressable (as a row trailing control, §20.2); the Switch Cell toggles from the whole row
Pencil vs chevron
a pencil edits in place (opens the field’s bottom edit Sheet), a chevron navigates to another screen; a Cell never draws both (the pencil takes the chevron’s slot)
Required (LAW)
an unset required Cell carries the one $error asterisk after its label and shows its placeholder in secondary ink; it starts unset
Spoken
navigating / editing: "장소, 올림픽공원 테니스장, 수정" (a button: the pencil is spoken as 수정); Tag value: "참가비, 입금 필요"; Task Cell: "가입 신청, 3개" ; Switch: "경기 알림" with aria-checked; slot controls are their own stops
Motion
press: spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in / 150 ms out; fade 150 ms standard (0.2, 0, 0, 1) for skeleton → content
Reduced motion
the settle is dropped, the dim stays
Text scale
reading class: from 150% the label goes over the value (the slot under the label block) and the end glyph stays; a Choice or Button slot is a label class (cap 1.5) and moves under the label; every height is a floor
Used by
the 일정 / 클럽 review pages (every editable field has its Cell, E1), 프라이버시 and 설정 rows, the officials’ 할 일 section (Task Cell)
Replaces
CellRow / FactRow / EditFieldRow (packages/ui/src/cell-row.tsx, edit-field-row.tsx): hand-placed pencils and trailing widgets per screen
Cell, edit
테니스장
올림픽공원 테니스장
rest pencil 16 tertiary
테니스장
올림픽공원 테니스장
pressed pencil 16 tertiary · scale 0.98 · opacity 0.80
테니스장
올림픽공원 테니스장
hover pencil 16 tertiary · web: opacity .85 + pointer
테니스장
올림픽공원 테니스장
focus pencil 16 tertiary · web: 2px ink ring, offset 2
테니스장
올림픽공원 테니스장
disabled pencil 16 tertiary · opacity .5 · inert · aria-disabled
장소
선택해주세요
unset · required asterisk after the label, placeholder, starts unset
loading skeleton label 40% / value 20%
테니스장
올림픽공원 테니스장
dark
Not drawn: selected and error (the field’s own Sheet validates; the review page shows the Cell’s danger sub line "필수 항목이에요" only after a failed submit, drawn on the forms board).
Trailing slot
종목
복식
참가비
입금 필요
정렬
최신순
카카오 계정
연결
초대 링크
경기 알림
가입 신청
3
the seven slots value · Tag · Choice ▾ · sm Button · IconButton · Switch · count Tag + chevron (Task Cell); hairlines between, none after the last
종목
복식
참가비
입금 필요
정렬
최신순
카카오 계정
연결
초대 링크
경기 알림
가입 신청
3
dark the same group
프로필 사진신규
없음
label Tag one Tag sm 8 after the label (신규); the value is text only; counts toward the object’s two Tags
가입 신청
Task Cell · nothing pending no count Tag when the count is 0
신고 관리
99+
Task Cell · 99+ the count Tag caps at 99+; neutral when nothing needs action
참가비
입금 필요
pressed · Tag value row .98 / .80
Not drawn: a slot never has a pressed state of its own beyond its control’s (Choice .96 / .85, Button .97 / .92, IconButton .92 / .78: the controls board); disabled is .5 on the whole Cell.
Text size
테니스장
올림픽공원 테니스장
참가비
입금 필요
정렬
최신순
130% text value still at the right
테니스장
올림픽공원 테니스장
참가비
입금 필요
정렬
최신순
200% text label over the slot; the glyph stays
ExpandToggle · lone row and disclosure CellThe one collapse pattern: a lone row that reveals more of a list, and a Cell that reveals its own text.
lone row: 48 full width, label centred 15/22·600 secondary that states its count (“4개 더 보기” / “접기”), a 16 chevron 8 after it rotating 180° in 200 ms · disclosure Cell: a Cell whose trailing chevron points down (closed) or up (open); an optional value before it; body 15/22·400 ink, 12 below the label, 16 at the bottom; cells open independently
Library §19.3 ExpandToggle, lone row · ExpandToggle, disclosure Cell · §19.5 “the bottom pad stays 16 … a box whose height comes from its text lines (n × 22) is a listed exemption from the 4pt height check, never a reason to bend a pad to 17.” A right chevron always means navigation.
Anatomy
20370
참가비는 언제 내나요?
9월 1일
일정에 참석을 신청하면 입금 안내가 와요. 시작 2시간 전까지 입금하면 돼요. 늦으면 예비로 바뀔 수 있어요.
48 lone row
label centred, chevron 16 after it (8)
disclosure, open
12 + 22 + 12 + n × 22 + 16
Press
scale 0.98 · opacity 0.80 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Independent
opening one disclosure never collapses another (auto-collapse makes the content jump under the finger, NN/g)
Label top
a disclosure Cell sets its label 12 from the top in both states, so the label does not move when it opens
Spoken
lone row: "4개 더 보기" with aria-expanded=false (open: "접기", expanded=true; the label states its count, never a bare chevron); disclosure: "참가비는 언제 내나요?" with aria-expanded; the body is read as the next text
Motion
chevron: toggle 200 ms standard, rotation 0° → 180° (158° at 100 ms); the body: reveal 250 ms enter (0, 0, 0, 1)
Reduced motion
the chevron and the body appear in their end state at once (no rotation, no slide)
Text scale
reading class: the label wraps (never clipped); the lone row stays centred; a disclosure value goes under the label from 150%; the body wraps and the cell grows
Used by
FAQ and 공지사항 (disclosure Cell), 멤버 / 사진 / 댓글 caps (lone row)
Replaces
ExpandToggle (packages/ui/src/expand-toggle.tsx), which draws an icon-only chevron collapse (a bare chevron hides what it does)
Lone row
closed states its count
접기
open 접기, chevron up
pressed row .98 / .80
hover web: opacity .85 + pointer
focus web: 2px ink ring, offset 2
disabled opacity .5, inert
loading the chevron becomes a spinner, aria-busy
dark
Not drawn: selected, error and empty: a toggle has no selection; a list with nothing more draws no toggle.
Chevron rotation
0 ms closed
100 ms 88% of the way (0.2, 0, 0, 1)
200 ms open
Disclosure Cell
closed a Cell, 48, a down chevron
참가비는 언제 내나요?
일정에 참석을 신청하면 입금 안내가 와요. 시작 2시간 전까지 입금하면 돼요.
open body 12 below the label, 16 at the bottom
9월 코트 이용 안내
9월 1일
9월부터 올림픽공원 3번 코트가 보수 공사로 닫아요.
with a value 공지사항: the date before the chevron
참가비는 언제 내나요?
일정에 참석을 신청하면 입금 안내가 와요. 시작 2시간 전까지 입금하면 돼요.
불참하면 환불되나요?
시작 24시간 전까지는 전액 환불돼요.
two cells, independent the first stays open when the second opens
pressed row .98 / .80
focus web ring
참가비는 언제 내나요?
일정에 참석을 신청하면 입금 안내가 와요. 시작 2시간 전까지 입금하면 돼요.
dark · open
Not drawn: disabled and loading: a disclosure is static text (nothing to disable, nothing to fetch); selected, error and empty do not apply.
Text size
참가비는 언제 내나요?
9월 1일
일정에 참석을 신청하면 입금 안내가 와요. 시작 2시간 전까지 입금하면 돼요.
130% text the label wraps; the value stays
참가비는 언제 내나요?9월 1일
일정에 참석을 신청하면 입금 안내가 와요. 시작 2시간 전까지 입금하면 돼요.
200% text value under the label
AnswerPairTwo tonal buttons that answer a request right inside its row.
two sm tonal Buttons (36, gap 8) under line 2 of a request row, left-aligned with the text · the row grows by 44 (8 + 36) · each Button has a 44 target · neutral tonal on both: the screen’s one coloured action is unaffected
Library §18.2 AnswerPair · §18.1 D2 (수락 · 거절 on 메시지 요청 and applicants, “request lists only”) and D8 (참석 · 불참 on unresolved 출석 확인 rows: “an explicit choice; a bare row tap never records attendance”).
Anatomy
2037072
이서연
오후 2:14
NTRP 3.0
한 번 같이 치고 싶어요
수락
거절
수락8거절
12 pad
44 text block
L1 22 + 4 + L2 18
8
36 buttons
sm tonal, gap 8, 44 target each
12 pad
Press
each button (the row around keeps .98 / .80 and opens the profile): scale 0.97 · opacity 0.92 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Order
the affirmative answer first (수락, 참석), the negative second; both tonal, so neither competes with the screen’s coloured action
After a tap
the tapped Button shows its spinner (width held) and the other is disabled until the mutation settles; on success the row leaves the list; on failure both return and a danger helper line names it
Error
a 13/18 danger line under the pair ("수락하지 못했어요. 다시 시도해주세요"); never a toast alone
Spoken
the row’s label "이서연, NTRP 3.0, 한 번 같이 치고 싶어요", then two more stops "수락" and "거절"; after the answer the row is announced as removed ("이서연, 수락함")
Motion
press: spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in / 150 ms out; the answered row fades out fadeFast 100 ms standard, the next row snaps up (no layout animation in lists, §15)
Reduced motion
the row disappears at once
Text scale
label class (cap 1.5): the Buttons grow with their words; from 150% each Button goes full width on its own line (stacked, gap 8); the row grows, never clips
Used by
메시지 요청, applicants (가입 신청), 출석 확인 (참석 · 불참)
Replaces
the per-screen Accept / Reject pair of ActionButtons in request rows (packages/features/messaging request-gate-bar and the applicants list)
States
이서연
오후 2:14
NTRP 3.0
한 번 같이 치고 싶어요
수락
거절
rest
이서연
오후 2:14
NTRP 3.0
한 번 같이 치고 싶어요
수락
거절
수락 pressed button .97 / .92
이서연
오후 2:14
NTRP 3.0
한 번 같이 치고 싶어요
수락
거절
수락 hover web: opacity .85
이서연
오후 2:14
NTRP 3.0
한 번 같이 치고 싶어요
수락
거절
수락 focus stop 2: ring around the Button, offset 2
이서연
오후 2:14
NTRP 3.0
한 번 같이 치고 싶어요
수락
거절
saving spinner on 수락, 거절 disabled
이서연
오후 2:14
NTRP 3.0
한 번 같이 치고 싶어요
수락
거절
수락하지 못했어요. 다시 시도해주세요
error both return; a danger line under the pair
이서연
오후 2:14
NTRP 3.0
한 번 같이 치고 싶어요
수락
거절
row · pressed the row presses, the pair untouched
row · loading skeleton, no pair
Not drawn: selected (an answer is not a state of the row) and empty (the list's EmptyState when no requests remain).
Variants
이서연
9월 19일 (토)
복식
참석
불참
attendance 참석 · 불참 on an unresolved 출석 확인 row (D8)
남궁민수 황보서연 (강남 라켓클럽 총무)
오후 2:14
NTRP 3.0
한 번 같이 치고 싶어요
수락
거절
a long name wraps the pair stays under line 2
이서연
오후 2:14
NTRP 3.0
한 번 같이 치고 싶어요
수락
거절
dark
Text size
이서연
오후 2:14
NTRP 3.0
한 번 같이 치고 싶어요
수락
거절
130% text Buttons grow with their label
이서연
NTRP 3.0
한 번 같이 치고 싶어요
오후 2:14
수락
거절
200% text each Button full width, stacked
2Labels and markersthe unread dot, the header badge, counted tabs, section heads and captions
Unread markerThe one sign that a notification has not been read.
6 px danger dot hung in the gutter at x 8 to 14, centred on line 1 · the text stays on the rail · spoken prefix “읽지 않음” · read rows draw nothing (no empty slot)
Library §18.2 Unread marker “6px danger dot hung at x 8–14, centred on line 1; spoken prefix 읽지 않음” · §18.1 D1 “unread = a 6px danger dot hung in the gutter (x 8–14) + ‘읽지 않음’ prefixed to the spoken label”. A colour-only cue fails WCAG 1.4.1, so the prefix is the non-visual cue.
Anatomy
814370
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
6
12 pad
22 line 1 · dot centred
18 line 2
12 pad
Press
scale 0.98 · opacity 0.80 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Select mode
dismissing is Select mode (D1): the checkbox leads on the rail and the dot stays in the gutter; the swipe action is only an accelerator
Colour
danger #B8332E on white (4.99:1), #FA9B94 on the dark canvas; a graphical object, 3:1 needed
Spoken
unread: "읽지 않음, 참가비 입금 확인 요청, 이서연님이 9월 회비 입금을 알렸어요, 방금"; read: the same without the prefix; the dot is hidden from the tree (the prefix replaces it)
Motion
it leaves on read in 100 ms (fadeFast exit); it never pulses
Reduced motion
it disappears at once
Text scale
the dot stays 6 px and stays centred on line 1 as the line grows; it is not text, so it does not scale
Used by
알림 센터, 메시지 목록, 클럽 공지 rows
Replaces
the per-screen unread cues: bold text, a tinted row and a CornerCountBadge on rows (packages/ui/src/corner-count-badge.tsx)
States
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
unread
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
read no dot, no gap reserved
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
unread · pressed row .98 / .80, the dot dims with the row
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
unread · focus
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
unread · hover
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
unread · disabled .5 on the row
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
select mode checkbox on the rail, dot in the gutter
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
dark
Not drawn: loading (a skeleton row has no state yet), selected (Select mode shows the checkbox, drawn), error and empty (not row states).
Text size
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
130% text dot centred on the taller line 1
참가비 입금 확인 요청
이서연님이 9월 회비 입금을 알렸어요
방금
200% text trailing under line 2, dot unchanged
Header icon badgeA dot or a count on a header icon: something is waiting behind it.
the tab-bar badge on a header glyph: a 8 dot or a count Tag 16 tall (min 16, r full, 12/16·700, 99+ cap) at the top-right of the 24 glyph · ring 1.5 in the canvas colour · the target stays 44 · count hidden at 0
Library §18.2 Header icon badge “the tab-bar dot or count Tag on a header icon (top-right of the 24 glyph, ring 1.5 canvas)” · §3 Tab bar badge: “7px danger dot (ring 1.5 canvas) or count tag 16 tall” · §21.1 E1 “the header 관리 action carries a Header icon badge when anything is pending”.
Anatomy
370
홈
3
glyph 24
44 target
glyph 24 · badge 16 top-right
Press
the whole IconButton, badge included: scale 0.92 · opacity 0.78 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Dot vs count
a dot when something is pending and its number is not the point (관리); a count when the number is (알림); never both on one icon
Spoken
the icon’s label then the state: "알림, 읽지 않은 3개" (dot: "알림, 새 알림 있음"; 99+: "알림, 읽지 않은 99개 이상"); the badge itself is hidden from the tree
Motion
appears and leaves fade 150 ms standard (0.2, 0, 0, 1); a changing count is instant (counts never animate, B7.6)
Reduced motion
the cross-fade becomes instant
Text scale
the glyph and the 44 target do not scale (chrome class, cap 1.3 for any text): a count Tag grows only with its 11/14 label (chrome cap 1.3), the badge stays on the glyph corner
Used by
홈 알림 bell, 메시지 icon, 클럽 관리 (the official’s header action)
Replaces
CornerCountBadge (packages/ui/src/corner-count-badge.tsx) placed ad hoc on header icons
States
홈
none nothing waiting: the plain icon
홈
dot something is pending
홈
3
count 3 a number
홈
99+
count 99+ capped, the badge grows to the right
홈
3
pressed icon .92 / .78 on the whole button
홈
3
hover
홈
3
focus
홈
3
disabled .5 incl. the badge
홈
3
dark the ring is the dark canvas
Not drawn: loading (the badge appears with the data, never as a placeholder), selected, error and empty (a count of 0 draws no badge).
Tabs item with a countA scope tab that says how many items wait behind it.
the Tabs item (row 44, label 15/22, active 600 ink + 2 underline, inactive 500 secondary, gap 24 from the rail) with a count Tag (20 tall, neutral; danger when the count needs action) 4 after the label · the underline spans the whole item
Library §18.2 Tabs item with count “a count Tag (20, neutral, or danger when it needs action) 4 after the label” · §3 Tabs (scope): “labels 15/22, active 600 ink + 2pt underline the label width … horizontally scrollable past 4”.
Anatomy
20370
예정3
매치 찾기
테니스장12
count 20
42 + 2 underline = 44
label 22 centred in the item
Press
the item (dim .85, no settle): dim 0.85, no settle · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Tones
neutral = a plain count; danger = the count needs action (pending requests); 99+ cap; a zero count draws no Tag
Overflow
past four items the row scrolls horizontally; the active item scrolls into view
Spoken
"예정, 3, 탭, 3개 중 1번째" with aria-selected on the active tab; the count is read after the label, the danger tone is not read (the number carries it)
Motion
the underline slides on the navSlide spring {22, 280, .8} (311 ms); the count changes instantly
Reduced motion
the underline jumps to the new tab at once
Text scale
chrome class, cap 1.3: label and count grow to at most 130% and the row grows (a floor); it stays one scrollable row, never wraps; the count Tag is 20 at 100%
Used by
경기 (예정 · 매치 찾기 · 테니스장), 클럽 panes, 기록 tabs
Replaces
PillNav / SegmentedTabs with a count (packages/ui/src/pill-nav.tsx, segmented-tabs.tsx): the count sat in the label string
States
예정3
매치 찾기
테니스장12
rest first tab active
예정3
매치 찾기
테니스장12
second active underline under label + count
예정3
매치 찾기
테니스장12
pressed dim .85 on the inactive item
예정3
매치 찾기
테니스장12
hover web: opacity .85 + pointer
예정3
매치 찾기
테니스장12
focus web: 2px ink ring, offset 2
알림99+
메시지7
요청
count 99+
예정
매치 찾기
테니스장
loading counts are placeholders
예정3
매치 찾기
테니스장12
dark
Not drawn: disabled (a tab is never disabled: an empty scope shows its EmptyState), selected is the active item, error and empty belong to the pane under the tabs.
Text size
예정3
매치 찾기
테니스장12
130% text the cap: 130%
예정3
매치 찾기
테니스장12
200% text stays at the 130% cap (chrome class)
SectionHead variantsThe 24-tall head of a section, in the four shapes the pages needed.
24 tall (floor) · title 17/24·600 · 32 above, 8 below · count suffix: 15/22·500 secondary 4 after the title · command action: 13/18·600 ink, no chevron (모두 읽음) · nav action: 13/18·500 secondary + chevron 16 (전체 보기) · ExpandToggle slot: the one chevron collapse, a 44 target at the right
Library §18.2 SectionHead variants “count suffix (17/24·600 title + 15/22·500 secondary count, gap 4) · command action (13/18·600 ink, no chevron, e.g. 모두 읽음) · ExpandToggle slot (the one chevron collapse)” · §3 Section head “action target 44; pressed .85”.
Anatomy
20370
참석6
전체 보기
titlecountaction 13/18
32 above
the section seam, ink to ink
24 head · 17/24·600
8
Press
the action (dim .85, no settle); its target is 44: dim 0.85, no settle · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Which
count suffix says how many; a nav action goes to the full list (chevron, navigates); a command action does something now (no chevron); the toggle collapses the section; one action per head
Disabled / busy
a command with nothing to do is .5 (모두 읽음 with no unread); while it runs a 16 spinner leads it
Spoken
the title is a level-2 heading: "참석, 6, 제목 2"; the action is its own stop: "전체 보기" (link) or "모두 읽음" (button); the toggle: "접기" / "펼치기" with aria-expanded
Motion
the action dims press: spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in / 150 ms out; the toggle chevron toggle 200 ms standard
Reduced motion
the settle is dropped, the dim stays
Text scale
reading class for the title and count, label class for the action: from 150% the action moves under the title, left-aligned (proposed, B6.5 for a trailing element); the head grows (a floor)
Used by
참석 · 멤버 · 사진 sections, 알림 (모두 읽음), 클럽 sections, collapsible lists
Replaces
SectionHeader (packages/ui/src/section-header.tsx, navTitle 17/22·700 + divider) and its per-screen count and action variants
Variants
참석
title only
참석6
count suffix 참석 6
참석6
전체 보기
nav action 전체 보기 ›
이번 주
모두 읽음
command action 13/18·600 ink, no chevron
지난 일정12
ExpandToggle slot · closed
지난 일정12
ExpandToggle slot · open
States
참석
전체 보기
action · pressed dim .85
참석
전체 보기
action · hover
참석
전체 보기
action · focus
이번 주
모두 읽음
command · disabled .5: nothing unread
이번 주
모두 읽음
command · busy a 16 spinner leads the label
참석6
전체 보기
dark
Not drawn: loading (the head is static copy: the section’s rows skeleton), selected, error and empty do not apply.
Text size
참석6
전체 보기
130% text one row
참석6
전체 보기
200% text the action under the title
Caption rowA quiet line of context under a section head: a legend, a subtitle, the comparison a number is read against.
13/18·500 secondary at the rail · min 32 · no action · it may wrap to two lines (forms, §21.2): min 32, grows; past two lines the copy is rewritten, never clamped
Library §18.2 Caption row “13/18·500 secondary text at the rail, 32 tall, for a legend or a subtitle under a section head; no action” · §21.2 Caption row, two lines “min 32, grows; past two lines the copy is too long and is rewritten, never clamped”.
Anatomy
20370
이번 달 기록
지난달 대비
승률은 최근 20경기 기준이에요. 경기가 적으면 크게 달라질 수 있어요.
24 section head
32 caption
one line 18, centred
36 two lines
2 × 18 (a floor of 32)
Static
no press, hover, focus or disabled: a caption is text, not a control
Content
one sentence; a legend or the comparison a Strip delta reads against ("지난달 대비"); omitted when it has nothing to say (no empty caption)
Spoken
read as the next text after its section head, never as a stop of its own (plain text); a legend that names a colour is not allowed: the legend is a sentence
Motion
none: the change is instant
Reduced motion
unchanged
Text scale
reading class, cap 2.0: the line wraps and the row grows (a floor of 32); at 200% a two-line caption is four lines, which is the cue to shorten the copy
Used by
Strip with deltas (지난달 대비), 기록 sections, forms (two-line helper under a section head)
Replaces
ad hoc 13/18 secondary Text lines placed under SectionHeader on each screen
Variants
지난달 대비
one line
승률은 최근 20경기 기준이에요. 경기가 적으면 크게 달라질 수 있어요.
two lines grows to 36
복식
최근 6개월
with segments a legend of facts uses MetaSegments
지난달 대비
dark
Not drawn: press, hover, focus, disabled, loading, selected, error and empty: a caption is static text; with nothing to say the row is not drawn.
Text size
승률은 최근 20경기 기준이에요. 경기가 적으면 크게 달라질 수 있어요.
130% text two lines
승률은 최근 20경기 기준이에요. 경기가 적으면 크게 달라질 수 있어요.
200% text four lines: shorten the copy
3Surfaces and heroesthe club hero, the hero facts and the system post
Club hero · place variantThe identity of a club under its photo: a logo hung on the band, the name, where and how many.
logo 56 r12 overlapping the band’s bottom by 16 · name 24/32·700 starts 24 below the band beside the logo (x 88), wraps and is never cut · one MetaSegments line, region | members, 15/22 secondary · no glyph facts · 82 tall with a one-line name, 114 with two
Library §18.2 Club hero, place variant “the club hub’s identity under the MediaBand: logo 56 r12 overlapping the band bottom by 16, name 24/32·700, region and members as a MetaSegments line 15/22 secondary — no glyph facts”.
Anatomy
2088370
9:41
강
강남 라켓클럽
서초구
멤버 42명
logo 5612
240 MediaBand
status bar inside · scrim 100
16 logo overlap
24
32 name · 24/32·700
22 meta · 15/22
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Logo
the club logo, or its monogram tile when it has none; r12; it overlaps the band by 16 and is never cut by the band’s scrim
Without a band
when a Notice sits under the header instead of a band the logo cannot overlap: it sits inline at the hero’s own 24 top (§19.5), same name x
Spoken
the name is the one h1: "강남 라켓클럽, 제목 1"; then the meta line "서초구, 멤버 42명"; the logo is decorative (hidden): the name beside it says it
Motion
none of its own; with the page: the header turns white past 140 of scroll (150 ms) and the hero name fades into the header title
Reduced motion
the cross-fade becomes instant
Text scale
name hero class (cap 1.7) and meta reading class (cap 2.0): both wrap in the 282 column and the hero grows (82 → 114 → …); the logo stays 56 and keeps the band overlap
Used by
클럽 홈 (hub board), 클럽 미리보기
Replaces
the club photo seam and ClubHero / ClubAvatar hero in club-detail-screen (packages/features/clubs/src/club-detail/club-place-header.tsx)
States
9:41
강
강남 라켓클럽
서초구
멤버 42명
rest · one-line name 82
9:41
강
강남 올림픽공원 주말 복식 테니스 동호회
서초구
멤버 42명
two-line name a 20-character name wraps in the 282 column: 114
9:41
loading logo, name and meta skeleton
강
강남 라켓클럽
서초구
멤버 42명
no band the logo inline at the hero’s own 24 top (a Notice sits above)
9:41
강
강남 라켓클럽
서초구
멤버 42명
dark
Not drawn: pressed, hover, focus and disabled (the hero is text and a logo: the page’s actions are the header and the tabs below), selected, error and empty.
Text size
강
강남 라켓클럽
서초구
멤버 42명
130% text the name still one line
강
강남 라켓클럽
서초구
멤버 42명
200% text the name and the meta wrap; the logo stays 56
Hero facts · count and stateThe facts under a detail title: one to three of them, exactly as many as exist, and one Tag for what is true of you.
the facts block holds one to three facts, each 24 tall with 8 between · the hero is as tall as its facts (160 / 136 / 112, 56 with none) · no placeholder fact · with no facts the block and the 16 above it go · one viewer-state Tag (sm) may trail the fact it qualifies, 8 after it · at most one per hero, counted toward the object’s two Tags
Library §19.3 Hero facts: count and state “the facts block holds one to three facts … no placeholder fact is drawn; with no facts the block and the 16 above it go. One viewer-state Tag (sm) may trail the fact it qualifies, with 8 between (참가비 15,000원 → 입금 완료).” Words per the terminology guide: 입금 필요 · 입금 확인 중 · 입금 완료 (§18.1 D6).
Anatomy
20370
강남 라켓클럽
토요일 올림픽공원 복식
9월 19일 (토) 09:00–11:00
올림픽공원 테니스장
참가비 15,000원입금 완료
208
20 eyebrow · 13/18
32 title · 24/32·700
16
24 fact · 17/24·500
24 fact
24 fact + state Tag
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Count
draw exactly the facts the object has: a free session has no 참가비 fact (no "무료" placeholder); no facts at all removes the block and its 16
State Tag
viewer-state only (입금 필요 warning · 입금 확인 중 info · 입금 완료 success); one per hero; it trails the fact it qualifies; never in place of the fact
Link fact
a fact that links (the venue) dims .85 on press, no settle; its target is the text line, 44 by slop
Spoken
title is the h1; each fact is one text stop with its glyph hidden: "9월 19일 토요일 오전 9시부터 11시까지", "올림픽공원 테니스장", "참가비 15,000원, 입금 완료" (the Tag text joins its fact)
Motion
the Tag changes with the state: fade 150 ms standard (0.2, 0, 0, 1); the count of facts never animates
Reduced motion
the cross-fade becomes instant
Text scale
reading class: facts wrap; from 150% the Tag goes under its fact; the glyph stays 20 aligned to the first line; the hero grows (every height is the sum of its parts)
Used by
일정 상세, 클럽 미리보기, 경기 / 테니스장 detail heroes
Replaces
SessionFactsSection lead and TypeHero / VenueHero facts (packages/ui/src/type-hero.tsx, fact-row.tsx)
Counts and states
강남 라켓클럽
토요일 올림픽공원 복식
9월 19일 (토) 09:00–11:00
올림픽공원 테니스장
참가비 15,000원입금 완료
three facts + state 160
강남 라켓클럽
토요일 올림픽공원 복식
9월 19일 (토) 09:00–11:00
올림픽공원 테니스장
two facts 136: a free session has no fee fact
강남 라켓클럽
토요일 올림픽공원 복식
9월 19일 (토) 09:00–11:00
one fact 112
강남 라켓클럽
토요일 올림픽공원 복식
no facts 56: the block and its 16 are gone
강남 라켓클럽
토요일 올림픽공원 복식
9월 19일 (토) 09:00–11:00
올림픽공원 테니스장
참가비 15,000원입금 필요
입금 필요 warning
강남 라켓클럽
토요일 올림픽공원 복식
9월 19일 (토) 09:00–11:00
올림픽공원 테니스장
참가비 15,000원입금 확인 중
입금 확인 중 info
강남 라켓클럽
토요일 올림픽공원 복식
9월 19일 (토) 09:00–11:00
올림픽공원 테니스장
참가비 15,000원입금 완료
link fact · pressed the venue: dim .85
loading eyebrow, title and three fact lines
강남 라켓클럽
토요일 올림픽공원 복식
9월 19일 (토) 09:00–11:00
올림픽공원 테니스장
참가비 15,000원입금 완료
dark
Not drawn: focus and hover on a text fact (not a control); disabled, selected, error and empty (a missing fact is not drawn, the rule above).
Text size
강남 라켓클럽
토요일 올림픽공원 복식
9월 19일 (토) 09:00–11:00
올림픽공원 테니스장
참가비 15,000원입금 완료
130% text facts wrap, the Tag follows
강남 라켓클럽
토요일 올림픽공원 복식
9월 19일 (토) 09:00–11:00
올림픽공원 테니스장
참가비 15,000원입금 완료
200% text the Tag under its fact
PostRow · system variantThe post the club makes by itself: a result was recorded, photos were uploaded.
the PostRow with: the author line is the club (logo 40 r12) and a 13/18 secondary line naming the event instead of a time · the body is the result (ResultRow) or the PhotoGrid preview (three tiles, +N on the last) · no ⋯ · the action row 44 is unchanged (feed: the heart carries the total reaction count)
Library §18.2 PostRow system variant “for 경기 결과 and 사진 업로드: the author line is the club (logo 40) with a 13/18 secondary line naming the event; body is the result (ResultRow) or the PhotoGrid preview; no ⋯” · §19.5 “feed: the action row’s heart carries the total reaction count, with no chips”.
Anatomy
20370
강
강남 라켓클럽9월 19일 복식 경기 결과
vs 박지훈, 최유나
6–4 7–6승
9월 19일 (토)
12
3
44 author line
logo 40 · club over event
12
68 ResultRow
opponents · date · score + Tag
12
44 action row
heart + count · comment + count
Press
scale 0.98 · opacity 0.80 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Opens
the post body opens the post page (the result opens the match, the photos open the 앨범 viewer); the author line opens the club
Counts
feed: the heart carries the total reaction count and there are no chips; the post page shows the per-emoji chips under the body and the heart has no number (each count is shown once)
Spoken
one stop for the post: "강남 라켓클럽, 9월 19일 복식 경기 결과, 김민준 이서연 대 박지훈 최유나, 6대4 7대6 승, 좋아요 12개, 댓글 3개"; the actions are their own stops
Motion
press: spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in / 150 ms out; fade 150 ms standard (0.2, 0, 0, 1) for skeleton → content
Reduced motion
the settle is dropped, the dim stays
Text scale
reading class: club and event wrap; from 150% the ResultRow trailing goes under line 2; the action row grows with its counts; the PhotoGrid tiles are computed from the width, not scaled
Used by
클럽 피드 (경기 결과, 사진 업로드 posts), the 홈 club pane
Replaces
PostRow's user variant in post-row.tsx for system posts (packages/features/clubs/src/shared/post-row.tsx), which showed the poster as a member and carried a ⋯
States
강
강남 라켓클럽9월 19일 복식 경기 결과
vs 박지훈, 최유나
6–4 7–6승
9월 19일 (토)
12
3
result
강
강남 라켓클럽사진 12장을 올렸어요
+9
12
3
photos three tiles of the album, +N on the last
강
강남 라켓클럽9월 19일 복식 경기 결과
vs 박지훈, 최유나
6–4 7–6승
9월 19일 (토)
12
3
body · pressed row .98 / .80 on the body
강
강남 라켓클럽9월 19일 복식 경기 결과
vs 박지훈, 최유나
6–4 7–6승
9월 19일 (토)
12
3
body · focus
강
강남 라켓클럽9월 19일 복식 경기 결과
vs 박지훈, 최유나
6–4 7–6승
9월 19일 (토)
12
3
body · hover
loading one skeleton per post
강
강남 라켓클럽9월 19일 복식 경기 결과
vs 박지훈, 최유나
6–4 7–6승
9월 19일 (토)
12
3
dark
Not drawn: disabled (a post is never disabled), selected, error (a failed photo is its tile’s placeholder, see PhotoGrid), empty (the feed’s EmptyState).
Text size
강
강남 라켓클럽9월 19일 복식 경기 결과
vs 박지훈, 최유나
6–4 7–6승
9월 19일 (토)
12
3
130% text text wraps
강
강남 라켓클럽9월 19일 복식 경기 결과
vs 박지훈, 최유나
9월 19일 (토)
6–4 7–6승
12
3
200% text the score under line 2
4Avatarsgroup sizes per §19.5, the anonymous face and the roster door
Avatar · groupTwo members’ faces in one avatar box: a conversation or a pair, in the space of one person.
two member avatars at 0.6× inside the avatar box, offset diagonally: the back one top-left, the front one bottom-right in a 2px canvas ring · 20 in a 32 box, 24 in a 40 box (§19.5; 28 in 48 and 32 in 56 by the same rule, proposed) · photos when present, initials otherwise · never a generic glyph
Library §19.3 Avatar, group “two member avatars at 0.6× inside the avatar box, offset diagonally: the back one top-left, the front one bottom-right with a 2px canvas ring … Spoken as the member names. Identities stay photos, never a generic glyph.” §19.5 “the inner avatars are 0.6× rounded to the grid: 20 in a 32 box, 24 in a 40 box”.
Anatomy
24
16
40 box inner 24, offset 16
20
32 box inner 20, offset 12
48 box inner 28
56 box inner 32
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Rule
inner size = 0.6 × the box rounded to the 4pt grid: 20, 24, 28, 32; the front avatar’s ring is the canvas colour (the dark canvas in dark mode) so the notch reads on any theme
Photos
photos when present, the initial on gray100 otherwise (the same cascade as a single avatar); never a generic user glyph (that is the anonymous avatar, a privacy cue)
More than two
only two faces are drawn, the first two members; the count of the rest is in the row’s L2 ("김민준 외 4명")
Spoken
the member names, comma-joined: "김민준, 이서연" (the group row adds "외 3명" from its own model); a decorative image otherwise
Motion
none of its own; the photos fade in fade 150 ms standard (0.2, 0, 0, 1) (image load)
Reduced motion
the cross-fade becomes instant
Text scale
avatars do not scale (identity images, like every glyph): the box stays 32 / 40 / 48 / 56 and the row text wraps beside it
Used by
메시지 목록 (group chats), 그룹 thread header, pair rows (복식 짝)
Replaces
AvatarStack (packages/ui/src/avatar-stack.tsx, overlap −6) in rows; the stack’s overlap hid the second face
States
토요일 복식 모임
오후 2:14
김민준 외 4명
in a row 40 box lead of a PersonRow-grammar row
토요일 복식 모임
오후 2:14
김민준 외 4명
row · pressed row .98 / .80, the faces dim with it
토요일 복식 모임
오후 2:14
김민준 외 4명
row · focus
김
이
토요일 복식 모임
오후 2:14
김민준 외 4명
initials no photos
이
토요일 복식 모임
오후 2:14
김민준 외 4명
one photo, one initial
loading two gray100 circles, pulse .6 ↔ .9
토요일 복식 모임
오후 2:14
김민준 외 4명
dark the ring is the dark canvas
Not drawn: hover (follows the row), disabled (.5 follows the row), selected, error (a failed photo falls back to the initial) and empty (a group of none is not drawn).
Avatar · anonymousThe face of someone the viewer is not allowed to see by name: counts, never names.
a gray100 circle with a user glyph in secondary: 20 in a 40 box, 16 in a 32 box (24 in 48 and 28 in 56 by the same half-size rule, proposed) · spoken “참석자” · used where privacy hides names (club avatar-privacy canon)
Library §19.3 Avatar, anonymous “a gray100 circle with a user glyph (20 in 40, 16 in 32) in secondary, spoken ‘참석자’. It is used where privacy hides names: a non-member’s preview shows counts, never names (club avatar-privacy canon).”
Anatomy
32 box glyph 16
40 box glyph 20
48 box glyph 24
56 box glyph 28
When
the viewer is a non-member (or blocked) and the object’s names are private: the preview shows the count and anonymous faces; a member sees real avatars
Not a placeholder
it is never a loading or error state: those are the skeleton and the initial; it marks a privacy decision
Spoken
"참석자" for each (the same word for every anonymous face: the count line says how many: "참석자 8명"); never a name
Motion
none: the change is instant
Reduced motion
unchanged
Text scale
identity images do not scale; the count text beside them wraps
Used by
클럽 미리보기 (non-member 참석자 preview), 일정 상세 for non-members
Replaces
the generic person icon tiles and blurred avatars used on private previews (club avatar-privacy canon, packages/features/clubs)
States
+5참석자 8명
avatar row 3 anonymous + the +N circle, then the count
참석자
이름은 클럽 멤버에게만 보여요
in a row
참석자
이름은 클럽 멤버에게만 보여요
dark
Not drawn: pressed, hover, focus, disabled (an anonymous face opens nothing), loading, selected, error and empty (not states of a privacy cue).
Roster doorThe last cell of a full roster: a door to everyone who did not fit.
when people outnumber the visible cells (eight, two rows of four) the last cell is a door: a 48 gray100 circle with “+N” 15/22·600 ink, the name line “외 N명” 13/18·500 · the same 100 pitch as a roster cell, no level Tag · it opens 참석자, as does the section head’s 전체 보기
Library §19.3 Roster door “when people outnumber the visible cells (eight, two rows of four), the last cell is a door: a 48 gray100 circle with ‘+N’ 15/22·600 ink and the name line ‘외 N명’. The door opens 참석자, as does the section head’s 전체 보기.”
Anatomy
20370
김민준
3.5
이서연
4.0
박지훈
3.5
최유나
3.0
정우진
3.5
한소희
4.0
강태오
3.0
+4
외 4명
4852 to the next
100 pitch
avatar 48 · name 20 · level slot 24
100 pitch
the door is the last of eight
Press
the door presses as a roster cell: scale 0.98 · opacity 0.80 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
When
only when people outnumber the cells: 8 people draw eight cells and no door; 9 draw seven and the door "+2"
Count
"+N" is the number not shown; above 99 it reads "+99+" and the name line "외 99+명"
Spoken
the door: "외 4명, 참석자 전체 보기" (button); the seven cells before it are "김민준, NTRP 3.5" each; the +N glyph text is hidden (the name line says it)
Motion
press: spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in / 150 ms out; the list it opens is a push (platform transition)
Reduced motion
the settle is dropped, the dim stays
Text scale
roster stacks 2-up from 150% (B6.5): the door stays the last cell; the name line wraps under the circle; every cell is a floor of 100
Used by
일정 상세 참석 roster (D3), 클럽 미리보기, 대진 rosters
Replaces
the ParticipationSection overflow text link (“모두 보기”) under a truncated roster
States
김민준
3.5
이서연
4.0
박지훈
3.5
최유나
3.0
정우진
3.5
한소희
4.0
강태오
3.0
+4
외 4명
rest
김민준
3.5
이서연
4.0
박지훈
3.5
최유나
3.0
정우진
3.5
한소희
4.0
강태오
3.0
+4
외 4명
door · pressed row .98 / .80
김민준
3.5
이서연
4.0
박지훈
3.5
최유나
3.0
정우진
3.5
한소희
4.0
강태오
3.0
+4
외 4명
door · hover
김민준
3.5
이서연
4.0
박지훈
3.5
최유나
3.0
정우진
3.5
한소희
4.0
강태오
3.0
+4
외 4명
door · focus web ring around the cell
loading gray100 circles, no door yet
김민준
3.5
이서연
4.0
박지훈
3.5
최유나
3.0
정우진
3.5
한소희
4.0
강태오
3.0
+99+
외 99+명
99+ a very full band
김민준
3.5
이서연
4.0
박지훈
3.5
최유나
3.0
정우진
3.5
한소희
4.0
강태오
3.0
+4
외 4명
dark
Not drawn: disabled, selected, error and empty (an empty roster is the empty slots, drawn on the detail board).
Text size
김민준
3.5
이서연
4.0
박지훈
3.5
최유나
3.0
정우진
3.5
한소희
4.0
강태오
3.0
+4
외 4명
130% text still 4-up
김민준
3.5
이서연
4.0
박지훈
3.5
최유나
3.0
정우진
3.5
한소희
4.0
강태오
3.0
+4
외 4명
200% text roster 2-up, the door last
5Conversationcomments and replies, the reply target, reactions with the feed / post count rule
CommentA reply under a post, with one level of replies beneath it.
avatar 32 at the rail, text column at x 64 · line 1: name 15/22·600 + MetaBreak + time 13/18·500 secondary, ⋯ IconButton (20 in 44) at the right · body 15/22·400 ink (full on the post page; 4 lines + 더보기 in previews) · 답글 달기 13/18·600 secondary (44 target by slop) · replies: avatar 24 at x 64, text column at x 96, one level · 24 between comments, 12 between a comment and its replies
Library §19.3 Comment “… deeper replies flatten with an @name; more than two replies collapse behind a lone ExpandToggle aligned to the text column (‘답글 3개 더 보기’) … A deleted comment reads ‘삭제된 댓글입니다’ (secondary) and keeps its replies. The body indents to the name because short, threaded text belongs to its author. PostRow keeps its full-width body for long content and photos.”
Anatomy
206496370
이서연2시간 전
오늘 코트 상태 어땠어요? 바닥이 미끄러웠다고 해서 신발을 바꿔야 하나 고민이에요.
답글 달기
박지훈1시간 전
저는 괜찮았는데 3번 코트는 조금 미끄러웠어요
답글 달기
321232 indent
22 line 1 · 15/22·600
44 body · 2 × 22
8
18 답글 달기
12 to replies
96 reply
avatar 24 · text x 96
Press
the 답글 달기 action (dim .85, no settle); the ⋯ is an IconButton (.92 / .78): dim 0.85, no settle · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Replies
one level only: a reply to a reply is a reply to the top comment with "@이름" first (600 weight); more than two replies collapse behind "답글 N개 더 보기" aligned to the text column
Deleted
"삭제된 댓글입니다" in secondary, a plain gray100 avatar circle, no name, no ⋯ and no 답글 달기; its replies stay
Sending / failed
sending: .6 with a 12 clock before the time; failed: "전송 실패" in danger instead of the time and a 다시 보내기 text action (MessageBubble §16)
Preview
in a feed preview the body clamps at 4 lines with 더보기 inline on the fourth
Spoken
one stop for the comment: "이서연, 2시간 전, 오늘 코트 상태 어땠어요? …, 답글 2개"; then its actions: "답글 달기" and "댓글 더보기"; a reply: "박지훈, 이서연에게 답글, 1시간 전, …"
Motion
a new comment enters fade 150 ms standard (0.2, 0, 0, 1) and rises 8; the reply toggle: reveal 250 ms enter (0, 0, 0, 1); a deleted comment swaps its text instantly
Reduced motion
the entrance and the reveal are instant
Text scale
reading class: name and time wrap together (time under the name from 150%); the body wraps; the text column keeps its x (avatars do not scale); the actions keep their 44 targets
Used by
post page (full body), feed previews (clamped), 공지사항 comments
Replaces
CommentRow (packages/features/clubs/src/post-detail/comment-row.tsx): a card-grammar row with a boxed reply field
Thread
이서연2시간 전
오늘 코트 상태 어땠어요? 바닥이 미끄러웠다고 해서 신발을 바꿔야 하나 고민이에요.
답글 달기
박지훈1시간 전
저는 괜찮았는데 3번 코트는 조금 미끄러웠어요
답글 달기
김민준30분 전
정기 모임에서는 4번 코트를 쓰면 좋겠어요
답글 달기
two replies 24 between comments, 12 to the replies, 12 between replies
이서연2시간 전
오늘 코트 상태 어땠어요? 바닥이 미끄러웠다고 해서 신발을 바꿔야 하나 고민이에요.
답글 달기
박지훈1시간 전
저는 괜찮았는데 3번 코트는 조금 미끄러웠어요
답글 달기
김민준30분 전
정기 모임에서는 4번 코트를 쓰면 좋겠어요
답글 달기
more than two the third and the rest collapse: 답글 3개 더 보기
이서연2시간 전
오늘 코트 상태 어땠어요? 바닥이 미끄러웠다고 해서 신발을 바꿔야 하나 고민이에요.
답글 달기
박지훈1시간 전
저는 괜찮았는데 3번 코트는 조금 미끄러웠어요
답글 달기
김민준30분 전
@박지훈 저도 3번 코트가 제일 미끄러웠어요
답글 달기
flattened reply a reply to a reply: @name first
삭제된 댓글입니다
박지훈1시간 전
저는 괜찮았는데 3번 코트는 조금 미끄러웠어요
답글 달기
deleted keeps its replies
States
이서연2시간 전
오늘 코트 상태 어땠어요? 바닥이 미끄러웠다고 해서 신발을 바꿔야 하나 고민이에요.
답글 달기
rest
이서연2시간 전
오늘 코트 상태 어땠어요? 바닥이 미끄러웠다고 해서 신발을 바꿔야 하나 고민이에요.
답글 달기
답글 달기 · pressed dim .85
이서연2시간 전
오늘 코트 상태 어땠어요? 바닥이 미끄러웠다고 해서 신발을 바꿔야 하나 고민이에요.
답글 달기
답글 달기 · hover
이서연2시간 전
오늘 코트 상태 어땠어요? 바닥이 미끄러웠다고 해서 신발을 바꿔야 하나 고민이에요.
답글 달기
답글 달기 · focus
이서연2시간 전
오늘 코트 상태 어땠어요? 바닥이 미끄러웠다고 해서 신발을바꿔야 하나 고민이에요. 다음 주 정기 모임 전에 미리알려주시면 좋겠어요. 다른 분들은 어떠셨는지도 궁금해요.저는 지난주에 갔는데 3번 코트가 더보기
답글 달기
preview 4 lines + 더보기 inline
이서연방금
오늘 코트 상태 어땠어요? 바닥이 미끄러웠다고 해서 신발을 바꿔야 하나 고민이에요.
답글 달기
sending .6, a clock before the time
이서연전송 실패
오늘 코트 상태 어땠어요? 바닥이 미끄러웠다고 해서 신발을 바꿔야 하나 고민이에요.
다시 보내기
failed 전송 실패 · 다시 보내기
loading avatar + three lines, pulse .6 ↔ .9
이서연2시간 전
오늘 코트 상태 어땠어요? 바닥이 미끄러웠다고 해서 신발을 바꿔야 하나 고민이에요.
답글 달기
박지훈1시간 전
저는 괜찮았는데 3번 코트는 조금 미끄러웠어요
답글 달기
dark
Not drawn: disabled (a comment is not a control; a blocked author’s comment is hidden, not dimmed), selected, error (failed, drawn), empty (the post page’s "첫 댓글을 남겨보세요" state).
Text size
이서연2시간 전
오늘 코트 상태 어땠어요? 바닥이 미끄러웠다고 해서 신발을 바꿔야 하나 고민이에요.
답글 달기
130% text name and time wrap together
이서연2시간 전
오늘 코트 상태 어땠어요? 바닥이 미끄러웠다고 해서 신발을 바꿔야 하나 고민이에요.
답글 달기
200% text time under the name; the body wraps
Composer reply targetWho a comment is answering, shown above the field while it is typed.
a 44 row inside the composer above the field: “OOO님에게 답글” 13/18·600 secondary, then a one-line excerpt 13/18·400 secondary that ellipsizes (quoted text, not a name, so it may crop) · a × IconButton (20 in 44) clears the target · the row appears with the field’s focus
Library §19.3 Composer reply target “a 44 row inside the composer, above the field … The row appears with the field’s focus, and Escape or × removes it.”
Anatomy
20370
이서연님에게 답글오늘 코트 상태 어땠어요? 바닥이 미끄러웠다고 해서 신발을 바꿔야 하나 고민이에요
저도 3번 코트가 미끄러웠어요
44 reply row
label 600 + excerpt 400 · × 20 in 44
56 composer
8 + field 40 + 8
34 home inset
Press
the × (an IconButton, .92 / .78): scale 0.92 · opacity 0.78 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Clears
the × and Escape (web) remove the target; sending clears it; leaving the field with text keeps it; with no text on blur it leaves
Excerpt
one line of the quoted comment; it may crop because it is quoted text, not a name (names are never cut)
Spoken
the row is read when it appears: "이서연님에게 답글, 오늘 코트 상태 어땠어요?"; the × is "답글 취소"; focus stays in the field
Motion
appears with the field’s focus: reveal 250 ms enter (0, 0, 0, 1) (the composer grows by 44 upward, the thread scrolls with it); × removes it fadeFast 100 ms standard
Reduced motion
it appears and leaves at once
Text scale
the label is a reading class: the excerpt ellipsizes first (never the name); from 150% the excerpt moves under the label and the row grows (a floor of 44)
Used by
post comment composer (답글 달기), DM reply-to (long-press reply)
Replaces
the reply banner built into comment-composer.tsx (packages/features/clubs/src/post-detail/comment-composer.tsx) and the DM compose bar’s quote strip (compose-bar.tsx)
States
이서연님에게 답글오늘 코트 상태 어땠어요? 바닥이 미끄러웠다고 해서 신발을 바꿔야 하나 고민이에요
메시지 보내기
row · rest
이서연님에게 답글오늘 코트 상태 어땠어요? 바닥이 미끄러웠다고 해서 신발을 바꿔야 하나 고민이에요
저도 3번 코트가 미끄러웠어요
typing the field filled, send enabled
이서연님에게 답글오늘 코트 상태 어땠어요? 바닥이 미끄러웠다고 해서 신발을 바꿔야 하나 고민이에요
메시지 보내기
× pressed icon .92 / .78
이서연님에게 답글오늘 코트 상태 어땠어요? 바닥이 미끄러웠다고 해서 신발을 바꿔야 하나 고민이에요
메시지 보내기
× focus web ring
이서연님에게 답글좋아요!
메시지 보내기
short excerpt no ellipsis
이서연님에게 답글오늘 코트 상태 어땠어요? 바닥이 미끄러웠다고 해서 신발을 바꿔야 하나 고민이에요
저도 3번 코트가 미끄러웠어요
sending send disabled, the target stays until sent
이서연님에게 답글오늘 코트 상태 어땠어요? 바닥이 미끄러웠다고 해서 신발을 바꿔야 하나 고민이에요
메시지 보내기
dark
Not drawn: disabled, loading, selected, error (a failed send is the Comment’s own failed state) and empty (no target: the row is not drawn).
ReactionsOne component in two parts: a quick row to react, a chip summary to see and toggle.
quick row (at the top of the object’s ActionSheet): the content set in 44 gray100 circles, emoji 24, gap 16 · the viewer’s reaction has a 2px ink ring, a tap toggles it and closes the sheet · summary (under a bubble or post): chips 28 tall, r full, gray100, pad 0 8, emoji 16 + count 13/18·600 tabular, gap 4 inside and 8 between · the viewer’s own has a 1.5px ink ring · long-press lists who reacted
Library §19.3 Reactions (replaces the §18.2 Reaction picker, §19.2) · §19.4 one registry, six emoji (thumbs-up, heart, clap, fire, laugh, tennis ball) for every surface · §19.5 PostRow counts vs Reactions “feed: the heart carries the total reaction count, with no chips in the feed. Post page: the per-emoji chips sit under the body, and the heart has no number. Each count is shown once.”
Anatomy
20370
4416
44 circle
emoji 24 · ring 2 for the viewer’s
20370
3
2
1
816
28 chip
hit area 44 by slop (8)
Press
a chip or a quick circle (pill physics .96 / .85): scale 0.96 · opacity 0.85 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Registry
one set of six for every surface: thumbs-up, heart, clap, fire, laugh, tennis ball (build note §19.4: today three vocabularies exist and one enum migration unifies them)
Long-press
a chip long-press opens a Sheet of PersonRows of who reacted (title "좋아요 3명"); no centred panel variant
Counts
feed: only the heart carries the total; post page: chips, and the heart has no number; each count is shown once
Spoken
a chip: "좋아요 3개, 내 반응" (a toggle with aria-pressed); a quick circle: "좋아요" (aria-pressed); the list sheet: "좋아요 3명" then PersonRows; emoji are read as their names, not as symbols
Motion
a tap toggles at once (the count changes instantly, B7.6) and the sheet leaves exit 200 ms exit (0.3, 0, 1, 1); a chip appears fade 150 ms standard (0.2, 0, 0, 1)
Reduced motion
the chip appears at once; the sheet leaves with the platform fade
Text scale
label class: chips wrap to new rows (gap 8 each way) and grow with their count; the emoji stay 16 / 24; the quick row wraps to two rows of three from 150%
Used by
메시지 long-press ActionSheet and the bubble summary, post page, 경기 / 포스트 reactions
Replaces
the post reaction map (like, clap, fire, heart, laugh) in packages/features/clubs/src/shared/post-row.tsx, the DM quick reactions (packages/features/messaging/src/dm-thread/helpers.ts) and the match reaction picker
Quick row
rest the thumbs-up is the viewer’s: 2px ink ring
none yet
pressed pill .96 / .85 on the circle
메시지
in its sheet top of a message’s ActionSheet
dark
Summary chips
3
2
1
12
rest the thumbs-up chip is the viewer’s: 1.5px ring
3
2
1
12
pressed pill .96 / .85
3
2
1
12
hover
3
2
1
12
focus web ring, offset 2
3
2
1
12
disabled a blocked thread: .5
loading counts skeleton
99+
99
99+
14
13
12
11
10
9
six reactions
3
2
1
12
dark
Not drawn: selected is the viewer’s ring (drawn); error (a failed toggle reverts and shows the Toast "반응하지 못했어요"); empty (no reactions: no chips, no row).
Feed vs post page
토요일 정기 모임 후기예요. 날씨가 좋아서 코트 세 면이 꽉 찼어요.
12
3
feed the heart carries the total (12); no chips
토요일 정기 모임 후기예요. 날씨가 좋아서 코트 세 면이 꽉 찼어요.
3
2
1
12
3
post page chips under the body; the heart has no number
좋아요 3명
김민준
NTRP 3.5
이서연
NTRP 4.0
박지훈
NTRP 3.5
who reacted long-press on a chip: a Sheet of PersonRows
Text size
3
2
1
12
130% text chips grow with their count
3
2
1
12
200% text chips wrap, one or two per row
6Sheets and bandsthe filter sheet, the edit footer, the danger band, the score sheet and the action scope
FilterSheetNarrow a list by facets in one bottom Sheet, with the result count live on the button.
bottom Sheet: groups of Choices (group title 13/18·600 secondary, 8 below, 24 between groups) · footer pair 초기화 (tonal) + “결과 n개 보기” (primary, the count live) · trigger: a Choice with a chevron and the active count, then a removable Choice (×) for each active filter · sort is its own Choice and its own Sheet (L5)
Library §18.2 FilterSheet · §20.1 L3 “테니스장 kind chips into the FilterSheet’s first group (single choice with counts) … the active kind shows as a removable Choice after the 필터 trigger” · L5 “split 정렬 from the filters … every Choice ▾ opens a bottom Sheet”.
Anatomy
20370
필터
종류
전체 124테니스장 86연습장 24샵 14
코트 바닥
하드클레이잔디
시설
야간 조명주차샤워실레슨
초기화
결과 24개 보기
20 pad
24 title row · 17/24·600
16
106 group 1
18 + 8 + 36 + 8 + 36 (wraps)
24
62 group 2
18 + 8 + 36
24
62 group 3
16
118 band
16 + 52 + 16 + 34 (pair)
Press
a Choice (the sheet’s buttons keep .97 / .92): scale 0.96 · opacity 0.85 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Reset
초기화 is disabled (.5) while nothing is set; it clears every group but keeps the sheet open
Count
“결과 n개 보기”: n is live; while it recounts the button shows its spinner (width held); 0 results disables the button ("결과 없음"); a failed count shows "결과 보기" and the helper "결과 수를 불러오지 못했어요"
Trigger
the 필터 Choice shows the count of active facets and a chevron; each active facet is a removable Choice (× 16 in a 44 target); sort stays a separate Choice ("최신순 ▾")
Spoken
the sheet is a dialog "필터"; each group is a labelled group ("종류, 단일 선택"); a Choice: "테니스장 86, 선택됨" (radio / aria-checked) or a toggle (aria-pressed); the button announces the count when it changes: "결과 24개 보기"
Motion
sheet rise 300 ms enter · exit 200 ms exit (0.3, 0, 1, 1); the count on the button changes instantly (counts never animate)
Reduced motion
the sheet appears with the platform fade; the count still changes at once
Text scale
Choices wrap and grow (label class, cap 1.5); from 150% the footer pair stacks (the primary first, 초기화 under it); the body scrolls inside the 88% cap and the footer stays pinned
Used by
테니스장 (venue-filters), 경기 기록 (seven facets), 매치 찾기, 클럽 찾기
Replaces
ExpandableFilterBar and the records facet row (packages/app/src/presentation/components/expandable-filter-bar.tsx, packages/features/records/src/records-screen.tsx)
Trigger strip
필터
no filter
필터 2
테니스장
하드
two active trigger + a removable Choice per filter
필터 2
테니스장
하드
trigger pressed pill .96 / .85
필터 2
테니스장
하드
chip pressed
필터 2
테니스장
하드
trigger focus
필터 2
테니스장
하드
dark
Sheet states
9:41
올림픽공원 복식
관리
필터
종류
전체 124테니스장 86연습장 24샵 14
코트 바닥
하드클레이잔디
시설
야간 조명주차샤워실레슨
초기화
결과 124개 보기
nothing set 초기화 disabled, all results
9:41
올림픽공원 복식
관리
필터
종류
전체 124테니스장 86연습장 24샵 14
코트 바닥
하드클레이잔디
시설
야간 조명주차샤워실레슨
초기화
결과 24개 보기
two set the count is live: 24
9:41
올림픽공원 복식
관리
필터
종류
전체 124테니스장 86연습장 24샵 14
코트 바닥
하드클레이잔디
시설
야간 조명주차샤워실레슨
초기화
결과 124개 보기
counting spinner on the button, width held
9:41
올림픽공원 복식
관리
필터
종류
전체 124테니스장 86연습장 24샵 14
코트 바닥
하드클레이잔디
시설
야간 조명주차샤워실레슨
초기화
결과 없음
no results the button disables: 결과 없음
9:41
올림픽공원 복식
관리
필터
종류
전체 124테니스장 86연습장 24샵 14
코트 바닥
하드클레이잔디
시설
야간 조명주차샤워실레슨
초기화
결과 24개 보기
dark
Not drawn: hover (web, on the Choices: controls board), disabled sheet (a sheet is never disabled), empty (no facets: the filter trigger is not drawn).
Text size
9:41
올림픽공원 복식
관리
필터
종류
전체 124테니스장 86연습장 24샵 14
코트 바닥
하드클레이잔디
시설
야간 조명주차샤워실레슨
초기화
결과 24개 보기
130% text Choices grow, groups wrap
9:41
올림픽공원 복식
관리
필터
종류
전체 124테니스장 86연습장 24샵 14
코트 바닥
하드클레이잔디
시설
야간 조명주차샤워실레슨
결과 24개 보기
초기화
200% text the footer pair stacks, primary first
Edit Sheet footerThe bottom Sheet a pencil opens to edit one field: no ×, a Cancel and a Save, and a guard when there is unsaved input.
an edit Sheet has no ×: its title row is the field name; the footer is the pair 취소 (tonal, flex 1) + 저장 (primary, flex 2) in the CTA band geometry (16 + 52 + 16 + inset) · 저장 is disabled until the value changes · dismissing with unsaved changes asks a Dialog (§21.1 C1: 수정을 그만둘까요?)
Library §18.2 Edit Sheet footer “an edit Sheet has no ×; its footer is the pair 취소 (tonal) + 저장 (primary); unsaved changes on dismiss ask a Dialog” · §21.2 Cell, edit (“opens the field’s bottom edit Sheet (EDIT-SHEET)”) · §21.1 C1 “edit forms ask 수정을 그만둘까요? (바꾼 내용은 저장되지 않아요)”.
Anatomy
20370
클럽 이름
클럽 이름
강남 라켓 클럽
2–20자
취소
저장
20 pad
24 title row · no ×
16
92 Field
label 18 + 4 + 48 + 4 + 18
16
118 band
16 + 52 + 16 + 34 (pair)
Press
the Buttons (.97 / .92): scale 0.97 · opacity 0.92 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
No ×
the cancel is a labelled button, never a bare glyph (the owner’s button-not-glyph LAW); the backdrop tap and hardware back behave as 취소
Guard
with an unsaved change, 취소, the backdrop and back ask the Dialog 수정을 그만둘까요? (그만두기 destructive); with none they just close
Saving
저장 shows its spinner (width held), 취소 is disabled, the field is inert; on success the sheet closes and a Toast confirms ("저장했어요", no dialog)
Error
a server error is the field’s danger helper and the Sheet stays open with 저장 enabled; never a Toast alone
Spoken
a dialog named by its title: "클럽 이름 수정"; focus lands on the field; 취소 and 저장 are the last two stops; "저장" announces "저장 중" while busy
Motion
sheet rise 300 ms enter · exit 200 ms exit (0.3, 0, 1, 1); the keyboard lifts the whole sheet (KeyboardStickyView), the footer docks above it
Reduced motion
platform fade, no rise
Text scale
the field and the title wrap; from 150% the pair stacks (저장 first, 취소 under it); the body scrolls inside the 88% cap and the footer stays pinned
Used by
every pencil Cell of the 일정 / 클럽 review pages and settings (EDIT-SHEET), ScoreEntry’s siblings
Replaces
the per-screen edit modals in packages/features/clubs/src/settings and the scorecard edit sheets (live-match-row, match-action-suite), each with its own close glyph
States
9:41
올림픽공원 복식
관리
클럽 이름
클럽 이름
강남 라켓클럽
2–20자
취소
저장
pristine 저장 disabled until the value changes
9:41
올림픽공원 복식
관리
클럽 이름
클럽 이름
강남 라켓 클럽
2–20자
취소
저장
changed 저장 enabled
9:41
올림픽공원 복식
관리
클럽 이름
클럽 이름
강남 라켓 클럽
2–20자
취소
저장
saving spinner on 저장, 취소 disabled
9:41
올림픽공원 복식
관리
클럽 이름
클럽 이름
강남 라켓 클럽
이미 있는 클럽 이름이에요
취소
저장
error the field’s danger helper, 저장 stays enabled
9:41
올림픽공원 복식
관리
클럽 이름
클럽 이름
강남 라켓 클럽
2–20자
취소
저장
keyboard up the footer docks above the keyboard, no home inset
9:41
올림픽공원 복식
관리
클럽 이름
클럽 이름
강남 라켓 클럽
2–20자
취소
저장
수정을 그만둘까요?바꾼 내용은 저장되지 않아요.
계속 수정
그만두기
leave guard dismiss with unsaved changes asks
9:41
올림픽공원 복식
관리
클럽 이름
클럽 이름
강남 라켓 클럽
2–20자
취소
저장
dark
Not drawn: hover, focus and pressed of the Buttons (the controls board), selected, empty and loading (the field value is already known when the sheet opens).
Text size
9:41
올림픽공원 복식
관리
클럽 이름
클럽 이름
강남 라켓 클럽
2–20자
취소
저장
130% text the pair still side by side
9:41
올림픽공원 복식
관리
클럽 이름
클럽 이름
강남 라켓 클럽
2–20자
저장
취소
200% text the pair stacked, 저장 first
CTA band · danger toneThe band’s one button in danger, for a destructive action on many items at once.
the standard CTA band (16 + 52 + 16 + the inset, hairline only while content scrolls under) with its one button in the danger tone: #B8332E with white label (the dark theme: #FA9B94 with the canvas label) · always preceded by a Dialog that names the count · a danger helper line is allowed above the button (§22.2) · never in a wizard
Library §18.2 CTA band, danger tone “the band’s one button in danger for a destructive bulk action (알림 n개 지우기), always preceded by a Dialog” · §19.1 D2 “the danger tone belongs to the confirming Dialog, not to the band” (for a single action; the bulk band is the exception).
Anatomy
20370
알림 12개 지우기
16
52 danger button
lg, full width on the rail
16
34 home inset
Press
the button (.97 / .92): scale 0.97 · opacity 0.92 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Order
tap the band → Dialog (취소 tonal, 지우기 danger) → on 지우기 the band shows its spinner, then the Toast "알림 12개를 지웠어요"; the band is never the only guard
Disabled
with nothing to delete the band is disabled (.5) or not drawn: here .5 with the helper "지울 알림이 없어요"
Colour
white on #B8332E is 5.7:1; in dark the canvas ink on #FA9B94 is 9.6:1; this is the one place a screen’s coloured action is not the ball
Spoken
"알림 12개 지우기" (button); activating it opens the Dialog "알림 12개를 지울까요?" which the screen reader reads as an alert dialog; "지우는 중" while busy
Motion
press: spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in / 150 ms out; fade 150 ms standard (0.2, 0, 0, 1) for skeleton → content
Reduced motion
the settle is dropped, the dim stays
Text scale
label class: the label wraps and the button grows (a floor of 52); the helper line wraps; the Dialog stacks its buttons from 150%
Used by
알림 센터 (알림 n개 지우기), 메시지 목록 (n개 나가기), bulk delete in Select mode
Replaces
the red ActionButton placed above the footer of the notification centre (packages/ui/src/bottom-cta-band.tsx has no tone prop)
States
9:41
알림
선택
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
알림 12개 지우기
rest
9:41
알림
선택
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
알림 12개 지우기
pressed button .97 / .92
9:41
알림
선택
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
알림 12개 지우기
알림 12개를 지울까요?지운 알림은 되돌릴 수 없어요.
취소
지우기
confirming Dialog always precedes: names the count
9:41
알림
선택
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
알림 12개 지우기
deleting spinner replaces the label, width held
9:41
알림
선택
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
알림 12개를 지웠어요
지울 알림이 없어요
알림 지우기
done a Toast, no dialog (a success needs none)
9:41
알림
선택
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
지우지 못했어요. 다시 시도해주세요
알림 12개 지우기
error a danger helper line above the button
9:41
알림
선택
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
알림 12개 지우기
focus
9:41
알림
선택
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
알림 12개 지우기
dark
Not drawn: hover (controls board), selected and empty (nothing to delete is the disabled "done" frame).
Text size
9:41
알림
선택
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
알림 12개 지우기
130% text the label grows
9:41
알림
선택
참가비 입금 확인 요청
이서연님이 9월 회비 입금을 알렸어요
방금
참가비 입금 확인 요청
이서연님이 9월 회비 입금을 알렸어요
방금
참가비 입금 확인 요청
이서연님이 9월 회비 입금을 알렸어요
방금
알림 12개 지우기
알림 12개를 지울까요?지운 알림은 되돌릴 수 없어요.
취소
지우기
200% text the Dialog stacks its buttons
ScoreEntry · Sheet formRecord a match’s score in a Sheet over the board: it opens without the keyboard on the quick picks, and a tap on a set field raises the keypad and one 저장.
open: the Sheet rises (300 ms) without the keyboard · top to bottom: title row (the match name, §19.5), set table, the quick-pick row (all seven, one tap), 저장 over the 34 home inset · typing: a tap on a set field raises the keypad, the quick row folds away (200 ms) and the sheet docks to the keyboard top (KeyboardStickyView) with 저장 right above the keypad and no home inset · the tiebreak row appears only at 6-6 / 7-6 and inline validation sits above 저장 · typing worst case under the 88% cap
Library §19.3 ScoreEntry, Sheet form · §19.5 ScoreEntry title row “the match name (‘2라운드 1번 코트’), not the teams” · §19.1 D4 “ScoreEntry opens in a Sheet over the board, one 저장 and an action Toast with 되돌리기 (6 s, §15) that restores the previous score. Errors stay inline.” (the keypad clause is amended by §23 ruling 1: the sheet opens without the keyboard) · §23 “The sheet opens without the keyboard, showing the quick picks; tapping a field raises the keypad and folds the quick row.” · §16 P1 quick pick credits the top team first; the tiebreak is a 44 row.
Anatomy
20370
2라운드 1번 코트
1세트
2세트
3세트
김민준, 이서연
6
3
박지훈, 최유나
4
빠른 입력
6-06-16-26-36-47-57-6
저장
20 pad
24 title row · match name
16
130 set table
head 18 + 8 + 48 + 8 + 48
24
106 quick pick
18 + 8 + 36 + 8 + 36
16 seam
118 저장 band
16 + 52 + 16 + 34 home inset
Press
the 저장 Button (.97 / .92); a set field and a quick Choice keep their own states (controls board): scale 0.97 · opacity 0.92 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Open
no keyboard: the title row, the set table and all seven quick picks (6-0 … 6-4, 7-5, 7-6), so a common result is one tap; 저장 sits over the 34 home inset
Typing
a tap on a set field raises the keypad and folds the quick row (200 ms); the sheet docks to the keyboard top with 저장 right above the keypad, no home inset
Quick pick
a tap credits the top team first (6-3 fills 6 for the top team, 3 for the other); tapping the same chip again swaps the teams (§16 P1)
Tiebreak
a 44 row appears under the table only at 6-6 / 7-6 with the 36 field at the right: "2세트 타이브레이크, 진 팀의 점수"
Guard
dismissing with unsaved digits asks the Edit-Sheet Dialog (수정을 그만둘까요?); with none the sheet just closes
After save
the sheet closes and an action Toast (되돌리기, 6 s) restores the previous score; the board refreshes underneath
Spoken
a dialog "2라운드 1번 코트, 점수 입력"; the fields: "1세트, 김민준 이서연 팀, 점수" …; the quick Choices: "6 대 3" (read while the row is shown); on error the helper is announced with the field; after save the Toast "저장됐어요" (no action)
Motion
sheet rise 300 ms enter · exit 200 ms exit (0.3, 0, 1, 1); the sheet rises without the keyboard; a tap on a set field raises the keypad and the quick row folds away over 200 ms, the sheet docking to the keyboard top; the numerals and counts never animate
Reduced motion
the sheet appears with the platform fade; the quick row disappears at once when the keypad rises
Text scale
reading class: team names wrap in the row label column; the 48 score fields keep their 48 (numeral class, cap 1.5 for the digits); quick Choices wrap to more rows; the sheet scrolls inside the 88% cap with 저장 pinned above the keypad
Used by
경기 보드 (점수 입력), 스코어카드 (the match row’s edit), 대진 results
Replaces
the full-screen score page and score-edit-sheet.tsx’s own layout (packages/features/sessions/src/scorecard/score-edit-sheet.tsx)
Anatomy · typing
20370
2라운드 1번 코트
1세트
2세트
3세트
김민준, 이서연
6
3
박지훈, 최유나
4
저장
123
456
789
0⌫
20 pad
24 title row · match name
16
130 set table
head 18 + 8 + 48 + 8 + 48
16 seam
84 저장 band
16 + 52 + 16, inset 0
291 keypad
numeric
Typing the quick row has folded away (200 ms; instant under reduced motion), the sheet is docked to the keyboard top, and 저장 sits right above the keypad with no home inset. Measured worst case (tiebreak plus validation) is in the state frames below, with the keypad, against the 88% cap of 742.
States
9:41
올림픽공원 복식
관리
2라운드 1번 코트
1세트
2세트
3세트
김민준, 이서연
박지훈, 최유나
빠른 입력
6-06-16-26-36-47-57-6
저장
open after the rise: no keyboard, the quick row, 저장 disabled
9:41
올림픽공원 복식
관리
2라운드 1번 코트
1세트
2세트
3세트
김민준, 이서연
6
3
박지훈, 최유나
4
빠른 입력
6-06-16-26-36-47-57-6
저장
open · quick pick 6-3 fills the second set, top team first: one tap
9:41
올림픽공원 복식
관리
2라운드 1번 코트
1세트
2세트
3세트
김민준, 이서연
6
박지훈, 최유나
저장
123
456
789
0⌫
typing a tap on a set field: keypad up, quick row folded
9:41
올림픽공원 복식
관리
2라운드 1번 코트
1세트
2세트
3세트
김민준, 이서연
6
3
박지훈, 최유나
4
저장
123
456
789
0⌫
typing · complete 저장 enabled once a set is complete
9:41
올림픽공원 복식
관리
2라운드 1번 코트
1세트
2세트
3세트
김민준, 이서연
6
7
박지훈, 최유나
4
6
2세트 타이브레이크, 진 팀의 점수
저장
123
456
789
0⌫
typing · tiebreak 7-6: the 44 row appears
9:41
올림픽공원 복식
관리
2라운드 1번 코트
1세트
2세트
3세트
김민준, 이서연
6
7
박지훈, 최유나
4
2
7-2는 가능한 점수가 아니에요
저장
123
456
789
0⌫
typing · validation inline danger helper + the two fields in error
9:41
올림픽공원 복식
관리
2라운드 1번 코트
1세트
2세트
3세트
김민준, 이서연
6
7
박지훈, 최유나
4
6
2세트 타이브레이크, 진 팀의 점수
3
타이브레이크 점수를 확인해 주세요
저장
123
456
789
0⌫
typing · worst case tiebreak + validation: 372 + 291 keypad = 663, under the 742 cap
9:41
올림픽공원 복식
관리
2라운드 1번 코트
1세트
2세트
3세트
김민준, 이서연
6
7
박지훈, 최유나
4
6
2세트 타이브레이크, 진 팀의 점수
3
저장
123
456
789
0⌫
saving spinner on 저장, fields inert
9:41
올림픽공원 복식
관리
저장됐어요
after save result Toast, no action
9:41
올림픽공원 복식
관리
2라운드 1번 코트
1세트
2세트
3세트
김민준, 이서연
6
3
박지훈, 최유나
4
빠른 입력
6-06-16-26-36-47-57-6
저장
open · dark
9:41
올림픽공원 복식
관리
2라운드 1번 코트
1세트
2세트
3세트
김민준, 이서연
6
3
박지훈, 최유나
4
저장
123
456
789
0⌫
typing · dark
Not drawn: hover (controls board), disabled sheet (it is a form), selected, empty (the empty frame is the first state), and a loading skeleton (the match is known when the sheet opens).
Rise
9:41
올림픽공원 복식
관리
2라운드 1번 코트
1세트
2세트
3세트
김민준, 이서연
6
3
박지훈, 최유나
4
빠른 입력
6-06-16-26-36-47-57-6
저장
0 ms y +100%, scrim 0
9:41
올림픽공원 복식
관리
2라운드 1번 코트
1세트
2세트
3세트
김민준, 이서연
6
3
박지훈, 최유나
4
빠른 입력
6-06-16-26-36-47-57-6
저장
150 ms 89% risen (0, 0, 0, 1)
9:41
올림픽공원 복식
관리
2라운드 1번 코트
1세트
2세트
3세트
김민준, 이서연
6
3
박지훈, 최유나
4
빠른 입력
6-06-16-26-36-47-57-6
저장
300 ms at rest, then the first empty field takes focus
Text size
9:41
올림픽공원 복식
관리
2라운드 1번 코트
1세트
2세트
3세트
김민준, 이서연
6
3
박지훈, 최유나
4
빠른 입력
6-06-16-26-36-47-57-6
저장
130% text open: names wrap, chips grow
9:41
올림픽공원 복식
관리
2라운드 1번 코트
1세트
2세트
3세트
김민준, 이서연
6
3
박지훈, 최유나
4
빠른 입력
6-06-16-26-36-47-57-6
저장
200% text open: the sheet scrolls inside the cap, 저장 pinned
9:41
올림픽공원 복식
관리
2라운드 1번 코트
1세트
2세트
3세트
김민준, 이서연
6
3
박지훈, 최유나
4
저장
123
456
789
0⌫
130% text typing: names wrap, 저장 above the keypad
9:41
올림픽공원 복식
관리
2라운드 1번 코트
1세트
2세트
3세트
김민준, 이서연
6
3
박지훈, 최유나
4
저장
123
456
789
0⌫
200% text typing: the sheet scrolls inside the cap, 저장 pinned
ActionSheet · scope and the group-title ruleOne sheet for any object’s actions: a row’s ⋯, a message’s long-press, a comment’s ⋯, the 관리 sheet.
the ActionSheet of §14 as the generic sheet: title 17/24·600 = the object’s name · groups of Cells 48 · a leading 20 icon is optional per instance and consistent within one sheet · a group title (13/18·600 secondary, 8 below) shows only when the sheet has two or more non-destructive groups; a single group and the destructive group are untitled (the danger colour marks the latter) · 24 still separates groups · cap 88%, the body scrolls
Library §19.3 ActionSheet (scope) “the generic sheet for any object’s actions, not a 관리-only sheet: row ⋯ (§18.1 D4), a message’s long-press, a comment’s ⋯. A leading 20 icon is optional per instance, consistent within one sheet.” §19.5 “a group title shows only when the sheet has two or more non-destructive groups … 24 still separates groups.” §16 P2: cap 88%.
Anatomy
20370
김민준
멤버
프로필 보기
메시지 보내기
관리
운영진으로 지정
회비 면제
멤버 내보내기
20 pad
24 title row · 17/24·600
16
26 group title
18 + 8
96 two Cells
48 each, hairline between
24
26 group title
96 two Cells
24
48 destructive
untitled, danger ink
20 + 34 inset
Press
a Cell presses as a row (.98 / .80): scale 0.98 · opacity 0.80 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Title rule
titles only when two or more non-destructive groups exist: a single group reads as one list, and the destructive group needs no title because its colour is the label
Icons
a leading 20 icon is optional per instance but consistent within one sheet: all Cells have one or none; the destructive Cell keeps its danger colour with or without
Destructive
last, in danger; it opens its confirming Dialog (never acts on tap); a sheet with only destructive Cells is not an ActionSheet
Quick row
a message’s sheet starts with the Reactions quick row, then 24, then the groups
Spoken
a dialog named by its title ("김민준"); each Cell is a button "프로필 보기"; the destructive Cell is announced last; the quick-reaction circles of a message sheet are a group of toggle buttons
Motion
sheet rise 300 ms enter · exit 200 ms exit (0.3, 0, 1, 1); a Cell’s result closes the sheet with the exit curve
Reduced motion
platform fade
Text scale
reading class: labels wrap; the 20 icon stays; every Cell is a floor of 48; the body scrolls inside the 88% cap and the destructive group stays last, even below the first screen
Used by
row ⋯ (D4: always an ActionSheet), message long-press, comment ⋯, the 관리 sheet, post ⋯
Replaces
HeaderActionsMenu used on rows and the per-screen ConfirmSheet lists (packages/ui/src/header-actions-menu.tsx, confirm-sheet.tsx)
Rule · titles
9:41
올림픽공원 복식
관리
댓글
수정
링크 복사
삭제
one group + destructive no titles at all
9:41
올림픽공원 복식
관리
김민준
멤버
프로필 보기
메시지 보내기
관리
운영진으로 지정
회비 면제
멤버 내보내기
two groups + destructive the two groups titled, the destructive untitled
9:41
올림픽공원 복식
관리
올림픽공원 복식
공유
단톡방
링크 복사
no destructive a single group: untitled
Scope
9:41
올림픽공원 복식
관리
메시지
답장
복사
삭제
message long-press the quick row first, then the group
9:41
올림픽공원 복식
관리
댓글
링크 복사
신고
someone else’s comment 신고 is the destructive-class Cell
9:41
올림픽공원 복식
관리
댓글
수정
링크 복사
삭제
no icons consistent: none at all
9:41
올림픽공원 복식
관리
올림픽공원 복식
운영
참석자 관리
출석 확인3
참가비 관리
경기
경기 규칙
호스트 관리
대진 짜기
공유
공유
단톡방
링크 복사
경기 종료
일정 취소
일정 삭제
88% cap the body scrolls, the destructive group below the first screen
States
프로필 보기
Cell · pressed row .98 / .80
프로필 보기
Cell · hover
프로필 보기
Cell · focus
회비 면제
Cell · disabled .5: not available to this viewer
출석 확인3
Cell · with a count a neutral count Tag (출석 확인 3)
9:41
올림픽공원 복식
관리
댓글
수정
링크 복사
삭제
dark
Not drawn: loading (the sheet is built from a known object), selected (a Cell is a command, not a choice), error (a failed action is the Toast) and empty (a sheet with no actions is not opened).
7Dataheatmap, strip deltas, form strip, live row, photo grid, album overlay and the glyph tile
Activity heatmapA year’s rhythm at a glance: one cell per day, darker for more matches.
7 rows × n weeks of 12 cells · gap 4 · r3 · 22 weeks fit the 350 rail (22 × 12 + 21 × 4 = 348) · five steps from gray100 to ink · month labels 12/16 secondary above (4 under them) · a scrub readout like the Chart’s: a 1px #D4D4D4 guide, the cell ringed, an ink count Tag above
Library §18.2 Activity heatmap “7 rows × n weeks of 12 cells, gap 4, r3; five steps from gray100 to ink; month labels 12/16 secondary above; a scrub readout like the Chart’s” · §4 Chart “scrub: 1px #D4D4D4 guide + 8px ink marker + readout count tag (ink) above; empty = glyph + 15/22 secondary centred”.
Anatomy
20370
5월6월7월8월9월
1222 weeks
16 month labels · 12/16
108 grid
7 × 12 + 6 × 4
Press
a scrub: pressing a cell shows its day; there is no per-cell press physics: dim 0.85, no settle · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Steps
gray100 · #C4C7C4 · #8D928E · secondary #4F5851 · ink #0D100E for 0, 1, 2, 3, 4+ matches (dark: surface → ink inverted); the colour is never the only carrier: the scrub readout and the spoken total give the numbers (WCAG 1.4.1)
Range
the last 22 weeks, the current week last; no horizontal scroll (a longer history is the 기록 chart)
Empty
every cell at step 0 plus the caption “아직 경기 기록이 없어요” (the grid is kept so the layout does not jump)
Spoken
the grid is one image stop: "경기 활동, 최근 22주 중 14주에 경기, 모두 31경기"; scrubbing (web: arrow keys) announces "9월 14일, 3경기" in a live region; the cells are not stops
Motion
the grid fades in fade 150 ms standard (0.2, 0, 0, 1); the scrub marker follows the finger with no animation and the readout changes instantly
Reduced motion
unchanged (gesture tracking is not motion the app times)
Text scale
month labels are chrome class (cap 1.3) and may collide: labels that do not fit are dropped, never overlapped; the 12 cells and the grid never scale (a graphic); the readout Tag grows with its text
Used by
기록 tab (활동), 프로필 활동, 클럽 활동
Replaces
ActivityHeatmapCard’s own grid and legend (packages/features/records/src/cards/activity-heatmap-card.tsx)
States
5월6월7월8월9월
rest
5월6월7월8월9월
9월 14일 3경기
scrub a ringed cell, a 1px guide, the ink readout
5월6월7월8월9월
focus web: the ring around the grid; arrow keys move the scrub
5월6월7월8월9월
loading gray100 cells, pulse .6 ↔ .9
5월6월7월8월9월
아직 경기 기록이 없어요
empty all step 0, kept in place
5월6월7월8월9월
dark
Not drawn: pressed, hover and disabled (not a control), selected (the scrub is its selection), error (a failed load is the section’s retry Notice).
Strip with deltasThe Strip’s numbers with how they moved since the last period.
an optional delta line 12/16·600 secondary under each label: a plain sign (“+3”, “−2”), never red or green or an arrow · the Strip grows 80 → 100 for every column · the comparison (“지난달 대비”) is named once, in the caption row under the section head · zero shows “0” · no comparison data leaves the line empty (height kept), never a dash · no column with data returns the Strip to 80 and drops the caption
Library §19.3 Strip with deltas “deltas are numbers, not status” · §19.5 “zero shows ‘0’; with no comparison data the delta line stays empty in that column (its height is kept so the columns align) and never shows a dash. If no column has data, the Strip returns to 80 and the caption is omitted.”
Anatomy
20370
이번 달 기록
지난달 대비
12경기+3
58%승률−4
9출석0
column16
24 section head
32 caption · 지난달 대비
100 Strip
numeral 32 · 4 · label 18 · 4 · delta 16
Press
a linked column presses as a row (.98 / .80): scale 0.98 · opacity 0.80 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Delta
a plain sign and a number: "+3", "−2", "0"; never colour (the Strip carries no status), never an arrow glyph; it is quiet: 12/16·600 secondary
No data
a column without a comparison keeps its empty delta line so the baselines align; if no column has data the Strip is 80 and the caption row is omitted
Spoken
one stop per column when it links: "경기 12, 지난달보다 3 늘어요" — the sign is spoken as a direction ("늘어요" / "줄어요" / "같아요"), never as red or green; an unlinked Strip is text in reading order
Motion
press: spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in / 150 ms out; fade 150 ms standard (0.2, 0, 0, 1) for skeleton → content
Reduced motion
the settle is dropped, the dim stays
Text scale
numeral class (cap 1.5) and label class: columns stay three side by side at every scale (they only grow in height, a floor of 80 / 100); the delta line wraps under its label
Used by
기록 tab Strip (경기 · 승률 · 출석), 프로필 Strip, 클럽 기록
Replaces
StatStrip / CellRow without deltas (packages/ui/src/stat-strip.tsx): the delta text was set inside the label
States
이번 달 기록
지난달 대비
12경기+3
58%승률−4
9출석0
with deltas 100; zero shows 0
이번 달 기록
지난달 대비
12경기+3
58%승률
9출석0
one column without data the line stays empty, height kept, no dash
이번 달 기록
12경기
58%승률
9출석
no column has data back to 80, no caption
이번 달 기록
지난달 대비
12경기+3
58%승률−4
9출석0
pressed a linked column: row .98 / .80
이번 달 기록
지난달 대비
12경기+3
58%승률−4
9출석0
focus web ring around the column
이번 달 기록
지난달 대비
loading skeleton at the final 100
이번 달 기록
지난달 대비
12경기+3
58%승률−4
9출석0
dark
Not drawn: hover (web: .85), disabled, selected, error (the section’s retry Notice) and empty (no data at all is the 80 Strip with 0 values).
Text size
이번 달 기록
지난달 대비
12경기+3
58%승률−4
9출석0
130% text three columns hold
이번 달 기록
지난달 대비
12경기+3
58%승률−4
9출석0
200% text numerals at the 1.5 cap, the Strip grows
Form stripThe last five results of a pairing or a player, in order.
five 24 × 24 r6 cells, gap 4, chronological left to right · 승 = ink fill with white 12/16·700 · 패 = gray100 with secondary 12/16·700 · head-to-head and player records only, never in rows (colour-only dots fail WCAG 1.4.1) · fewer than five matches draw only those cells; none omits the strip
Library §19.3 Form strip “head-to-head and player records only, never in rows: rows keep one trailing figure … Spoken ‘최근 5경기: 승, 승, 패, 승, 패’. In a row, recent form is line-2 text (‘최근 5경기 3승 2패’).” §19.5 “the last five completed head-to-head matches, in chronological order, from the existing head-to-head query”.
Anatomy
20370
최근 5경기
승승패승패
24424
24 section head
8
24 cells · r6
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Not in rows
a row keeps one trailing figure; recent form in a row is line-2 text: “최근 5경기 3승 2패”
Data
the last five completed head-to-head matches in chronological order (left = oldest); fewer than five draw only those cells; none: the strip is omitted (no empty slots)
Spoken
one image stop: "최근 5경기: 승, 승, 패, 승, 패" (oldest first, the same order as the cells); the cells are hidden individually
Motion
none: the change is instant
Reduced motion
unchanged
Text scale
the 24 cell is a floor and its 승 / 패 is a label (chrome cap 1.3): up to 130% the character still fits; the strip never wraps (five cells = 140 wide)
Used by
head-to-head (상대 전적), 플레이어 기록 header
Replaces
the colour-only result dots in rows (win green, loss red) of the profile and head-to-head screens
States
승승패승패
five results
승패승
three results only those cells
승승승승승
all wins
none the strip and its head are omitted
loading five gray100 cells
박지훈
2승 1패
최근 5경기 3승 2패
in a row (the rule) line-2 text, never dots
승승패승패
dark ink fill becomes the dark ink, label the canvas
Not drawn: pressed, hover, focus, disabled (static), selected, error and empty (omitted).
Live rowThe one line under a scoring header that says the match is live, when and where.
min 24, directly under the header: the Live tag (§6), 8, then the date and venue as MetaSegments 13/18·500 secondary at the rail · 8 above and below · replaces the LIVE band on 스코어카드 and 경기 보드
Library §19.3 Live row “min 24, directly under the header: the Live tag (§6), 8, then the date and venue as MetaSegments 13/18·500 secondary at the rail; 8 above and below. Replaces the LIVE band on 스코어카드 and 경기 보드.” §6 Live tag “danger solid Tag with a 6px white pulse dot (1600 ms); reduced motion: static dot”; §15 loops 5 s per mount.
Anatomy
20370
스코어카드
LIVE
9월 19일 (토)
올림픽공원 3번 코트
Live tag8
56 header
8
24 live row
8
Ended
when the match ends the Live tag is replaced by a neutral Tag “종료” and the line stays (proposal: §19.3 only specifies the live state)
Spoken
one text stop: "라이브, 9월 19일 토요일, 올림픽공원 3번 코트"; the pulse is hidden; the Live tag reads as the word "라이브"
Motion
the dot pulses 1600 ms, for 5 s per mount, then rests on the static frame (WCAG 2.2.2); the row appears with the page; scores and counts below never animate
Reduced motion
the dot stays, the ring is not drawn (a static frame from the start)
Text scale
the tag and the line are label / reading classes: from 150% the date and venue wrap under the tag; the row is a floor of 24 + 16
Used by
스코어카드, 경기 보드, 관전 화면
Replaces
the LIVE band of the scorecard and match board (SessionLiveBand, packages/features/sessions/src/scorecard)
States
스코어카드
LIVE
9월 19일 (토)
올림픽공원 3번 코트
live · pulse start 0 ms: ring at the dot, α.45
스코어카드
LIVE
9월 19일 (토)
올림픽공원 3번 코트
live · pulse mid 800 ms: ring ×1.7, α.22
스코어카드
LIVE
9월 19일 (토)
올림픽공원 3번 코트
live · pulse end 1600 ms: ring ×2.4, α0
스코어카드
LIVE
9월 19일 (토)
올림픽공원 3번 코트
after 5 s / reduced motion the static dot, no ring
스코어카드
종료
9월 19일 (토)
올림픽공원 3번 코트
ended neutral Tag 종료 (proposal)
스코어카드
LIVE
9월 19일 (토) 09:00
올림픽공원 테니스장 3번 코트 (강남구 올림픽로 424)
long venue the line wraps next to the Tag; the row grows
스코어카드
LIVE
9월 19일 (토)
올림픽공원 3번 코트
dark
Not drawn: pressed, hover, focus, disabled (the row is static text), loading (the scorecard skeleton), selected, error and empty.
Text size
스코어카드
LIVE
9월 19일 (토)
올림픽공원 3번 코트
130% text the tag grows with its label
스코어카드
LIVE
9월 19일 (토)
올림픽공원 3번 코트
200% text the line wraps beside the tag
PhotoGridA club or a venue’s photos as one full-bleed grid, with select mode for managing them.
3-up, full-bleed 390, 2pt gaps, square tiles · the tile is (390 − 4) / 3 = 128.67, derived from the width and exempt from the 4pt grid like any computed media size (D5) · the +N overflow tile is the PostRow pattern (ink α0.5 with 20/28·700 white) · long-press enters select · a partial last row is left-aligned, never stretched
Library §18.2 PhotoGrid “3-up full-bleed, 2pt gaps, square tiles; long-press enters select; the +N overflow tile pattern from PostRow” · §18.1 D5 “앨범 grid: 3-up full-bleed with 2pt gaps (the photo-grid standard); tile size is derived from the width and exempt from the 4pt grid”.
Anatomy
0390
128.672128.67
128.67 tile
square, computed
128.67 tile
Press
a tile (dim .85, no settle: a full-bleed image does not scale): dim 0.85, no settle · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Select
a long-press on a tile enters select mode (a 24 round check at the tile’s top-right, inset 8: off = a 2px white ring on ink α0.3, on = ink fill + a white check, the photo dims to .6); the header becomes “N장 선택됨” with 취소 at the right; the actions (삭제) are one CTA band (proposal: §18.2 gives only the long-press)
Overflow
in a preview the last visible tile carries “+N” (ink α0.5, 20/28·700 white); in the full grid there is no overflow tile
Tile error
a tile that fails to load is gray100 with a 24 image glyph in secondary and stays pressable (retry on open)
Spoken
the grid is a list "사진 12장"; each tile a button "사진 3 / 12" that opens the viewer; in select mode a tile is a checkbox "사진 3, 선택됨" and the header says "2장 선택됨"
Motion
the tiles fade in fade 150 ms standard (0.2, 0, 0, 1) as they load; select mode: the checks fade fade 150 ms standard (0.2, 0, 0, 1); the viewer is a platform transition
Reduced motion
the cross-fade becomes instant
Text scale
photos do not scale and the grid keeps 3 columns at every size; only the header text and the band grow
Used by
클럽 앨범, 테니스장 사진, the +N preview inside a system PostRow
Replaces
the horizontal PhotoStrip of the venue screen (packages/features/sessions/src/directory-venue-detail/parts/photo-strip.tsx) and the club album’s ad hoc 2-up grid
States
rest · 9 photos
a partial last row 4 photos: left-aligned
tile pressed dim .85, no settle
tile hover
tile focus web: the ring inside the tile
2장 선택됨
취소
select mode long-press: checks, 2 picked
a tile that failed gray100 + the image glyph
loading gray100 tiles, pulse .6 ↔ .9
+9
preview with +N in a post: 350 wide, tile 115.33
아직 사진이 없어요경기 후에 찍은 사진을 올려보세요
사진 올리기
empty first-run EmptyState (members: 사진 올리기)
dark
Not drawn: disabled (a blocked album is the permission EmptyState), error of the whole grid (the section’s retry Notice).
MediaBand · album overlayA photo band that holds several photos says which one you are on.
the photo MediaBand (240, scrim, glyph circles) with a count Tag (neutral solid, “1 / 12”, sm 24) at the bottom-right, 12 in, and pager dots (6 px white α0.6, the current one white, gap 8) at the bottom-centre on the Tag’s centre line · one photo draws neither
Library §18.2 MediaBand album overlay “photo band count Tag (neutral solid ‘1 / 12’) bottom-right and pager dots (6px white α0.6, current white) bottom-centre”.
Anatomy
20370
9:41
1 / 12
Tag12
240 band · photo variant
status bar included
24 Tag · centre y 216
12 in
Press
the band (dim .85, no settle); the glyph circles keep their own press: dim 0.85, no settle · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Dots
one dot per photo up to 12; with more than 12 the dots are not drawn and the Tag alone carries the position (proposal: §18.2 does not cap the dots)
One photo
no Tag and no dots; the band is just the cover (COVER-ALWAYS: a club without photos shows its default cover, with no overlay)
Scrim
the white dots sit on the photo’s bottom, which the top scrim does not cover: on a bright photo the Tag (solid) stays legible, the dots rely on α0.6 white over a darkened photo (a bottom scrim ink α0.2 → 0 over 48 is proposed)
Spoken
the band is one stop: "사진 1 / 12, 앨범 열기" (a swipe moves to the next photo and announces "사진 2 / 12"); the dots are hidden
Motion
a swipe tracks the finger; the settle to the next photo is the platform page animation; the Tag text changes instantly
Reduced motion
the page change is instant; the dots and the Tag are unchanged
Text scale
the Tag is a label class (cap 1.5): it grows with its text and stays 12 from the corner; the dots do not scale (graphics)
Used by
클럽 홈 / 클럽 미리보기 with an album, 테니스장 detail with photos
Replaces
the single cover image of the club hub band (a hub with an album had no way to browse from the band)
States
9:41
1 / 12
1 / 12
9:41
12 / 12
12 / 12 the last dot is current
9:41
2 / 3
3 photos
9:41
7 / 24
more than 12 the Tag alone: 7 / 24
9:41
one photo no overlay
9:41
1 / 12
pressed dim .85 on the photo
9:41
1 / 12
focus web: ring inset 2
9:41
loading gray100, no overlay
9:41
error the default cover, no overlay
9:41
3 / 12
dark
Not drawn: hover (web, opacity .85), disabled, selected and empty (a band with no photos is the default cover).
Thumb · glyph tile, and the medal setThe identity image of an object that has no photo or logo: its glyph on a tile, in its tier colour once earned.
56 × 56 r12 gray100 with the object’s 24 glyph centred (an identity image, so the no-glyphs-in-rows rule does not apply) · earned = the solid tier fill with the glyph in its on-colour · locked = gray100 with a secondary glyph and the row’s trailing figure the progress (“7/10”) · hidden = “?” 20/28·700 secondary with L1 “숨겨진 업적” · one medal set of four fills feeds the tile, the Podium rings and every medal
Library §20.2 Thumb, glyph tile · §20.3 “earned glyph tile = the solid tier fill with the glyph in its on-colour; locked = gray100 with the glyph in secondary … One medal set: these four values feed the glyph tile, the Podium rings and every medal use. The shared-semantics.ts medal set retires at build. Dark mode keeps the same fills.” §20.1 L6 “keep the unreachable 업적 states drawn and marked ‘not reachable today’”.
Anatomy
20370
첫 승리
첫 경기에서 이겼어요
5612
12 pad
56 tile · r12
12 pad
Press
the row presses as a row (.98 / .80); the tile has no press of its own: scale 0.98 · opacity 0.80 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Colour
tier fills are used as fills, never as glyph colours on gray100 (silver 2.91:1 and gold 2.09:1 fail WCAG 1.4.11); the on-colours below pass 4.89 to 7.63; no weak tint fills (S2 neutrals are pure grey)
Not reachable
progress and hidden states are drawn but marked “not reachable today” until a non-챌린지 achievement can be earned (§20.1 L6): the build either schedules the unlock engine or deletes the states
Spoken
one stop: "첫 승리, 달성, 9월 19일"; locked: "100경기, 미달성, 7/10"; hidden: "숨겨진 업적, 미달성"; the glyph is hidden, the tier name is not read (the state is)
Motion
the unlock moment: the fill cross-fades from gray100 to the tier fill in fade 150 ms standard (0.2, 0, 0, 1) with the success haptic; nothing loops
Reduced motion
the cross-fade becomes instant
Text scale
the tile and its 24 glyph do not scale (an identity image); the row text wraps beside it; the “?” is hero class capped at 1.7
Used by
업적 (list-b board), 순위표 Podium rings, 기록 badges, the medal Tags
Replaces
the shared-semantics.ts medal colours (#D9A621 …) and the achievement tile’s glyph-in-tier-colour on gray100 (packages/app/src/config/shared-semantics.ts)
Medal set
bronze
#B2580B · glyph #FFFFFF
4.89:1 measured (library 4.89:1) · ΔE76 79 from gray100
silver
#8E8E93 · glyph #15181A
5.47:1 measured (library 5.47:1) · ΔE76 36 from gray100
gold
#D9A00F · glyph #15181A
7.63:1 measured (library 7.63:1) · ΔE76 77 from gray100
platinum
#4A6378 · glyph #FFFFFF
6.27:1 measured (library 6.27:1) · ΔE76 57 from gray100
Computed on this board: the contrast of each glyph on its fill and the ΔE76 of each fill against gray100 (library: “ΔE76 ≥ 36 between any fill and gray100”): the smallest here is 36.
Tiles
bronze 브론즈
silver 실버
gold 골드
platinum 플래티넘
locked 미달성
?
hidden 숨겨진
plain 문서
?
dark the same fills, the locked tile on the dark surface
States
첫 승리
9월 19일 달성
earned
100경기
7/10
경기를 100번 해요
locked · progress not reachable today
?
숨겨진 업적
조건은 달성하면 알 수 있어요
hidden not reachable today
챌린지 완주
9월 챌린지를 모두 마쳤어요
pressed row .98 / .80
첫 출석
첫 모임에 참석했어요
focus
loading tile 56 r12 skeleton
10경기
9월 19일 달성
100경기
7/10
경기를 100번 해요
dark · earned and locked
Not drawn: hover (row .85), disabled, selected, error (the list’s retry) and empty (the list’s EmptyState).
8Statesthe permission wall, the offline strip over a band, content under a band and the field skeleton
EmptyState · permissionThe third kind of empty: the page is real but this viewer may not see it.
the content area’s centred block: a 48 lock glyph in secondary → 16 → title 17/24·600 ink → 8 → body 15/22 secondary (one line naming what sign-in unlocks) → 24 → one md (44) Button, primary when it is the screen’s only action, else tonal · the Button is drawn only when an action resolves the wall (로그인); a role wall has none and the header’s back is the way out · never on a hub
Library §20.2 EmptyState, permission “… It joins first-run and filtered-empty as the third kind of empty (contract §3) and never appears on a hub.” §9 EmptyState: “centred; top 64; disc 80 gray100 with a 32 glyph secondary; title 17/24·600; body 15/22 secondary; one md tonal button”.
Anatomy
195
로그인이 필요해요로그인하면 내 기록을 볼 수 있어요
로그인
48 lock glyph
secondary, no disc
16
24 title · 17/24·600
8
22 body · 15/22
24
44 md Button
Press
the Button (.97 / .92): scale 0.97 · opacity 0.92 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Which Button
primary when it is the screen’s only action (a signed-out 내 기록), tonal when another action exists; absent for a role wall (e.g. “운영진만 볼 수 있어요”): the wall cannot be resolved by the viewer
Not elsewhere
it never appears on a hub (홈, 기록, 프로필 tabs have their own signed-out states); it fills the content area of a pushed page, header and back stay
Kinds of empty
first-run (disc + glyph + the creating action), filtered-empty (disc + 필터 해제), permission (this one); an error is a different state (AlertCircle + 다시 시도)
Spoken
the screen’s h1 is the header title ("내 기록"); the block: "로그인이 필요해요" (heading 2), the body, then the button "로그인"; the lock glyph is hidden
Motion
appears with the page: fade 150 ms standard (0.2, 0, 0, 1) from the skeleton; the Button’s press is the standard
Reduced motion
the cross-fade becomes instant
Text scale
reading class: title and body wrap and the block grows; the 48 glyph stays; the Button grows with its label (a floor of 44) and stays centred
Used by
signed-out 내 기록 / 프로필 detail routes, role-gated club pages
Replaces
EmptyState (packages/ui/src/empty-state.tsx) with its mint disc used for locked pages, and per-screen ‘로그인이 필요해요’ text blocks
States
9:41
내 기록
로그인이 필요해요로그인하면 내 기록을 볼 수 있어요
로그인
sign-in wall the one action: primary 로그인
9:41
회비 관리
운영진만 볼 수 있어요클럽 운영진에게 문의해보세요
role wall no Button: the header’s back is the way out
9:41
내 클럽
로그인이 필요해요로그인하면 내 클럽을 볼 수 있어요
로그인
with another action tonal when a second action exists
9:41
내 기록
로그인이 필요해요로그인하면 내 기록을 볼 수 있어요
로그인
Button pressed button .97 / .92
9:41
내 기록
로그인이 필요해요로그인하면 내 기록을 볼 수 있어요
로그인
Button focus
9:41
내 기록
resolving the auth state is unknown: a skeleton, never a flash of the wall
9:41
내 기록
로그인이 필요해요로그인하면 내 기록을 볼 수 있어요
로그인
dark
Not drawn: hover (web .85 on the Button), disabled, selected, error and empty (this is the empty).
Text size
9:41
내 기록
로그인이 필요해요로그인하면 내 기록을 볼 수 있어요
로그인
130% text the block wraps
9:41
내 기록
로그인이 필요해요로그인하면 내 기록을 볼 수 있어요
로그인
200% text the Button grows, stays centred
Offline strip · over a MediaBandThe offline notice on a page whose top is a photo or a map.
the Offline strip (350 × 48 everywhere, §9 corrected: a neutral Notice, “오프라인이에요” + 다시 시도) attached to the header’s bottom edge in the header’s own layer · over a photo or a map it is solid (gray100 fill, ink text), never translucent, and it pushes no content · on a plain page it is pinned under the header and does push
Library §19.5 Offline strip over a MediaBand “the strip attaches to the header’s bottom edge in the header’s own layer; over a photo or map it is solid (gray100 fill, ink text), never translucent, and it pushes no content” · §9 Offline strip “350 × 48 (min; grows with text per B6) … the size every board draws, corrected 2026-10-10”.
Anatomy
20370
9:41
오프라인이에요
다시 시도
35020
100 header
status 44 + 56, transparent
48 strip
350 wide · r12 · pad 12 16
Press
the 다시 시도 action (dim .85, no settle): dim 0.85, no settle · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Why solid
ink on a translucent fill over a photo cannot be guaranteed 4.5:1; gray100 + ink is 17:1 on any photo
Where
a photo or map band: over the band (this sheet); a plain page: pinned under the header, content below it pushed (the same strip); a page with no header: pinned under the status bar (entry family)
Retry
다시 시도 re-requests; while it runs a 20 spinner replaces the label (aria-busy) and the strip stays; success removes the strip
Spoken
announced when it appears: "오프라인이에요" (status, polite); the action: "다시 시도" (button); when the connection returns the strip leaves silently
Motion
appears fade 150 ms standard (0.2, 0, 0, 1); leaves with the Notice dismiss (fadeFast 100 ms standard then the height 150, standard); it never animates its position over a band
Reduced motion
it appears and leaves at once
Text scale
a floor of 48: text wraps and the strip grows; from 150% 다시 시도 goes under the text, left-aligned (Notice rule); over a band it grows downward over the photo, still pushing nothing
Used by
클럽 홈, 일정 상세 (map band), 테니스장 detail — any page with a MediaBand
Replaces
NetworkBanner (packages/app/src/presentation/components/network-banner.tsx), a translucent banner over the content
States
9:41
오프라인이에요
다시 시도
over a photo
9:41
오프라인이에요
다시 시도
over a map
9:41
오프라인이에요
다시 시도
over the default cover
9:41
오프라인이에요
다시 시도
action pressed dim .85
9:41
오프라인이에요
다시 시도
action focus
9:41
오프라인이에요
retrying a spinner replaces the label
올림픽공원 테니스장
오프라인이에요
다시 시도
김민준
NTRP 3.5
plain page pinned under the header; the content moves down
9:41
오프라인이에요
다시 시도
dark
Not drawn: hover (web, on the action), disabled, selected, loading (the strip is the loading fallback itself), error and empty.
Text size
9:41
오프라인이에요
다시 시도
130% text one line, larger
9:41
오프라인이에요
다시 시도
200% text the action under the text, the strip grows over the photo
Content under a MediaBandWhere the page begins once the photo or map ends.
the first element starts 24 below the band (the band’s own 240 including the status bar); a Notice in that slot is followed by the hero at the hero’s own 24 top · the club hero is the one exception: its logo hangs on the band (Club hero, place variant)
Library §19.5 Content under a MediaBand “the first element starts 24 below the band. A Notice in that slot is followed by the hero at the hero’s own 24 top.”
Anatomy
20370
9:41
참석6
김민준
NTRP 3.5
240 MediaBand
status bar included
24
24 SectionHead
8
68 PersonRow
Notice slot
a Notice (offline, role, archived) goes in the 24 slot; the hero or first section then starts 24 below the Notice (its own top), so two 24s separate the three
Exception
the club hero hangs its logo on the band and starts its name 24 below the band beside it (the overlap is the point)
Spoken
reading order: back, the (hidden) header title, the first element; the band image is decorative
Motion
none of its own: the band scrolls away with the page (the header turns white past 140)
Reduced motion
unchanged
Text scale
the 24 is a spacing value and does not scale; the band does not scale
Used by
일정 상세 (map band), 클럽 미리보기 / 클럽 홈 (photo band), 테니스장 detail
Replaces
the 16 / 20 / 32 gaps under the band that each screen chose for itself
Variants
9:41
참석6
a section first band → 24 → SectionHead
9:41
강남 라켓클럽
토요일 올림픽공원 복식
9월 19일 (토) 09:00–11:00
a hero first band → 24 → hero (eyebrow starts)
9:41
이 일정은 취소됐어요
강남 라켓클럽
토요일 올림픽공원 복식
9월 19일 (토) 09:00–11:00
a Notice first band → 24 → Notice → 24 → hero
9:41
참석6
map band the same 24
9:41
loading the skeleton keeps the same 24
9:41
참석6
dark
Not drawn: every interactive state (the band and the content have their own sheets), selected, error and empty.
Field skeletonThe placeholder of a form field while its value loads.
a 13/18 label bar at 30% width (a 12 bar in the 18 line), then 8, then a 48 gray100 r12 box · 74 per field, 32 between fields (the wizard seam) · one shimmer per screen (§9): every bar of the page pulses together, .6 ↔ .9 over 800 ms
Library §21.2 Field skeleton “a 13/18 label bar at 30% width, then 8, then a 48 gray100 r12 box; one shimmer per screen per §9”. §9 Skeleton “gray100, pulse .6 ↔ .9”.
Anatomy
20370
30% of 350
18 label · bar 12
8
48 box · r12
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
One shimmer
one pulse per screen, in phase: every skeleton on the page (header title, fields, rows) shares the same opacity at the same moment, so the page breathes once rather than flickering
Where
edit forms whose values come from the server (the 클럽 설정 pages, the edit Sheet while it loads); new-create wizards start with empty fields and need none
Spoken
the form region is aria-busy with "불러오는 중"; the skeleton shapes are hidden; when the data lands the fields replace them in place (150 ms fade) and focus is not moved
Motion
shimmer .6 ↔ .9, 800 ms half-cycle; the swap to the real field fade 150 ms standard (0.2, 0, 0, 1); shown only after 400 ms of loading and held ≥ 400 ms once shown (§9) so a fast load never flashes
Reduced motion
the skeleton pins .75 (no loop)
Text scale
the bars are shapes, not text: the label bar and the 48 box keep their size; the real field below them (floors) may be taller at large text, which the swap absorbs by growing
Used by
클럽 설정 / 프로필 편집 / 정기 일정 edit forms, the edit Sheet when its value is fetched
Replaces
the generic Skeleton blocks stacked by hand in settings screens (packages/ui/src/skeleton.tsx, skeleton-flat-page.tsx)
States
one field · pulse .6
one field · pulse .9 the other end of the half-cycle
reduced motion pinned .75, no loop
a form page header title bar + three fields at the 32 seam, one shared phase
클럽 이름
강남 라켓클럽
2–20자
swap to the field fade 150 ms to the real Field, same top
dark
Not drawn: every interactive state (a skeleton is inert), selected, error (the form’s retry Notice replaces it) and empty.
9Formsthe wizard header, the required marker, the counter, the time wheel, the map face, photo tiles, search and the one-action dialog
WizardShell headerWhere you are in a multi-step flow, the way back, and the way to skip: all three are buttons.
left: a 40 × 40 r12 gray100 square with a 20 ink chevron (an × on step 1, with the exit Dialog) in a 44 target · right: 건너뛰기 as an sm tonal Button (36, slop to 44), never ghost or plain text · the progress on the header’s bottom edge: 2 tall, ink for done + current, #E5E5E5 for the steps ahead · header 56 at every scale · the footer holds the single CTA band and its helper line only
Library §21.2 WizardShell header (owner LAW, 2026-08-15: “top controls must look like buttons, not bare glyphs”) “… Centre: the progress, in ink … Hardware back mirrors the header back (useWizardBackHandler).” §21.1 “건너뛰기 drawn as ghost text: against the owner’s wizard-navigation LAW”.
Anatomy
20370
건너뛰기
40sm 36 · hit 44
44 target
square 40 r12 · chevron 20
Press
the back square and 건너뛰기 (button .97 / .92): scale 0.97 · opacity 0.92 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Order
back at the rail on the left, 건너뛰기 at the rail on the right (only on skippable steps); neither is a bare glyph or ghost text
Hardware back
mirrors the header back (useWizardBackHandler): a step back, or the exit Dialog on step 1
Footer
one CTA band and its helper line; the helper names what is missing; no second button in the footer (a pair only on the confirm step)
Spoken
back: "뒤로" (step 1: "닫기" which opens the exit Dialog); skip: "건너뛰기"; the progress: "6단계 중 3단계" (progressbar, value 3 of 6); the title below is the h1
Motion
the progress changes colour by step in 150 ms (fade) and carries no loop; the header never moves; press: spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in / 150 ms out
Reduced motion
the cross-fade becomes instant
Text scale
header 56 at every scale (chrome class): 건너뛰기 stops at the 1.3 cap; the back square and the 44 target stay; the progress stays 2
Used by
일정 만들기, 클럽 만들기, 온보딩 and every WizardShell flow
Replaces
WizardShell’s card-grammar header (packages/ui/src/wizard-shell.tsx) and the ghost-text skip of the controls board
States
건너뛰기
step 3 of 6
step 1 · exit × with the exit Dialog
last step no skip; progress full
건너뛰기
back pressed button .97 / .92 on the square
건너뛰기
skip pressed
건너뛰기
back hover
건너뛰기
back focus web ring, offset 2
건너뛰기
skip focus
건너뛰기
dark
Not drawn: disabled (the header’s controls are always available; while a step saves the CTA carries the busy state), loading, selected, error and empty.
Page · footer
건너뛰기
클럽 이름을 정해주세요
클럽 이름
클럽 이름을 입력하세요
2–20자
클럽 이름을 입력해주세요
다음
step with a required field unset: 다음 disabled, the helper names what is missing
건너뛰기
클럽 이름을 정해주세요
클럽 이름
강남 라켓클럽
2–20자
다음
answered 다음 enabled, no helper
Text size
건너뛰기
130% text the label at its 1.3 cap
건너뛰기
200% text still 1.3: the chrome cap; the header stays 56
Required markerThe one sign that a field must be answered: an asterisk on its label, and a field that starts empty.
the one `LabeledField required` asterisk: $error, the size of the label (it sits inside the label’s own text, 13/18·600 on a Field, 15/22 on a Cell) · the control starts genuinely unset (no default value, an empty Choice set) · the CTA helper names what is missing · an optional field has no marker and no “(선택)” suffix
Library §21.1 “Required fields drawn without the asterisk: against the owner’s required-marker LAW (2026-08-15, canon wizards.md): every required control’s label carries the one LabeledField required asterisk ($error, size-matched), and the field starts genuinely unset. This applies to the step path and to unset required Cells on the review page.” §10 Field.
Anatomy
20370
클럽 이름
클럽 이름을 입력하세요
2–20자
label
18 label
13/18·600 + asterisk
48 control
unset: placeholder
18 helper
Colour
$error #B8332E on white (5.7:1, dark #FA9B94 on #0F1211 9.6:1): enough for text of 13 and 15; the asterisk is also named in the spoken label, so colour is not its only carrier
Starts unset
a required control has no default: the text field is empty with its placeholder, the Choice set has no chip chosen, an edit Cell shows “선택해주세요” in secondary; a default would answer for the user
CTA
the screen’s CTA stays disabled (.5) until every required control is answered, and its helper line names what is missing (“장소, 날짜를 입력해주세요”); a failed tap marks the Field’s danger border and helper
Optional
no marker, no “(선택)”: optional is the default; the marker is reserved for required (one wording per concept)
Spoken
the field: "클럽 이름, 필수, 입력란" (aria-required; the asterisk glyph is hidden from the tree and replaced by the word “필수”); an unset required Cell: "장소, 필수, 선택 안 됨, 수정"
Motion
none: the change is instant
Reduced motion
unchanged
Text scale
the asterisk is part of the label text node: it scales with the label (reading class) and never wraps alone
Used by
every wizard step and edit form Field, the unset Cells of the 일정 / 클럽 review pages
Replaces
LabeledField without the marker (packages/ui/src/labeled-field.tsx) and the hint-only required cues of the wizards
States
클럽 이름
클럽 이름을 입력하세요
2–20자
unset asterisk, placeholder
클럽 이름
강남
2–20자
focus
클럽 이름
클럽 이름을 입력하세요
클럽 이름을 입력해주세요
left empty after a failed attempt: danger border + helper
클럽 이름
강남 라켓클럽
2–20자
answered the asterisk stays: it marks the field, not its state
한 줄 소개
클럽을 한 줄로 소개해주세요
optional no marker, no suffix
주로 치는 종목
복식단식혼합 복식
Choice set, unset
장소
선택해주세요
종목
복식
장소, 날짜를 입력해주세요
일정 만들기
review page two required Cells unset; the helper names them
클럽 이름
클럽 이름을 입력하세요
2–20자
dark
Not drawn: pressed and hover (a label is text), disabled and loading (the control’s own states, controls board), selected and empty (the unset frame is the empty).
Text size
클럽 이름
클럽 이름을 입력하세요
2–20자
130% text the asterisk grows with the label
클럽 이름
클럽 이름을 입력하세요
2–20자
200% text label and helper wrap
Input counterHow much of a limited field is used.
“12/20” 12/16·500 secondary, right-aligned on the helper line under the field · at the limit it turns danger and further typing is refused · spoken only at 80% and at the limit
Library §21.2 Input counter “‘12/20’ 12/16·500 secondary, right-aligned on the helper line under the field. At the limit it turns danger and further typing is refused. Spoken only at 80% and at the limit.” §7 TextArea keeps its counter inside the pad (13/18, bottom-right).
Anatomy
20370
클럽 이름
강남 라켓클럽 올림픽
2–20자12/20
counter 12/16
18 label
48 control
18 helper line
helper left · counter right
Limit
at the limit the counter turns danger, the field border stays rest (typing is refused, not failed), and the helper may say why (“20자까지 쓸 수 있어요”); a pasted overflow is cut to the limit
Where
a single-line Input shows the counter on the helper line; a TextArea keeps its own counter inside the pad (13/18, bottom-right): two grammars for two shapes of field
Spoken
silent while typing; announced "16자 중 20자" style at 80% ("16 / 20") and "20자, 최대" at the limit (live region polite); not repeated on each key
Motion
none: the change is instant
Reduced motion
unchanged
Text scale
the counter is a caption (reading class): it wraps under the helper from 150% (right-aligned) and never overlaps the helper text
Used by
클럽 이름 (20), 한 줄 소개 (40), 닉네임, 일정 메모 fields with a limit
Replaces
the hint text “2–20자” that did not say how much was used (packages/ui/src/labeled-field.tsx helper)
States
클럽 이름
클럽 이름을 입력하세요
2–20자0/20
empty · 0/20
클럽 이름
강남 라켓클럽 올림픽
2–20자12/20
typing · 12/20
클럽 이름
강남 라켓클럽 올림픽 복식 모
2–20자16/20
80% · 16/20 announced once
클럽 이름
강남 라켓클럽 올림픽 복식 모임 모집
20자까지 쓸 수 있어요20/20
at the limit · 20/20 the counter turns danger; typing is refused
클럽 이름
강남 라켓클럽 올림픽 복식 모임 모집
20자까지 쓸 수 있어요20/20
dark · limit
Not drawn: pressed, hover (the field is not a Pressable), disabled (.5 on the whole Field), selected, loading and empty (0/20 is the empty frame).
Text size
클럽 이름
강남 라켓클럽 올림픽
2–20자12/20
130% text helper and counter side by side
클럽 이름
강남 라켓클럽 올림픽
2–20자12/20
200% text the counter under the helper
Caption row · two linesThe explanation under a form section when one line is not enough.
the Caption row (13/18·500 secondary at the rail, min 32) may wrap to two lines: min 32, grows to 36 · past two lines the copy is too long and is rewritten, never clamped
Library §21.2 Caption row, two lines “the §18.2 Caption row may wrap to two lines (min 32, grows); past two lines the copy is too long and is rewritten, never clamped.”
Anatomy
20370
정기 일정
정기 일정은 매주 같은 시간에 자동으로 만들어져요. 시작 전날 멤버에게 알림이 가고, 일정마다 참
한 줄짜리 설명
24 section head
36 two lines
2 × 18 (a floor of 32)
32 one line
Never clamped
two lines at 100% is the limit of the copy; the board draws the two-line caption and the over-long one flagged as a copy defect (rewrite), not truncated with an ellipsis
Spoken
read as text after its section head; never a stop
Motion
none: the change is instant
Reduced motion
unchanged
Text scale
reading class: the lines wrap and the row grows; at 200% two lines become four, which is the cue that the copy is too long
Used by
form section heads (정기 일정, 참가비, 공개 범위), the 프라이버시 page
Replaces
free 13/18 helper Text under SectionHeader in each form
States
정기 일정은 매주 같은 시간에 자동으로 만들어져요. 시작 전날 멤버에게 알림이 가고, 일정마다 참
two lines 36
정기 일정은 매주 같은 시간에 자동으로 만들어져요. 시작 전날 멤버에게 알림이 가고, 일정마다 참가비도 같이 이어져요.
over two lines a copy defect: rewrite it (never clamp)
정기 일정은 매주 같은 시간에 자동으로 만들어져요. 시작 전날 멤버에게 알림이 가고, 일정마다 참
dark
Not drawn: every interactive state, loading, selected, error and empty (static text).
Text size
정기 일정은 매주 같은 시간에 자동으로 만들어져요. 시작 전날 멤버에게 알림이 가고, 일정마다 참
130% text two lines still
정기 일정은 매주 같은 시간에 자동으로 만들어져요. 시작 전날 멤버에게 알림이 가고, 일정마다 참
200% text four lines: the cue to shorten
Time range wheelPick a start and an end time in one Sheet, with one wheel.
a Sheet titled with the field name · Tabs 시작 · 종료 show their current values (52 tall, the Tabs grammar) · one native time wheel (오전/오후 · 시 · 분) edits the selected tab · 완료 commits both · an end before the start shows the danger helper “종료가 시작보다 빨라요” and disables 완료
Library §21.2 Time range wheel “a Sheet titled with the field name. Tabs 시작 · 종료 show their current values, and one native time wheel edits the selected tab. 완료 commits both. An end before the start shows the danger helper ‘종료가 시작보다 빨라요’ and disables 완료.” (the wheel is the platform control; the board draws a stand-in).
Anatomy
20370
시간
시작오전 9:00
종료오전 11:00
오전
오후
7
8
9
10
11
58
59
00
01
02
완료
20 pad
24 title row
16
52 Tabs
시작 · 종료 + value
26 helper line
4 + 22 (kept so the wheel does not jump)
180 wheel
5 rows × 36
118 band
Press
a tab (dim .85); the 완료 Button keeps .97 / .92: dim 0.85, no settle · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Commit
완료 commits both values; backdrop / back dismiss without saving (no guard: the sheet has nothing the user typed)
Validation
moving the end before the start shows the helper and disables 완료 at once; the end tab value turns danger; the wheel keeps its position
Step
the wheel’s minute step belongs to the field (a 5-minute step for sessions is proposed: the library does not say)
Spoken
a dialog "시간"; tabs: "시작, 오전 9시, 탭 1 / 2, 선택됨"; the wheel is the platform picker ("오전 9시 0분, 조절 가능"); the helper is announced when it appears; 완료 "완료, 사용할 수 없음" while the range is invalid
Motion
sheet rise 300 ms enter · exit 200 ms exit (0.3, 0, 1, 1); the tab underline slides on the navSlide spring (311 ms); the wheel is native and unanimated by the app
Reduced motion
the underline jumps; the sheet appears with the platform fade
Text scale
tabs are chrome class (cap 1.3): from 130% the value sits under its label; the wheel is the system’s and follows the OS size; 완료 grows (floor 52)
Used by
일정 만들기 (시간), 정기 일정, 코트 이용 시간 fields
Replaces
TimeBandField / TimeBandSlider as the form control (packages/ui/src/time-band-field.tsx) and the stacked pair of date-time pickers
States
9:41
올림픽공원 복식
관리
시간
시작오전 9:00
종료오전 11:00
오전
오후
7
8
9
10
11
58
59
00
01
02
완료
시작 selected
9:41
올림픽공원 복식
관리
시간
시작오전 9:00
종료오전 11:00
오전
오후
9
10
11
12
1
58
59
00
01
02
완료
종료 selected
9:41
올림픽공원 복식
관리
시간
시작오전 9:00
종료오전 8:00
종료가 시작보다 빨라요
오전
오후
6
7
8
9
10
58
59
00
01
02
완료
end before start danger helper, 완료 disabled, the end value in danger
9:41
올림픽공원 복식
관리
시간
시작오전 9:00
종료오전 11:00
오전
오후
7
8
9
10
11
58
59
00
01
02
완료
tab pressed dim .85
9:41
올림픽공원 복식
관리
시간
시작오전 9:00
종료오전 11:00
오전
오후
7
8
9
10
11
58
59
00
01
02
완료
완료 pressed
9:41
올림픽공원 복식
관리
시간
시작오전 9:00
종료오전 11:00
오전
오후
7
8
9
10
11
58
59
00
01
02
완료
dark
Not drawn: hover (web, on the tabs and the Button), focus (rings per control), loading (the device wheel is instant), selected (the active tab), empty and error beyond the range check.
Text size
9:41
올림픽공원 복식
관리
시간
시작오전 9:00
종료오전 11:00
오전
오후
7
8
9
10
11
58
59
00
01
02
완료
130% text the cap: tabs at 130%
9:41
올림픽공원 복식
관리
시간
시작오전 9:00
종료오전 11:00
오전
오후
7
8
9
10
11
58
59
00
01
02
완료
200% text still 130%: the chrome cap; the wheel follows the OS
Map face in a formThe picked venue, shown on a map under its Cell.
after a venue is picked: a 350 × 140 r12 static map with the 24 ink pin, a 1px inset #E5E5E5, 8 under the 장소 Cell · a tap reopens the Search takeover · loading gray100 · error = the court tile
Library §21.2 Map face in a form “after a venue is picked: a 350 × 140 r12 static map with the 24 ink pin, a 1px inset #E5E5E5, 8 under the 장소 Cell; a tap reopens the Search takeover; loading gray100; error = the court tile”.
Anatomy
20370
테니스장
올림픽공원 테니스장
350
48 장소 Cell
value + pencil
8
140 map face
350 × 140 · r12 · inset 1px
Press
the map face (dim .85 on the image, no settle; its radius clips the dim): dim 0.85, no settle · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Source
the static map of the venue (the same tile as the venue detail: storage-direct, no per-view map call); the pin is the 24 ink pin at the venue’s coordinates, centred
Error
the court tile of the venue’s surface (hard / clay / grass) at 350 × 140, no pin (a venue without coordinates draws no map, only the tile)
Locked
after the session starts the venue Cell is locked (reason line + lock) and the map face is not drawn
Spoken
a button: "장소 지도, 올림픽공원 테니스장, 눌러서 장소 바꾸기"; the map art and the pin are hidden
Motion
appears fade 150 ms standard (0.2, 0, 0, 1) after the venue is picked; a tap opens the Search takeover (rise 300 ms)
Reduced motion
the cross-fade becomes instant
Text scale
an image: it does not scale; the Cell above it wraps and grows, the 8 stays
Used by
일정 만들기 review (장소), 클럽 만들기 (모임 장소), 정기 일정 edit
Replaces
the venue Cell with its text address only, and the inline VenueLocationPicker map (packages/ui/src/venue-location-picker.tsx) in forms
States
테니스장
올림픽공원 테니스장
rest
테니스장
올림픽공원 테니스장
pressed dim .85
테니스장
올림픽공원 테니스장
hover
테니스장
올림픽공원 테니스장
focus web: the ring outside, offset 2
테니스장
올림픽공원 테니스장
loading gray100 350 × 140
테니스장
올림픽공원 테니스장
error the venue’s court tile
테니스장
올림픽공원 테니스장
dark the map tile stays light (an image)
Not drawn: disabled (locked: the face is not drawn), selected, empty (no venue picked: only the Cell with its asterisk and placeholder).
Photo tiles (composer)The photos attached to a post being written: see them, remove one, add more, put them in order.
4-up wrapping rows, gap 8 · tile = (350 − 3 × 8) / 4 = 81.5, computed (a listed exemption), r8 · remove = a 24 ink α0.6 circle with a white 16 × at the top-right, inset 4, in a 44 target · the add tile is gray100 with a 24 + and the count “3/10” · long-press drag reorders, with an accessible move action
Library §21.2 Photo tiles (composer) “4-up wrapping rows, tile = (350 − 3 × 8) / 4 (computed, a listed exemption), r8. Remove = a 24 ink α0.6 circle with a white 16 × at top-right inset 4, in a 44 target. The add tile is gray100 with a 24 + and the count ‘3/10’. Long-press drag reorders, with an accessible move action.”
Anatomy
20370
3/10
81.58add tile
81.5 tile · r8
computed from 350
Press
the remove button (icon .92 / .78); the add tile presses as a row (.98 / .80): scale 0.92 · opacity 0.78 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Remove target
the 44 target is centred on the 24 circle and overlaps the tile’s corner and its neighbours: a tap inside the circle’s 44 removes, elsewhere on the tile opens the preview
Add tile
hidden at the limit (10/10); disabled .5 while an upload is the only free slot is not used; its count “3/10” is the only count on screen
Upload
an uploading tile dims .6 with a white spinner; a failed tile shows a danger glyph and “다시 올리기” and stays removable; the composer’s send is disabled while any tile uploads
Spoken
a list "사진 3장"; each tile "사진 1 / 3" with the actions "삭제" and "앞으로 이동 / 뒤로 이동" (the accessible move action replaces the drag); the add tile: "사진 추가, 3 / 10"
Motion
a lifted tile scales 1.05 with .9 opacity while dragged and the others slide aside (200 ms toggle, no layout animation in a virtualised list: this is a short stack ≤ 10); a removed tile fades 100 ms and the rest snap up
Reduced motion
no lift scale and no slide: the tile jumps to its slot
Text scale
photos and the 24 / 44 targets do not scale; the count text grows (caption, cap 1.5) inside the add tile (tile stays 81.5: a longer count wraps below the glyph)
Used by
게시글 쓰기 (photos), 사진 올리기, DM attachments
Replaces
the composer’s horizontal thumbnail strip with a text × (the post composer and DM compose bar: packages/features/messaging/src/dm-thread/compose-bar.tsx)
States
3/10
three photos
0/10
empty only the add tile: 0/10
6/10
six photos wrap the add tile follows on the second row
limit 10/10: no add tile
3/10
remove pressed icon .92 / .78
3/10
add pressed row .98 / .80
3/10
add focus
4/10
dragging long-press: the tile lifts (1.05, .9)
3/10
uploading dim + spinner
다시 올리기
3/10
upload failed danger glyph + 다시 올리기
3/10
dark
Not drawn: hover (web: .85 on the remove button and the add tile), disabled and selected (a tile is not selectable), and a loading skeleton (tiles are local files).
Search takeoverThe one place a venue (or any pick from a long list) is searched: a full screen with the keyboard up.
full screen, rising 300 ms with the keyboard up · the bar is a Search field (40 r12 gray100, 15/22) filling the header with 취소 as the text action at the right · below: recent searches as Text rows with a × trailing control, then results as MediaRows · empty = the filtered-empty EmptyState plus the contribution row (“찾는 곳이 없나요?”)
Library §21.2 Search takeover “… It is the one venue-pick surface (VenueFinder canon).” §21.1 A1 “장소 opens the full-screen Search takeover, per the VenueFinder canon (IA §4.7)”. §7 Search is the 44 pill; the takeover field is the header form (40 r12): one Search grammar, two placements. §23 ruling 3: the 40 r12 field is drawn inside a 44 target (the hit area stays 44, the visual follows the grammar’s radii).
Anatomy
20370
9:41
테니스장 이름이나 동네
취소
최근 검색
올림픽공원
9월 17일
한강 테니스
9월 17일
강남구
9월 17일
44 status
56 header
44 target around the 40 r12 field + 취소
8
24 section head
68 Text row
× 20 in a 44 target
more rows
Press
a recent row / a result row (.98 / .80); the × is its own control (icon .92 / .78); 취소 is a text action (button): scale 0.98 · opacity 0.80 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Order
empty query: recent searches (Text rows with × trailing controls); typing: results as MediaRows (venue thumb, name, region · surface); none: filtered-empty + the contribution row
Contribution
“찾는 곳이 없나요?” is a Cell that opens the venue-add flow (a library gap-free composition: Task Cell grammar)
Pick
tapping a result picks the venue, closes the takeover and shows the map face under the 장소 Cell; one venue-pick surface for every form
Spoken
a dialog "장소 검색"; the field: "검색, 입력란"; results: "올림픽공원 테니스장, 송파구, 하드 8면"; the × on a recent: "올림픽공원 검색어 삭제"; when results change "결과 3개" (polite)
Motion
sheet rise 300 ms enter · exit 200 ms exit (0.3, 0, 1, 1) (rise 300 ms with the keyboard); results replace recents instantly (fade 150 ms standard (0.2, 0, 0, 1) skeleton → rows)
Reduced motion
the takeover appears with the platform fade; the keyboard still opens with it
Text scale
the field and rows are reading class: the field grows (floor 40), recents and results wrap; the header stays 56 (the field is in it, 취소 at the chrome 1.3 cap)
Used by
일정 만들기 / 클럽 만들기 (장소), 테니스장 찾기, the venue pick in edit Sheets
Replaces
VenueSearchTakeover (packages/app/src/presentation/components/venue-search-takeover.tsx) with its header-less field and the inline venue picker panels
Full frames
9:41
테니스장 이름이나 동네
취소
최근 검색
올림픽공원
9월 17일
한강 테니스
9월 17일
강남구
9월 17일
opened the keyboard up, recents with ×
9:41
올림픽
취소
올림픽공원 테니스장
송파구하드 8면
반포 한강 테니스장
서초구클레이 4면
양재 시민의숲 테니스장
서초구하드 6면
typing · results MediaRows 80
9:41
올림퍽
취소
찾는 결과가 없어요다른 이름이나 동네로 찾아보세요
찾는 곳이 없나요?
새 테니스장 알리기
no results filtered-empty + the contribution row
9:41
올림
취소
searching skeleton rows
9:41
올림픽
취소
오프라인이에요
다시 시도
offline the Offline strip under the field
9:41
올림픽
취소
올림픽공원 테니스장
송파구하드 8면
반포 한강 테니스장
서초구클레이 4면
양재 시민의숲 테니스장
서초구하드 6면
dark
Not drawn: hover (web .85 on rows), focus (rings), disabled, selected (a pick closes the takeover) and error beyond the offline frame.
Dialog · one actionAn acknowledgement that blocks: the user must read it, and there is only one way on.
the Dialog (350, r16, pad 20, title 17/24·600, body 15/22 secondary) with one full-width primary 확인 (52) · the backdrop does not dismiss it · never used where a Toast would do
Library §21.2 Dialog, one action “for an acknowledgement that blocks (a version gate, a rule the user must read): title + body + one full-width primary 확인. The backdrop does not dismiss it. Never used where a Toast would do.” §8 Dialog (the pair form).
Anatomy
20370
앱을 업데이트해주세요새 버전에서 일정 만들기 방법이 바뀌었어요. 업데이트한 뒤에 이어서 쓸 수 있어요.
확인
20 pad
24 title · 17/24·600
8
44 body · 2 × 22
20
52 button · lg
20 pad
Press
the 확인 Button (.97 / .92): scale 0.97 · opacity 0.92 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Blocking
the backdrop and hardware back do not dismiss it: the only exit is 확인; use it only when the user must act on the information (a version gate, a rule); a success or an FYI is a Toast
Pair form
a Dialog with a choice keeps its pair (취소 tonal + the action, danger for destructive): this is the one-action sibling
Spoken
an alert dialog: the title and body are read, focus lands on 확인 ("확인, 버튼"); the backdrop is inert; nothing else is reachable behind it
Motion
appears fade 150 ms standard (0.2, 0, 0, 1) (the ModalPanel fade), leaves exit 100 ms standard
Reduced motion
the same fade (it is an opacity change, instant under reduced motion)
Text scale
reading class: title and body wrap and the dialog grows (it scrolls past the screen height); the 확인 Button grows (floor 52)
Used by
the version gate and rule acknowledgements; never for a success or an FYI (a Toast)
Replaces
Alert.alert with one button and ConfirmSheet used as an acknowledgement (packages/ui/src/confirm-sheet.tsx)
States
9:41
올림픽공원 복식
관리
앱을 업데이트해주세요새 버전에서 일정 만들기 방법이 바뀌었어요. 업데이트한 뒤에 이어서 쓸 수 있어요.
확인
rest
9:41
올림픽공원 복식
관리
앱을 업데이트해주세요새 버전에서 일정 만들기 방법이 바뀌었어요. 업데이트한 뒤에 이어서 쓸 수 있어요.
확인
pressed button .97 / .92
9:41
올림픽공원 복식
관리
앱을 업데이트해주세요새 버전에서 일정 만들기 방법이 바뀌었어요. 업데이트한 뒤에 이어서 쓸 수 있어요.
확인
focus the default focus, web ring
9:41
올림픽공원 복식
관리
앱을 업데이트해주세요새 버전에서 일정 만들기 방법이 바뀌었어요. 업데이트한 뒤에 이어서 쓸 수 있어요.
확인
busy spinner on 확인 (a gate that checks the version)
9:41
올림픽공원 복식
관리
이용 규칙을 확인해주세요클럽에서 정한 규칙이 바뀌었어요. 참석 신청 후 24시간 안에 취소하지 않으면 참가비가 환불되지 않고, 예비로 밀려난 분께 자리가 자동으로 넘어가요.
확인
long body wraps; the dialog grows
9:41
올림픽공원 복식
관리
앱을 업데이트해주세요새 버전에서 일정 만들기 방법이 바뀌었어요. 업데이트한 뒤에 이어서 쓸 수 있어요.
확인
dark
Not drawn: hover (web), disabled (the only action is never disabled), selected, error and empty.
Text size
9:41
올림픽공원 복식
관리
앱을 업데이트해주세요새 버전에서 일정 만들기 방법이 바뀌었어요. 업데이트한 뒤에 이어서 쓸 수 있어요.
확인
130% text the dialog grows
9:41
올림픽공원 복식
관리
앱을 업데이트해주세요새 버전에서 일정 만들기 방법이 바뀌었어요. 업데이트한 뒤에 이어서 쓸 수 있어요.
확인
200% text the body wraps to many lines
10Entry and legalKakao, the entry band, the lockup slot, consent, busy, the document sheet, the gate and the install row
ProviderButton · KakaoThe Kakao sign-in button, drawn exactly as Kakao requires, inside our geometry.
the lg Button geometry (52, full width on the rail) with Kakao’s mandated values: container #FEE500, the speech-bubble symbol #000000 (centred with the label or left-aligned), label #000000 α0.85 in the system font, radius 6 (not our 12), label “카카오 로그인” / “Login with Kakao” · pressed dims .85, no settle · the same in dark mode · the screen’s one coloured action (a brand-mandated exception to S2)
Library §22.2 ProviderButton (Kakao) “… At large text the label stops at the 1.5 cap and the container grows so the label stays within a third of its height. It is the same in dark mode … Source: developers.kakao.com Kakao Login design guide.” §22.1 “Kakao label in English: ‘Login with Kakao’, Kakao’s mandated English label, which overrides our ‘Sign in’ wording on that one button.”
Anatomy
20370
카카오 로그인
350 · full width on the rail
52 button
r6 · #FEE500
Press
the whole button (dim .85, no settle: Kakao’s proportions are fixed): dim 0.85, no settle · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Mandated
container #FEE500, symbol #000000, label #000000 α0.85, radius 6: none of these follow our tokens; they are fixed by Kakao’s guide and do not change in dark mode
Label font
the platform system font (not Pretendard): Apple SD Gothic Neo on iOS, Noto Sans CJK / Roboto on Android, system-ui on the web
Symbol
Kakao’s official symbol asset (the board draws a stand-in bubble); left-aligned with the label centred in the rest, or centred with the label: one choice per product (drawn: centred)
Contrast
α0.85 black on #FEE500 is 14:1 (Kakao’s own pairing); the 3:1 non-text rule is met by the yellow against white (1.3:1 is Kakao’s, accepted as the brand-mandated exception)
Spoken
"카카오 로그인" (button); busy: "카카오 로그인, 처리 중"; the symbol is hidden
Motion
press: the dim only, fadeFast timing 80 ms in / 150 ms out; no spring
Reduced motion
unchanged (the dim stays; there is no settle to drop)
Text scale
label class capped at 1.5: the label font grows to 22.5 at most and the container grows with it so the label stays within a third of the height (52 → 60 at 130%, 68 from 150%); the symbol stays 20
Used by
A1 entry (시작), the web /login, 계정 연결 in 설정
Replaces
the green ActionButton with a Kakao text label in login-screen.tsx (packages/features/auth), which did not follow Kakao’s design guide
States
카카오 로그인
rest
카카오 로그인
pressed dim .85, no settle
카카오 로그인
hover
카카오 로그인
focus
카카오 로그인
disabled
loading a black spinner, width held
카카오 로그인
left-aligned symbol the other mandated layout
Login with Kakao
English “Login with Kakao”
카카오 로그인을 취소했어요
카카오 로그인
cancelled / failed a 13/18 danger helper above, not a Notice (§22.1)
카카오 로그인
dark identical to light
Not drawn: selected and empty (a command), error beyond the helper line (the failure copy is the helper).
Text size
카카오 로그인
130% text 60: the label within a third of the height
카카오 로그인
200% text the 1.5 cap: 68
CTA band · entry, and the danger helperThe foot of the sign-in screen: the provider, the email way in, and the two documents.
the ProviderButton (52) · 12 · a ghost md Button (이메일로 로그인, 44) · 12 · a 13/18 secondary links row (이용약관 · 개인정보 처리방침 as ghost links that open the document Sheet) · plus the home inset · no hairline · 16 on top · used only on A1 and the web /login · the danger helper (13/18, one line) sits above the provider button for a failure that belongs to the action
Library §22.2 CTA band, entry · CTA band helper, danger “the §14 helper line in danger, for a failure that belongs to the action (provider failed, cancelled). One line, never a second action.” §22.1 “Provider cancelled: a 13/18 danger helper line above the provider button, not a danger Notice. A user’s own cancel is not an alarm.”
Anatomy
20370
카카오 로그인
이메일로 로그인
이용약관
개인정보 처리방침
16
52 provider button
12
44 ghost md
12
18 links
16
34 inset
Press
each control keeps its own physics: the provider dims .85, the ghost Button .97 / .92, a link dims .85: scale 0.97 · opacity 0.92 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Helper
one line in danger above the provider button for a failure of the action (cancelled, failed); it clears on the next attempt; never a second action and never a Notice (a user’s own cancel is not an alarm)
Links
two text links open the document Sheet (이용약관, 개인정보 처리방침) without leaving the screen; they are ghost links (13/18 secondary, underlined), 44 targets by slop
One colour
the Kakao yellow is the screen’s one coloured action; the email Button is ghost (no fill) so nothing competes with it
Spoken
three stops in order: "카카오 로그인", "이메일로 로그인", then the links "이용약관, 링크" and "개인정보 처리방침, 링크"; the danger helper is read first when present ("카카오 로그인을 취소했어요")
Motion
press: spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in / 150 ms out; fade 150 ms standard (0.2, 0, 0, 1) for skeleton → content
Reduced motion
the settle is dropped, the dim stays
Text scale
label class: buttons grow (the provider with the 1.5 cap rule); the links row wraps to two lines, still centred; the band grows upward and the screen scrolls
Used by
A1 시작 (the app), the web /login
Replaces
the stacked buttons and the legal footer text of login-screen.tsx (packages/features/auth)
States
9:41
MARK
DABUSI
다붓이 모여, 함께 치는 테니스
카카오 로그인
이메일로 로그인
이용약관
개인정보 처리방침
rest
9:41
MARK
DABUSI
다붓이 모여, 함께 치는 테니스
카카오 로그인을 취소했어요
카카오 로그인
이메일로 로그인
이용약관
개인정보 처리방침
provider cancelled a danger helper above the provider button
9:41
MARK
DABUSI
다붓이 모여, 함께 치는 테니스
로그인하지 못했어요. 다시 시도해주세요
카카오 로그인
이메일로 로그인
이용약관
개인정보 처리방침
provider failed
9:41
MARK
DABUSI
다붓이 모여, 함께 치는 테니스
이메일로 로그인
이용약관
개인정보 처리방침
signing in the provider spinner; the email Button inert
9:41
MARK
DABUSI
다붓이 모여, 함께 치는 테니스
카카오 로그인
이메일로 로그인
이용약관
개인정보 처리방침
email pressed button .97 / .92
9:41
MARK
DABUSI
다붓이 모여, 함께 치는 테니스
카카오 로그인
이메일로 로그인
이용약관
개인정보 처리방침
link pressed dim .85
9:41
MARK
DABUSI
다붓이 모여, 함께 치는 테니스
카카오 로그인
이메일로 로그인
이용약관
개인정보 처리방침
dark
Not drawn: hover and focus (per control, controls board), selected and empty; offline is the next sheet (the strip under the status bar).
Text size
9:41
MARK
DABUSI
다붓이 모여, 함께 치는 테니스
카카오 로그인
이메일로 로그인
이용약관
개인정보 처리방침
130% text the provider grows to 60
9:41
MARK
DABUSI
다붓이 모여, 함께 치는 테니스
카카오 로그인
이메일로 로그인
이용약관
개인정보 처리방침
200% text the band grows upward; links wrap
Brand lockup slotThe place the logo goes on the sign-in and gate screens: a labelled slot until the new mark exists.
the MARK slot of the brand touchpoints doc plus “DABUSI” in Pretendard Bold: stacked (the mark 2.5u over a 0.7u gap and the wordmark, 180 wide, u = the wordmark’s cap height) for A1 and the version gate, horizontal (the mark 1.5u = 28 high, a 0.6u gap, the wordmark) for the web pages · no mark is drawn until the owner’s new mark exists
Library §22.2 Brand lockup “the MARK slot from the brand touchpoints doc, stacked (mark over the wordmark, 180 wide) for A1 and the version gate, and horizontal (28 high) for the web pages. No mark is drawn until the owner’s new mark exists.” Brand doc §10 (u = cap height 0.707 em, wordmark 6.126u wide; clear space 1u).
Anatomy
MARK
DABUSI
stacked · 180 wide mark 73.5 · gap 20.6 · wordmark
MARK
DABUSI
horizontal · 28 high mark 28 · gap 11.2 · wordmark
Slot
the dashed MARK box is board chrome (never part of a component): the build leaves an empty box of the same size until the new mark exists, so the layout does not move when it arrives
Colour
wordmark ink on white, white on ink, ink on ball; the mark follows the colourway of its ground (brand doc §10)
Minimum
horizontal 96 wide, stacked 72 wide; this board uses 180 stacked and 28 high horizontal
Spoken
one image: "다붓이" with the app’s name as its alternative text (the web: an img with alt "Dabusi"); the MARK slot itself is hidden
Motion
none: the change is instant
Reduced motion
unchanged
Text scale
an image: it does not scale with text; the clear space around it stays 1u
Used by
A1 시작, the version gate (stacked); the web pages and share pages (horizontal)
Replaces
Logo (packages/ui/src/logo.tsx): the Georgia wordmark with ball glyphs, the only serif of the product, used once
Variants
MARK
DABUSI
stacked on white
MARK
DABUSI
horizontal on white a web page header
MARK
DABUSI
stacked · dark
MARK
DABUSI
horizontal · dark
Not drawn: every interactive and data state (the lockup is a static image).
Cell · consent, and 전체 동의Agree to the terms one by one, or all at once, and read each before agreeing.
a leading 24 checkbox (the Select-mode box), the label with its [필수] / [선택] prefix 15/22, a trailing sm ghost 보기 (its own focus stop, 44 by slop) that opens the document Sheet · the master row 전체 동의: 600 weight, ticks or clears every item and shows the mixed state when partly ticked · a 16 gap and a cell hairline separate it from the items · marketing sits last as its own group with the (광고) wording · nothing is pre-ticked
Library §22.2 Cell, consent “… Master row 전체 동의: 600 weight, ticks or clears every item, and shows the mixed state when partly ticked. A 16 gap and a cell hairline separate it from the items. Marketing sits last as its own group, with the (광고) wording. Nothing is pre-ticked.” §22.1 “Consent prefix: [필수] / [선택], the shipped strings (onboarding.ts)”; marketing push needs the (광고) prefix and active consent (canon notifications.md).
Anatomy
20370
전체 동의
[필수] 이용약관 동의
보기
[필수] 개인정보 수집·이용 동의
보기
[필수] 만 14세 이상입니다
보기
[선택] 마케팅 정보 수신 동의 (광고)
보기
2412보기 sm · hit 44
48 master
전체 동의 · 600
16
48 item
[필수] label + 보기
48 item
48 item
16
48 marketing
its own group
Press
the cell presses as a row (.98 / .80) and toggles; 보기 is a ghost Button (.97 / .92) of its own: scale 0.98 · opacity 0.80 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Master
ticking it ticks every item and the marketing choice; clearing it clears all; partly ticked it shows a minus (mixed); ticking the last item ticks the master
Marketing
always last, its own group (16 above), the [선택] prefix and “(광고)” after the label; it is never required to continue; (PIPA: no pre-ticked box, one consent per purpose)
Continue
the CTA stays disabled until every [필수] is ticked; its helper names what is missing (“이용약관 동의가 필요해요”); a failed tap shows the danger line under the group
Spoken
master: "전체 동의, 체크 박스, 일부 선택됨" (aria-checked mixed); item: "[필수] 이용약관 동의, 체크 박스, 선택 안 됨" then its own stop "이용약관 보기, 버튼"; marketing: "[선택] 마케팅 정보 수신 동의 (광고)"
Motion
the check appears instantly (no animation: the toggle is the feedback); the document Sheet rises sheet rise 300 ms enter · exit 200 ms exit (0.3, 0, 1, 1)
Reduced motion
unchanged
Text scale
reading class: labels wrap; from 150% 보기 goes under the label, left-aligned; the checkbox stays 24 aligned to the first line; every cell is a floor of 48
Used by
온보딩 약관 동의 (A1 flow), 설정 > 동의 이력 (changing a consent)
Replaces
the consent rows of onboarding-step-consent.tsx (packages/features/onboarding/src/onboarding-step-consent.tsx) and SelectionCheck lists (packages/ui/src/selection-check.tsx)
Checkbox states
off
on
mixed a minus in an ink square
States
전체 동의
[필수] 이용약관 동의
보기
[필수] 개인정보 수집·이용 동의
보기
[필수] 만 14세 이상입니다
보기
[선택] 마케팅 정보 수신 동의 (광고)
보기
nothing ticked the master is off
전체 동의
[필수] 이용약관 동의
보기
[필수] 개인정보 수집·이용 동의
보기
[필수] 만 14세 이상입니다
보기
[선택] 마케팅 정보 수신 동의 (광고)
보기
partly ticked the master shows mixed
전체 동의
[필수] 이용약관 동의
보기
[필수] 개인정보 수집·이용 동의
보기
[필수] 만 14세 이상입니다
보기
[선택] 마케팅 정보 수신 동의 (광고)
보기
all ticked the master is on, marketing included
전체 동의
[필수] 이용약관 동의
보기
[필수] 개인정보 수집·이용 동의
보기
[필수] 만 14세 이상입니다
보기
[선택] 마케팅 정보 수신 동의 (광고)
보기
required done marketing left off is fine
전체 동의
[필수] 이용약관 동의
보기
[필수] 개인정보 수집·이용 동의
보기
[필수] 만 14세 이상입니다
보기
[선택] 마케팅 정보 수신 동의 (광고)
보기
item pressed row .98 / .80
전체 동의
[필수] 이용약관 동의
보기
[필수] 개인정보 수집·이용 동의
보기
[필수] 만 14세 이상입니다
보기
[선택] 마케팅 정보 수신 동의 (광고)
보기
보기 pressed ghost button .97 / .92
전체 동의
[필수] 이용약관 동의
보기
[필수] 개인정보 수집·이용 동의
보기
[필수] 만 14세 이상입니다
보기
[선택] 마케팅 정보 수신 동의 (광고)
보기
focus web ring on the row
전체 동의
[필수] 이용약관 동의
보기
[필수] 개인정보 수집·이용 동의
보기
[필수] 만 14세 이상입니다
보기
[선택] 마케팅 정보 수신 동의 (광고)
보기
개인정보 수집·이용 동의가 필요해요
a required one missing a failed continue: the danger line under the group
전체 동의
[필수] 이용약관 동의
보기
[필수] 개인정보 수집·이용 동의
보기
[필수] 만 14세 이상입니다
보기
[선택] 마케팅 정보 수신 동의 (광고)
보기
dark
Not drawn: disabled (an item the user cannot change, e.g. a withdrawn legal basis, is .5), loading (the consent list is static copy), selected is the checked state, empty.
Text size
전체 동의
[필수] 이용약관 동의
보기
[필수] 개인정보 수집·이용 동의
보기
[필수] 만 14세 이상입니다
보기
[선택] 마케팅 정보 수신 동의 (광고)
보기
130% text labels wrap; 보기 stays right
전체 동의
[필수] 이용약관 동의
보기
[필수] 개인정보 수집·이용 동의
보기
[필수] 만 14세 이상입니다
보기
[선택] 마케팅 정보 수신 동의 (광고)
보기
200% text 보기 under the label
Cell · busyA Cell whose action is running: it shows a spinner and the others wait.
a trailing 20 spinner in place of the chevron while the Cell’s action runs · sibling Cells are inert (.5) until it settles · spoken “처리 중” · the Cell keeps its 48 (and its width)
Library §22.2 Cell, busy “a trailing 20 spinner in place of the chevron while the Cell’s action runs; sibling Cells are inert (.5) until it settles; spoken ‘처리 중’”.
Anatomy
20370
카카오 계정 연결 해제
로그아웃
회원 탈퇴
20
48 busy Cell
spinner 20 in the end slot
2 × 48 inert
.5, not pressable
Press
the Cell presses as a row until it runs; while busy neither it nor its siblings respond: scale 0.98 · opacity 0.80 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Siblings
inert at .5 (no press, no focus) so a second action cannot start while one runs; the group’s other Cells return to rest when it settles
Settles
success: the Cell returns to its chevron and the result shows (a Toast, or the screen changes); failure: the Cell returns and a Toast reads the reason; never silent
Spoken
the busy Cell: "로그아웃, 처리 중" (aria-busy); the siblings: "…, 사용할 수 없음" (aria-disabled) until it settles; the result is announced by the Toast or the screen change
Motion
the spinner replaces the chevron instantly; the siblings dim fade 150 ms standard (0.2, 0, 0, 1); the spinner turns continuously (the platform indicator; a loop under 5 s of a user wait, so no cap is needed)
Reduced motion
the spinner stays (it is information); the dim of the siblings is instant
Text scale
the spinner is 20 and does not scale; the label wraps and the Cell grows (a floor of 48)
Used by
설정 (로그아웃, 계정 연결 해제, 회원 탈퇴), 프로필 actions that call the server
Replaces
the per-screen loading Pressable rows and the full-screen blocking spinner on 설정 actions
States
카카오 계정 연결 해제
로그아웃
회원 탈퇴
rest
카카오 계정 연결 해제
로그아웃
회원 탈퇴
busy · 로그아웃 spinner, siblings .5
카카오 계정 연결 해제
로그아웃
회원 탈퇴
busy · 회원 탈퇴 a destructive action running
카카오 계정 연결 해제
로그아웃
회원 탈퇴
pressed row .98 / .80
카카오 계정 연결 해제
로그아웃
회원 탈퇴
연결을 해제하지 못했어요
settled · failed back to rest; the Toast reads the reason
카카오 계정 연결 해제
로그아웃
회원 탈퇴
dark
Not drawn: hover and focus (follow the row), selected, error (the Toast), empty and a loading skeleton (the Cell’s own busy state is the loading).
Text size
카카오 계정 연결 해제
로그아웃
회원 탈퇴
130% text labels grow
카카오 계정 연결 해제
로그아웃
회원 탈퇴
200% text the spinner stays 20
Sheet · documentA legal or long document read inside a Sheet, without leaving the screen.
title = the document name (17/24·600) + a 13/18 secondary version line (“시행일 2026. 9. 1.”) · body: 17/24·600 article headings, 15/22 paragraphs, tables as label / value Cell pairs · it scrolls within the 88% cap, the title row with its close × is sticky, and there is no CTA
Library §22.2 Sheet, document “title = the document name + a 13/18 secondary version line … Body: 17/24·600 article headings, 15/22 paragraphs, tables as label/value Cell pairs. It scrolls within the 88% cap, the close is sticky, and there is no CTA.”
Anatomy
20370
개인정보 처리방침시행일 2026. 9. 1.
제1조 수집하는 개인정보투마는 서비스를 제공하기 위해 이름, 생년, 휴대폰 번호와 같은 정보를 필요한 만큼만 수집해요. 수집한 정보는 목적이 끝나면 지체 없이 파기해요.제2조 보관 기간
회원 정보
탈퇴 후 즉시 파기
결제 기록
5년
접속 기록
3개월
제3조 이용자의 권리언제든지 설정에서 동의를 철회하거나 정보를 열람하고 정정할 수 있어요.
20 pad
42 title block
name 24 + version 18
12 + 16
24 article head · 17/24·600
8
66 paragraph · 3 × 22
table · Cell pairs
label / value, 48 each
Press
the × is an IconButton (.92 / .78); the body is text and Cells that are not pressable: scale 0.98 · opacity 0.80 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Sticky
the title row (name, version, ×) stays pinned while the body scrolls; its bottom hairline #D4D4D4 appears only while text passes under it
No CTA
a document is read, not answered: no 확인 band (agreeing is done in the consent Cell that opened it); the × or the backdrop closes
Content
articles are text blocks (17/24·600 heading + 15/22 paragraphs); tables are label / value Cell pairs (the Cell grammar), never a bordered table
Spoken
a dialog named "개인정보 처리방침, 시행일 2026년 9월 1일"; headings are level-2 headings in order; a table is read as label then value pairs; the × is "닫기"
Motion
sheet rise 300 ms enter · exit 200 ms exit (0.3, 0, 1, 1); the title edge hairline fades fadeFast 100 ms standard when the body scrolls under it (hysteresis 8)
Reduced motion
platform fade; the hairline appears at once
Text scale
reading class: headings and paragraphs wrap and scroll; the version line wraps under the name; the sheet stays within the 88% cap and the title row stays pinned
Used by
온보딩 약관 동의 (보기), 로그인 links, 설정 > 약관 및 정책
Replaces
the full-screen legal pages (the legal board, /legal routes) opened from consent rows
States
9:41
올림픽공원 복식
관리
개인정보 처리방침시행일 2026. 9. 1.
제1조 수집하는 개인정보투마는 서비스를 제공하기 위해 이름, 생년, 휴대폰 번호와 같은 정보를 필요한 만큼만 수집해요. 수집한 정보는 목적이 끝나면 지체 없이 파기해요.제2조 보관 기간
회원 정보
탈퇴 후 즉시 파기
결제 기록
5년
접속 기록
3개월
제3조 이용자의 권리언제든지 설정에서 동의를 철회하거나 정보를 열람하고 정정할 수 있어요.
top
9:41
올림픽공원 복식
관리
개인정보 처리방침시행일 2026. 9. 1.
제1조 수집하는 개인정보투마는 서비스를 제공하기 위해 이름, 생년, 휴대폰 번호와 같은 정보를 필요한 만큼만 수집해요. 수집한 정보는 목적이 끝나면 지체 없이 파기해요.제2조 보관 기간
회원 정보
탈퇴 후 즉시 파기
결제 기록
5년
접속 기록
3개월
제3조 이용자의 권리언제든지 설정에서 동의를 철회하거나 정보를 열람하고 정정할 수 있어요.
scrolled the title edge hairline appears
9:41
올림픽공원 복식
관리
개인정보 처리방침시행일 2026. 9. 1.
제1조 수집하는 개인정보투마는 서비스를 제공하기 위해 이름, 생년, 휴대폰 번호와 같은 정보를 필요한 만큼만 수집해요. 수집한 정보는 목적이 끝나면 지체 없이 파기해요.제2조 보관 기간
회원 정보
탈퇴 후 즉시 파기
결제 기록
5년
접속 기록
3개월
제3조 이용자의 권리언제든지 설정에서 동의를 철회하거나 정보를 열람하고 정정할 수 있어요.
88% cap the sheet stops at 742; the title stays
9:41
올림픽공원 복식
관리
개인정보 처리방침시행일 2026. 9. 1.
loading heading + paragraph skeleton
9:41
올림픽공원 복식
관리
개인정보 처리방침시행일 2026. 9. 1.
문서를 불러오지 못했어요
다시 시도
error the Notice with 다시 시도
9:41
올림픽공원 복식
관리
개인정보 처리방침시행일 2026. 9. 1.
제1조 수집하는 개인정보투마는 서비스를 제공하기 위해 이름, 생년, 휴대폰 번호와 같은 정보를 필요한 만큼만 수집해요. 수집한 정보는 목적이 끝나면 지체 없이 파기해요.제2조 보관 기간
회원 정보
탈퇴 후 즉시 파기
결제 기록
5년
접속 기록
3개월
제3조 이용자의 권리언제든지 설정에서 동의를 철회하거나 정보를 열람하고 정정할 수 있어요.
dark
Not drawn: pressed, hover, focus (the × is an IconButton: controls board), disabled, selected and empty (a document always has content).
Text size
9:41
올림픽공원 복식
관리
개인정보 처리방침시행일 2026. 9. 1.
제1조 수집하는 개인정보투마는 서비스를 제공하기 위해 이름, 생년, 휴대폰 번호와 같은 정보를 필요한 만큼만 수집해요. 수집한 정보는 목적이 끝나면 지체 없이 파기해요.제2조 보관 기간
회원 정보
탈퇴 후 즉시 파기
결제 기록
5년
접속 기록
3개월
제3조 이용자의 권리언제든지 설정에서 동의를 철회하거나 정보를 열람하고 정정할 수 있어요.
130% text text grows, the body scrolls
9:41
올림픽공원 복식
관리
개인정보 처리방침시행일 2026. 9. 1.
제1조 수집하는 개인정보투마는 서비스를 제공하기 위해 이름, 생년, 휴대폰 번호와 같은 정보를 필요한 만큼만 수집해요. 수집한 정보는 목적이 끝나면 지체 없이 파기해요.제2조 보관 기간
회원 정보
탈퇴 후 즉시 파기
결제 기록
5년
접속 기록
3개월
제3조 이용자의 권리언제든지 설정에서 동의를 철회하거나 정보를 열람하고 정정할 수 있어요.
200% text the title block wraps; the cap holds
EmptyState · full screen, and the header-less offline stripA blocking gate that owns the whole screen (the version gate), and how the offline notice sits on a screen with no header.
EmptyState full screen: the §9 EmptyState centred in the whole screen with no header: mark slot (stacked lockup), title 17/24·600, body 15/22 secondary, one primary Button when an action exists (업데이트) · Offline strip, no header: the strip (350 × 48, §9) pins directly under the status bar, solid, and pushes the content down by its own height
Library §22.2 EmptyState, full screen “the §9 EmptyState centred in the whole screen with no header, for a blocking gate (the version gate): mark slot, title, body, one primary Button when an action exists (업데이트)” · Offline strip, no header “on a screen with no header, the strip (350 × 48, §9) pins directly under the status bar, solid, and pushes the content down by its own height”.
Anatomy
195
9:41
오프라인이에요
다시 시도
MARK
DABUSI
업데이트가 필요해요최신 버전으로 업데이트하면 계속 쓸 수 있어요
업데이트
44 status bar
48 strip
pins under the status bar
centred in the rest
lockup · 32 · title · 8 · body · 24 · Button
Press
the 업데이트 Button (.97 / .92): scale 0.97 · opacity 0.92 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Gate
blocking: no back, no header, no tab bar: the only ways out are the action (업데이트) or closing the app; with no action (a maintenance screen) there is no Button
Strip without a header
pinned directly under the status bar (no 8 gap), solid, 350 × 48 at the rail; the content is pushed down by 48 (the screen has no band to overlay)
Mark
the stacked lockup slot (180 wide) in place of the §9 disc; no mark is drawn until the owner’s mark exists
Spoken
the title is the screen’s h1 ("업데이트가 필요해요"); the body; the button "업데이트, 버튼" (opens the store); the strip: "오프라인이에요" status
Motion
appears at once on launch (no transition from a header); the strip fade 150 ms standard (0.2, 0, 0, 1)
Reduced motion
the cross-fade becomes instant
Text scale
reading class: title and body wrap and the block grows (it scrolls when taller than the screen); the strip grows (floor 48) and pushes the block down; the lockup stays 180
Used by
the version gate and a maintenance gate; A1 and the web gate for the offline strip
Replaces
the app-update modal text and the ad hoc offline Banner on the login screen (packages/features/auth, network-banner.tsx)
States
9:41
MARK
DABUSI
업데이트가 필요해요최신 버전으로 업데이트하면 계속 쓸 수 있어요
업데이트
version gate a primary 업데이트
9:41
MARK
DABUSI
점검 중이에요잠시 후에 다시 열어주세요
maintenance no action: no Button
9:41
MARK
DABUSI
업데이트가 필요해요최신 버전으로 업데이트하면 계속 쓸 수 있어요
업데이트
Button pressed
9:41
MARK
DABUSI
업데이트가 필요해요최신 버전으로 업데이트하면 계속 쓸 수 있어요
업데이트
Button focus
9:41
MARK
DABUSI
업데이트가 필요해요최신 버전으로 업데이트하면 계속 쓸 수 있어요
업데이트
checking the Button’s spinner while the store opens
9:41
오프라인이에요
다시 시도
MARK
DABUSI
업데이트가 필요해요최신 버전으로 업데이트하면 계속 쓸 수 있어요
업데이트
offline · no header the strip under the status bar, content pushed down
9:41
MARK
DABUSI
업데이트가 필요해요최신 버전으로 업데이트하면 계속 쓸 수 있어요
업데이트
오프라인이에요
다시 시도
offline · Android status 24: the strip at 24
9:41
오프라인이에요
다시 시도
MARK
DABUSI
업데이트가 필요해요최신 버전으로 업데이트하면 계속 쓸 수 있어요
업데이트
dark
Not drawn: hover (web), disabled (the only action is never disabled), selected, error (a store failure is a Toast over the gate) and empty.
Text size
9:41
오프라인이에요
다시 시도
MARK
DABUSI
업데이트가 필요해요최신 버전으로 업데이트하면 계속 쓸 수 있어요
업데이트
130% text the block grows
9:41
오프라인이에요
다시 시도
MARK
DABUSI
업데이트가 필요해요최신 버전으로 업데이트하면 계속 쓸 수 있어요
업데이트
200% text the strip stacks its action; the block scrolls
Web install row (blocked on EXT-8)On the web, a quiet line that offers the app. Drawn as a marked gap: it is not built until the apps are listed.
a 13/18 caption plus a tonal 앱 설치하기 button (sm 36, 44 by slop) · blocked on EXT-8 (the store listings): drawn as a marked gap until the apps are listed
Library §22.2 Web install row “a 13/18 caption plus a tonal 앱 설치하기 button. Blocked on EXT-8 (store listings); drawn as a marked gap until the apps are listed.”
Anatomy
20370
앱으로 열면 알림도 받고 더 빨라요
앱 설치하기
BLOCKED · EXT-8
sm 36 · hit 44
56 row
pad 8 · caption + sm tonal Button
Press
the 앱 설치하기 Button (.97 / .92): scale 0.97 · opacity 0.92 · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Blocked
EXT-8: the iOS and Android listings do not exist yet, so the Button has nowhere to go: this sheet marks the gap (BLOCKED · EXT-8) and the row is not built until the apps are listed
Shown
only on the web, only when the app is not installed, never on a hub; dismissible for the session (a quiet row, not a Notice)
Spoken
"앱으로 열면 알림도 받고 더 빨라요" then the button "앱 설치하기" (a link to the store listing)
Motion
press: spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in / 150 ms out
Reduced motion
the settle is dropped, the dim stays
Text scale
reading class: the caption wraps; from 150% the Button goes under the caption, left-aligned
Used by
the web /login, the share pages (/s, /m, /p, /lb) and the web app shell
Replaces
nothing yet (new): the share pages’ ball “앱에서 열기” button is a different, primary action
States
앱으로 열면 알림도 받고 더 빨라요
앱 설치하기
BLOCKED · EXT-8
rest marked as blocked
앱으로 열면 알림도 받고 더 빨라요
앱 설치하기
BLOCKED · EXT-8
Button pressed
앱으로 열면 알림도 받고 더 빨라요
앱 설치하기
BLOCKED · EXT-8
Button focus
앱으로 열면 알림도 받고 더 빨라요
앱 설치하기
BLOCKED · EXT-8
dark
Not drawn: hover (web, .85), disabled, selected, loading, error and empty: not drawn while the component is blocked.
11Gesturesthe swipe action
SwipeActionA shortcut on a row: swipe left to reveal one action, swipe all the way to do it.
a row swipe left reveals one 72-wide action the full height of the row (danger for delete, ink for archive; a 20 glyph over a 13/18·600 label in the on-colour) · a full swipe commits with an undo toast · accelerator only: every swipe action also exists in Select mode or the row’s ActionSheet · disabled in Select mode · reduced motion: no elastic
Library §18.2 SwipeAction “row swipe left reveals one 72-wide action (danger for delete, ink for archive); full swipe commits with an undo toast; accelerator only — every swipe action also exists in Select mode or the row’s ActionSheet; disabled in Select mode; reduced motion: no elastic” · §18.1 D1 “Select mode is the path (accessible, discoverable); SwipeAction is an accelerator only”.
Anatomy
318390
삭제
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
the row, shifted 7272
68 row
the action is the row’s full height
Press
the revealed action (dim .85, no settle: it is a block of colour): dim 0.85, no settle · spring in {20, 400, .7}, out {15, 300, .7} · dim 80 ms in, 150 ms out · UI thread
Hover
web, pointer: opacity .85 + cursor: pointer
Focus
web: 2px ink ring, offset 2
Disabled
opacity .5, not-allowed, inert · aria-disabled
Loading
aria-busy · skeleton opacity .6 ↔ .9, 800 ms half-cycle (shimmer pins .75 under reduced motion)
Accelerator only
every swipe action also exists in Select mode (the accessible, discoverable path) or the row’s ActionSheet; a screen reader, a keyboard and a mouse never need the gesture
Select mode
swiping is disabled while Select mode is on (the checkboxes lead): the row does not move
Commit
a full swipe (past half the row width, 195, proposed) commits the action, slides the row out and shows the action Toast with 되돌리기 (6 s); a swipe released between 72 and the commit point settles open at 72
Colour
danger for delete (white label on #B8332E), ink for archive (white on ink; dark: canvas ink on #DFE4DC); one action per row, one direction (left)
Spoken
the action is exposed to assistive technology as a custom action on the row ("삭제"), never relying on the gesture; the row’s label is unchanged; after commit the Toast is announced ("알림을 지웠어요, 되돌리기")
Motion
the reveal follows the finger 1:1; past 72 the row rubber-bands (an elastic resistance) up to the full-swipe commit point; release settles on a spring {15, 300, .7}; commit slides the row out in 200 ms (exit curve) and the siblings snap up; the undo Toast is the 6 s action Toast
Reduced motion
no elastic and no spring settle: the row stops exactly at 72, a full swipe commits at once
Text scale
the action is 72 wide and row-height tall (it grows with the row at large text); its label is a label class (cap 1.5) and wraps in the 72 only up to two words; the glyph stays 20
Used by
알림 센터 (삭제 / 읽음), 메시지 목록 (나가기 / 보관); never the only way to act
Replaces
no shipped swipeable row exists today (no Swipeable in the code base): this is a new accelerator on rows that already have Select mode
States
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
rest
삭제
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
swiping the action appears behind the row (36 of 72)
삭제
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
revealed settled open: 72
삭제
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
past 72 rubber-band resistance to the commit point
삭제
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
full swipe commit: the row slides out, the action fills
삭제
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
action pressed dim .85
삭제
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
action focus web: the action is reachable as a button only through the ActionSheet equivalent
보관
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
archive ink tone
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
Select mode swipe disabled: the row does not move
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
알림을 지웠어요
되돌리기
after commit the action Toast with 되돌리기, 6 s
삭제
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
dark danger #FA9B94 with the canvas ink
Not drawn: hover and disabled (the gesture has no pointer or disabled form; the ActionSheet and Select mode are its equivalents), loading, selected, error (a failed commit restores the row and shows a Toast) and empty.
Text size
삭제
참가비 입금 확인 요청
방금
이서연님이 9월 회비 입금을 알렸어요
130% text the row and its action grow
삭제
참가비 입금 확인 요청
이서연님이 9월 회비 입금을 알렸어요
방금
200% text the action keeps 72 wide, the row grows taller
12Verification and spec conflictswhat was measured in a real browser, and every place the text could not be drawn as written
DOM verification (Chromium, window 1800): 0 failures

Everything below was read from the rendered page by a script: 55 specimen blocks, 54 anatomy stages and 571 state and variant frames. The rules are the page-board contract section 5: rail 20 / 370, boxes on the 4pt grid (a height set by text lines, n × 22, is a standard exemption; spacing values (pads, gaps, seams) are never exempt), type nodes only on the scale, media rows 80 / person rows 68 / Cells 48 as floors, section seams, no shadow, no middle dot, one trailing element per row, Tags at most two per row, every interactive element a 44 target. Magenta board chrome, art and the platform keyboard stand-in are outside the checks.

Rules checked
RuleCheckedFailsExemptions (listed, with the reason)
Heights against the spec (marked boxes)5440none
minHeights are floors (§15): a mark with a spec must measure exactly that at 100% text; large-text frames and wrapped variants are listed as text-driven.
Marked box heights on the 4pt grid44040
533 height set by text lines (n × line)
135 computed media size (the width / 3 or / 4)
65 the device home inset (34, an OS value) in the height
14 height built from text lines (22 meta line · 18 label line)
Spacing on the 4pt grid (pads, margins, gaps; never exempt)103410
85 device home inset (OS value) added to a 16 / 20 pad
24 2pt photo-grid gap (ruled by §18.1 D5, the photo-grid standard)
Negative margins (345: -10 × 180, -12 × 75, -6 × 65, -2 × 25) are the glyph boxes bleeding onto the rail (a 44 target around a 24 glyph) and are not spacing.
Type nodes on the scale (11/14 12/16 13/18 15/22 17/24 20/28 24/32 40/48)29560
392 scaled by B6 (base on the scale, ×k)
4 Kakao label: platform system font, 15 × label cap
Interactive elements: 44 targets (visual < 44 needs hit slop)10810
170 Choice 36 (hit slop 4) (library §1: hit slop (44 − visual) / 2)
97 sm Button 36 (hit slop 4) (library §1: hit slop (44 − visual) / 2)
64 checkbox 24 inside a 48 row (hit slop 10) (library §1: hit slop (44 − visual) / 2)
49 reaction chip 28 (hit slop 8) (library §1: hit slop (44 − visual) / 2)
13 wizard back square 40 in a 44 target
2 Switch 28 (hit slop 8) (library §1: hit slop (44 − visual) / 2)
1 tab item 26 wide (the 24 gap is its slop) (library §1: hit slop (44 − visual) / 2)
Slop is read as a 44 box around the visual; none of the exempt visuals is smaller than 24.
Hairlines and seams (cell 1 #E5E5E5 on the rail, none after the last, edge 1 #D4D4D4, seams exact)2920none
Rail 20 (text edges on 390 frames)1820none
Pressed frames (scaled .98) and swiped rows are outside the check.
No shadows (blur 0 only)530none
54 zero-blur rings (a focus or reaction ring, the avatar-group canvas ring) are not shadows.
No middle dot between facts (text nodes read)29560
13 the Korean term 수집·이용 (a word-joiner inside one term, the shipped string)
One trailing element per row, at most two Tags1470none
Names and text never clipped (L1 / L2 text boxes)12250none
Annotation hygiene: ruler notes inside their column and not overlapping2290none
Heights against the spec (63 marks)
ComponentSpecnMeasured
Avatar group, 32 box32 (§19.5)132 ×1pass
Avatar group, 40 box40 (§19.5)740 ×7pass
Avatar group, 48 box48 (§19.5 rule)148 ×1pass
Avatar group, 56 box56 (§19.5 rule)156 ×1pass
Caption row, one line32 (§18.2 min 32)1332 ×14 · 36 ×1pass
Cell48 (§5 min 48)348 ×3pass
Two-line Cell68 (§19.2 min 68)768 ×7pass
Cell, sm Button slot48 (§21.2 min 48)248 ×2pass
Cell, busy48 (§22.2 min 48)2048 ×20 · 52.6 ×3 · 68 ×3pass
Cell, Choice slot48 (§21.2 min 48)248 ×2pass
Cell, contribution row48 (§21.2 min 48)148 ×1pass
Cell, document table48 (§22.2 min 48)1548 ×15 · 52.6 ×3 · 116 ×3pass
Cell, edit48 (§21.2 min 48)1648 ×16pass
Cell, IconButton slot48 (§21.2 min 48)248 ×2pass
Cell, label Tag48 (§21.2 min 48)148 ×1pass
Cell, locked (reason line)68 (§21.2 min 68)468 ×4pass
Cell, Switch slot48 (§21.2 min 48)248 ×2pass
Cell, Tag value48 (§19.3 min 48)248 ×2pass
Task Cell48 (§18.2 min 48)448 ×4pass
Cell, consent48 (§22.2 min 48)3948 ×39 · 52.6 ×3 · 81.2 ×1 · 108 ×1 · 152 ×3pass
Cell, 전체 동의48 (§22.2 min 48)1048 ×10 · 52.6 ×1 · 68 ×1pass
CTA band (danger, with / without helper)118 / 144 (§3: 16 + 52 + 16 + 34 (+ 26))11118 ×9 · 144 ×2pass
Disclosure Cell, closed48 (§19.3 min 48)248 ×2pass
Edit Sheet footer band84 / 118 (§18.2: 118 (84 on the keyboard))984 ×1 · 118 ×8 · 182 ×1pass
CTA band, entry (± helper)204 / 230 (§22.2: 204 (+ 26))8204 ×6 · 217.4 ×1 · 230 ×2 · 238 ×1pass
ExpandToggle, lone row48 (§19.3 48)1148 ×11pass
Field skeleton74 (§21.2: 18 + 8 + 48)974 ×9pass
Figure column row68 (§20.2 68)1968 ×19 · 80 ×1 · 103.4 ×1 · 145 ×1 · 181 ×1pass
FilterSheet footer band118 (§18.2: 118)7118 ×7 · 182 ×1pass
Club hero, place variant82 (§18.2: 82)582 ×5 · 98.2 ×1 · 114 ×1 · 126.4 ×1pass
Hero facts (3 / 2 / 1 facts)96 / 128 / 160 (§19.3: 160 / 136 / 112 (drawn 96 / 128 / 160 without the top 24))1256 ×1 · 96 ×4 · 128 ×1 · 160 ×7 · 194.6 ×1 · 403.8 ×1pass
HeaderTextAction target44 (§14 44)1844 ×18pass
Web install row56 (§22.2 56)556 ×5pass
ProviderButton, Kakao52 (§22.2: 52 (60 / 68 at large text))1952 ×19 · 60 ×1 · 68 ×1pass
Live row (with its 8 and 8)40 (§19.3: 24 + 16)740 ×7pass
Map face in a form140 (§21.2 140)8140 ×8pass
Offline strip over a band48 (§9 / §19.5 48)848 ×8 · 52.6 ×1 · 116 ×1pass
Offline strip, plain page48 (§9 48)248 ×2pass
Offline strip, no header48 (§22.2 48)448 ×4 · 52.6 ×1 · 116 ×1pass
Composer reply target44 (§19.3 44)844 ×8pass
Reply toggle (lone row)48 (§19.3 48)148 ×1pass
Review page CTA band (helper)144 (§3: 144)1144 ×1pass
Achievement row (glyph tile)80 (§5 MediaRow 80)880 ×8pass
Row, anonymous avatar68 (§5 PersonRow 68)268 ×2pass
Row with a trailing control68 (§20.2 68)168 ×1pass
Row, group avatar68 (§5 PersonRow 68)668 ×6pass
Row, notification (unread)68 (§5 68)4468 ×44 · 80 ×4 · 184 ×4pass
Request row with AnswerPair112 (§18.2: 68 + 44 = 112)8112 ×8pass
Row with form text68 (§5 68)168 ×1pass
Person row68 (§5 68)568 ×5pass
Recent search Text row68 (§20.2 68)668 ×6pass
ResultRow in a post68 (§5 68)568 ×5 · 80 ×1 · 139 ×1pass
Text row68 (§20.2 min 68)2268 ×22pass
ScoreEntry 저장 band (inset 0)84 / 118 (§19.3: 16 + 52 + 16)1984 ×10 · 118 ×9pass
Search takeover field40 (§21.2 40)740 ×7pass
SectionHead24 (§3 24)3024 ×30 · 31.2 ×2 · 48 ×1 · 79 ×1pass
Strip with / without deltas80 / 100 (§19.3: 100 / 80)880 ×1 · 100 ×7pass
Tabs row44 (§3 44)1144 ×11pass
Time range 완료 band118 (§3: 118)9118 ×9pass
Time range tabs52 (§21.2 52)952 ×9pass
Wizard footer band (± helper)118 / 144 (§3: 118 (+ 26))2118 ×1 · 144 ×1pass
WizardShell header56 (§21.2 56)1256 ×12pass
WizardShell header at large text56 (B6: 56 at every scale)256 ×2pass
Heights set by text or by the width (no single spec, a standard exemption)
Caption row, two lines936 · 108 · 46.82 × 18 (floor 32)
Caption row, past two lines136a copy defect, shown flagged
Cells at large text2116 · 52.6n × line + 24
Two-line Cells, long text and large text380 · 86 · 156n × line + 24
Locked Cell at 200%1156n × line + 24
Tag-value Cell at large text299 · 52.6n × line + 24
Choice-slot Cell at large text2108 · 52.6n × line + 24
Comment (body of n lines)1444 · 60 · 96 · 14022 + 4 + n × 22 + 8 + 18
Comment at large text2268 · 149.8n × line
Reply (body of n lines)974 · 9622 + 4 + n × 22 + 8 + 18
Disclosure Cell, open884 · 106 · 256 · 125.812 + 22 + 12 + n × 22 + 16 (§19.5 exemption)
Figure row, long name190n × line + 24
Text row, wrapped or large text380 · 90 · 139n × line + 24
Row with control, wrapped380 · 90 · 148n × line + 24
Request row, wrapped3124 · 134 · 272n × line + 24 + 44
Request row, error line1138112 + 8 + 18
Notification row, large text280 · 184n × line + 24
PhotoGrid (390 / 350 wide)12390 · 115.3 · 259.3 · 128.7tile = (w − 4) / 3, computed
PhotoGrid tile58115.3 · 128.7computed media size
Composer photo tile4381.5(350 − 24) / 4 = 81.5, computed
Composer add tile1081.5the tile size
Composer tile rows12171 · 81.5 · 260.5rows of 81.5 + 8
FilterSheet8492 · 614 · 508.2the sum of its parts (content)
Edit Sheet10252 · 286 · 304 · 410the sum of its parts (content)
Document Sheet9172 · 260 · 522 · 676 · 631.6content, capped at 88%
Time range Sheet9452 · 456the sum of its parts (content)
ScoreEntry Sheet (without the keypad)19290 · 314 · 316 · 346 · 372 · 454 · 478 · 461.2 · 297.2the sum of its parts (content)
ActionSheet9254 · 278 · 302 · 370 · 474 · 734the sum of its parts (content)
Dialog, one action8188 · 237 · 344content
Dialog (pair)3166 · 368content
EmptyState, permission (content area)6420fills the content area
Roster with the door10200 · 548 · 234.8rows of 100
Strip at large text2100 · 11624 + n × line
Kakao button at large text260 · 683 × font size
Live row, wrapped340 · 52 · 88n × line + 16
Spec conflicts: where the library text could not be drawn exactly as written (31)
WhereWhat the spec saysWhat the board draws, and the proposed fix
§19.3 ScoreEntry Sheet form (height)“Worst case about 390 + the keypad, under the 88% cap (§16 P2).”Measured: with the keypad up, the quick row plus tiebreak plus validation reached 785, over the 88% cap of 742.7. Ruled in §23 ruling 1 and drawn: the sheet opens without the keyboard on the title row, the set table and all seven quick picks (454); a tap on a set field raises the keypad and folds the quick row, and the typing worst case (tiebreak plus validation) is 372 + 291 keypad = 663, 78.6% of 844, under the cap.
§19.3 vs §18.2 Edit Sheet§18.2: an edit Sheet has no ×, its footer is the pair 취소 + 저장. §19.3 / D4: ScoreEntry has “one 저장”.Drawn: no ×, a single 저장, dismissal by backdrop or back with the unsaved-changes Dialog. Proposed fix: allow a single-action edit Sheet when the Sheet is a rapid-entry form (ScoreEntry); the pair stays the rule for field edits.
§20.2 / §21.2 tertiary ink in darkA 16 chevron, edit pencil and lock are drawn in tertiary (#626C64 on white, “≥ 16px only”).Ruled in §23 ruling 2 and drawn: the dark tertiary is #8F968F, 5.47:1 on the dark card #1A201B (the light tertiary #626C64 is 5.46:1 on white); it lands in the colour gate with the other dark values at D/S1.
§15 B6 Cell formulaB6 derives a Cell as line + 26 (a 13 pad above and below).13 is off the 4pt grid and spacing is never exempt: Cells are drawn with a 12 pad and a 48 floor (22 + 24 = 46 → 48), so from 130% a Cell is 2 shorter than the addendum table. Proposed fix: change the formula to line + 24.
§21.2 Cell trailing slot (Choice, Button)A Choice ▾ (sm 36) or an sm tonal Button sits in a Cell that “stays 48”.48 holds only if the control has 6 above and below, which is off the grid as a pad; drawn with no vertical pad on the Cell (the label block carries its own 12), so the control is centred by alignment. Proposed fix: say “min 48, vertical padding on the label block only”.
§21.2 WizardShell header“Centre: the progress, in ink.”Drawn as the forms board does: the 2 pt line on the header’s bottom edge, ink for done + current, #E5E5E5 for the rest; nothing sits in the centre of the header. Proposed fix: reword to “the progress line on the header’s bottom edge”.
§21.2 Search takeover field vs §7 Search§7: Search is 44, r full, gray100 with a 20 icon. §21.2: the takeover bar is 40, r12, gray100, 15/22.Ruled in §23 ruling 3 and drawn: the 40 r12 field sits inside a 44 target in the 56 header (the hit area stays 44, the visual follows the grammar’s radii); the page Search stays the 44 pill.
§20.2 EmptyState, permission vs §9§9: disc 80 gray100 with a 32 glyph; §20.2: a bare 48 lock glyph, no disc, vertically centred.Drawn as §20.2 (bare glyph) so a wall does not read as an empty list. Proposed fix: state in §9 that the permission variant is bare and centred.
§19.3 ExpandToggle lone row vs CommentThe lone row’s label is centred; a Comment’s reply toggle is “aligned to the text column”.Drawn: centred for lists, left-aligned at x 96 for the reply toggle. Proposed fix: add a left-aligned variant to the lone-row spec.
§19.3 Disclosure Cell label positionCell label centred in 48 (13 from the top); the body opens “12 below the label”.Centring would move the label 1 when the cell opens; drawn with the label 12 from the top in both states. Proposed fix: write “label 12 from the top”.
§18.2 Tabs item with count§3: the active underline is “the label width”; §18.2 adds a count Tag 4 after the label.Drawn: the underline spans label + count (the item is one target). Proposed fix: say “the underline spans the item”.
§18.2 Header icon badge (dot)§3 Tab bar: “7px danger dot”; §18.2: the same badge on a header icon.7 is off the grid: drawn 8 for the dot and a 16 count Tag (as the tab bar’s count). Proposed fix: make the dot 8 in §3 too.
§18.2 Count Tag paddingCount tag: 20 tall, min 20, 12/16·700.The other boards pad it 6 (off the grid); drawn 4 here. Proposed fix: fix the pad at 4 in §6.
§18.2 SectionHead (large text)Unspecified: where the action goes when the title and action no longer fit.Drawn: the action moves under the title, left-aligned, from 150% (B6.5 for a trailing element). Proposed: confirm.
§18.2 Activity heatmapOnly: 7 × n weeks of 12 cells, gap 4, r3, five steps from gray100 to ink, month labels, a scrub readout.Unspecified and drawn as proposals: the five steps (#F2F2F2, #C4C7C4, #8D928E, #4F5851, #0D100E; dark inverted), 22 weeks (fits 350), no weekday labels, an ink count Tag readout; colour is not the only carrier (readout and spoken total, WCAG 1.4.1).
§18.2 PhotoGrid select mode“Long-press enters select.”The select affordance (a 24 round check at the tile’s top-right, inset 8, the photo dimmed to .6), the “N장 선택됨” header and the 삭제 action are proposals. Gap 2 is ruled by D5 and is the one spacing value off the 4pt grid.
§18.2 MediaBand album overlayA count Tag bottom-right and pager dots bottom-centre.Unspecified: the Tag inset (12), the dot gap (drawn 8), the dots above 12 photos (dropped; the Tag alone carries the position) and legibility of the white dots on a bright photo (a bottom scrim ink α0.2 → 0 over 48 is proposed).
§19.3 Live row, endedThe Live tag then the date and venue.The ended state is not specified; drawn as a neutral Tag “종료” in the Live tag’s place. Proposed fix: add it to the row.
§19.3 Form strip (dark)승 = ink fill with white 12/16·700; 패 = gray100 with secondary.In dark, ink becomes #DFE4DC and the character takes the canvas colour; drawn so.
§19.3 Comment spacingSpacing only between comments (24) and to replies (12).Drawn as proposals: 4 between line 1 and the body, 8 between the body and 답글 달기; a deleted comment shows a plain gray100 circle (not the anonymous glyph, which is a privacy cue).
§19.5 Group avatar sizes20 in a 32 box, 24 in a 40 box.48 and 56 boxes are not specified; drawn by the same 0.6× rule rounded to the grid (28 and 32), and the anonymous glyph at half the box. Proposed: confirm.
§18.2 SwipeActionOne 72-wide action, full swipe commits with an undo toast.The commit point (drawn at half the row width, 195), the glyph over label layout and the archive colour are proposals; the board notes no shipped swipeable row exists.
§21.2 Time range wheelA native wheel edits the selected tab.The wheel is the platform control, drawn as a stand-in; its minute step is not specified (5 minutes proposed for sessions).
§21.2 Photo tilesRemove = a 24 circle in a 44 target at the top-right inset 4.The 44 target overlaps the tile’s corner and its neighbours; a drag lift (scale 1.05, .9) and the failed-upload tile are proposals.
§22.2 ProviderButton, large text“The container grows so the label stays within a third of its height.”Read as: the label font size is at most a third of the height (15 → 52, 19.5 → 60, 22.5 → 68). The Kakao symbol is drawn as a stand-in bubble; the label weight is not specified (500 drawn).
§22.2 CTA band, entry (links)“이용약관 · 개인정보 처리방침 as ghost links”.The written middle dot is not allowed in the board contract (no middle dots, MetaBreak instead): drawn with a 1px break. Proposed fix: write the spec with the break.
§22.2 EmptyState, full screen (mark slot)“mark slot, title, body, one primary Button”.The mark size is not specified: drawn as the stacked lockup (180 wide, a 73.5 slot). The Button is md 44 (the §9 EmptyState size).
§22.2 / §19.5 Offline strip placementOver a MediaBand: attached to the header’s bottom edge, pushing nothing; no header: directly under the status bar, pushing the content.A plain page with a header is not specified: drawn pinned under the header with 8 above and below. Proposed: the same 8 rule everywhere.
§22.2 Cell, consentA leading 24 checkbox, the [필수] / [선택] prefix, a trailing sm ghost 보기.The prefix weight (600) and tone ([선택] secondary) are not specified; the master row’s mixed state is drawn as a minus in the ink square.
B7 / B8 verification limitsReduced-motion variants, spoken labels and live regions.A static render cannot run motion or a screen reader: the motion is printed as tokens and frames, the spoken names are written from the model and are unverified until a device run (axe-core was not run).
Emoji fontThe Reactions set is the platform emoji.The board’s browser has no emoji font: the six reactions are flat vector stand-ins; the app renders the platform emoji.
Coverage: 75 library rows, each to the specimen that draws it
§Library rowSpecimen
18.2Unread markerUnread marker
18.2SwipeActionSwipeAction
18.2AnswerPairAnswerPair
18.2PhotoGridPhotoGrid
18.2SectionHead variants (count suffix · command action · ExpandToggle slot)SectionHead variants
18.2Tabs item with countTabs item with count
18.2FilterSheetFilterSheet
18.2Edit Sheet footerEdit Sheet footer
18.2CTA band, danger toneCTA band · danger tone
18.2PersonRow trailing metaRow trailing control
18.2Caption rowCaption row
18.2Activity heatmapActivity heatmap
18.2MediaBand album overlayMediaBand · album overlay
18.2PostRow system variantPostRow · system variant
18.2Club hero, place variantClub hero · place variant
18.2Header icon badgeHeader icon badge
18.2Task CellCell · edit, trailing slot, label Tag, Task Cell
18.2Two-line Cell (min 68, §19.2)Cell · two-line, reason line, locked
18.2Reaction picker (replaced by Reactions, §19.2)Reactions
19.3ExpandToggle, lone rowExpandToggle
19.3ExpandToggle, disclosure CellExpandToggle
19.3Cell, reason lineCell · two-line, reason line, locked
19.3Cell, Tag valueCell · edit, trailing slot, label Tag, Task Cell
19.3Roster doorRoster door
19.3Hero facts: count and stateHero facts · count and state
19.3PersonRow trailing alignmentRow trailing control
19.3Composer reply targetComposer reply target
19.3Avatar, groupAvatar · group
19.3Avatar, anonymousAvatar · anonymous
19.3CommentComment
19.3Live rowLive row
19.3Strip with deltasStrip with deltas
19.3Form stripForm strip
19.3ScoreEntry, Sheet formScoreEntry · Sheet form
19.3ActionSheet (scope)ActionSheet · scope and the group-title rule
19.3ReactionsReactions
19.5ScoreEntry title row (the match name)ScoreEntry · Sheet form
19.5Group avatar sizes (20 in 32, 24 in 40)Avatar · group
19.5Disclosure Cell body (pad 16, n × 22 exemption)ExpandToggle
19.5Offline strip over a MediaBandOffline strip · over a MediaBand
19.5ActionSheet group titles (two or more non-destructive groups)ActionSheet · scope and the group-title rule
19.5PostRow counts vs Reactions (feed heart / post-page chips)Reactions · PostRow · system variant
19.5Content under a MediaBand (24)Content under a MediaBand
19.5Strip delta: zero or no dataStrip with deltas
19.5Form strip data (fewer than five)Form strip
20.2Text rowText row
20.2Thumb, glyph tileThumb · glyph tile, and the medal set
20.2Row trailing controlRow trailing control
20.2Figure columnFigure column
20.2EmptyState, permissionEmptyState · permission
20.3Medal colours and the glyph tile (four tiers)Thumb · glyph tile, and the medal set
21.2Cell, editCell · edit, trailing slot, label Tag, Task Cell
21.2Cell, lockedCell · two-line, reason line, locked
21.2Cell trailing slotCell · edit, trailing slot, label Tag, Task Cell
21.2Cell, label TagCell · edit, trailing slot, label Tag, Task Cell
21.2WizardShell header (owner LAW)WizardShell header
21.2Dialog, one actionDialog · one action
21.2Search takeoverSearch takeover
21.2Map face in a formMap face in a form
21.2Photo tiles (composer)Photo tiles (composer)
21.2Field skeletonField skeleton
21.2Input counterInput counter
21.2Caption row, two linesCaption row · two lines (and Caption row)
21.2Time range wheelTime range wheel
21.1Required-marker LAW (asterisk, starts unset)Required marker
22.2ProviderButton (Kakao)ProviderButton · Kakao
22.2CTA band, entryCTA band · entry, and the danger helper
22.2CTA band helper, dangerCTA band · entry, and the danger helper
22.2Brand lockupBrand lockup slot
22.2Cell, consent (+ 전체 동의 mixed)Cell · consent, and 전체 동의
22.2Cell, busyCell · busy
22.2Sheet, documentSheet · document
22.2EmptyState, full screenEmptyState · full screen, and the header-less offline strip
22.2Offline strip, no headerEmptyState · full screen, and the header-less offline strip
22.2Web install row (blocked, EXT-8)Web install row (blocked on EXT-8)
Spec (grounding, anatomy)

Proposal (2026-10-10), Phase B of the flat grammar v2 migration, the fourth component board: the components the page boards found missing, specified in docs/design/flat-v2-component-library-2026-10-09.md sections 18.2, 19.3, 19.5, 20.2, 20.3, 21.2 and 22.2, each drawn as a specimen at 1:1 with anatomy rulers, every applicable state, content rules, the spoken label, the motion with its reduced-motion variant, the dark values and the 130% / 200% frames, then a DOM verification (grid, type scale, minHeights, 44 targets, hairlines and seams) and the spec conflicts with a proposed fix each. Built from local primitives only (Fa*): no WFPhone, WFCard, WFChip, WFBadge, WFSelectChip, WFInput or WFSwitch.

route · packages/ui/src/cell-row.tsx · expand-toggle.tsx · section-header.tsx · pill-nav.tsx · empty-state.tsx · wizard-shell.tsx · list-row.tsx · avatar-stack.tsx · stat-strip.tsx · labeled-field.tsx · bottom-cta-band.tsx · time-band-field.tsx · confirm-sheet.tsx · packages/app/src/presentation/components/network-banner.tsx · venue-search-takeover.tsx · packages/features/clubs/src/shared/post-row.tsx · packages/features/messaging/src/dm-thread/compose-bar.tsx · packages/features/records/src/cards/activity-heatmap-card.tsx · packages/features/sessions/src/scorecard/score-edit-sheet.tsx · directory-venue-detail/parts/photo-strip.tsx · packages/features/onboarding/src/onboarding-step-consent.tsx grounded in 21 source file(s)
packages/ui/src/cell-row.tsxpackages/ui/src/expand-toggle.tsxpackages/ui/src/section-header.tsxpackages/ui/src/pill-nav.tsxpackages/ui/src/empty-state.tsxpackages/ui/src/wizard-shell.tsxpackages/ui/src/person-row.tsxpackages/ui/src/avatar-stack.tsxpackages/ui/src/stat-strip.tsxpackages/ui/src/labeled-field.tsxpackages/ui/src/bottom-cta-band.tsxpackages/ui/src/time-band-field.tsxpackages/ui/src/confirm-sheet.tsxpackages/app/src/presentation/components/network-banner.tsxpackages/app/src/presentation/components/venue-search-takeover.tsxpackages/features/clubs/src/shared/post-row.tsxpackages/features/messaging/src/dm-thread/compose-bar.tsxpackages/features/records/src/tab/records-record-zone.tsxpackages/features/sessions/src/scorecard-page/overlays/score-entry-sheet.tsxpackages/features/sessions/src/venue-page/venue-shelves.tsxpackages/features/onboarding/src/onboarding-step-consent.tsx