← Cockpit Wireframes
상세 페이지 — 플랫 언어
Approved — build queue
Venue Finder · 코트 찾기
ArchiveStrip Map · 세션 스트립 전체 시나리오Headers · 헤더 아이콘Compaction — 일정 · 코트 · 스코어카드Compaction — 클럽 상세 · 홈Compaction — 기록 · 프로필Live anchor — 진행 중 밴드Density · 카드 밀도토스트 — 리디자인 제안위저드 내비게이션 — 결정 보드드래프트 복원 — 무음 복원클럽 조 편성 — 모델 결정 보드Session Detail — Redesign · 일정Venue Detail — Redesign · 테니스장Profile Detail — Redesign · 프로필Tournament Detail — Redesign · 대항전Applicant Detail — Redesign · 가입 심사Canvas Migration — 회비 · 멤버 · 규칙Record Detail — Redesign · 기록Venue Filters · 코트 필터 재구성
Decided 2026-09-05 — shipped; kept as the decision record
Interactive kit · grounded in code

상세 페이지 — 플랫 언어

FLAT LANGUAGEDetail pages: no cards, on a non-grey canvas
Every detail screen renders as flat components directly on the page canvas — full-bleed hero, spacing-only rows inside a section, one hairline divider between sections, and Well as the one surviving grey for recessed content. This board shows the shipped grammar, the ten detail pages built on it, the seam ladder, the palette, and the hero anatomy. Every ruling that produced it — including the rejected options — is in docs/design/decision-log-detail-pages.md.
GRAMMARThe 7 parts of a flat detail page
A full-bleed media or typographic hero, a section title on the 16 rail, a full-bleed divider between sections (never between rows), fact rows and list rows with no hairline, a stat strip, Well as the one surviving grey, and a single bottom CTA.
문법
📷
풀블리드 미디어 히어로
모집중티어 배지
사진 아래, 캔버스 위에 — 스크림도 카드도 없음메타는 한 줄에 하나의 사실만
섹션 제목은 바깥메타는 하나배지
더보기 ›
사실 행
값 오른쪽
구분
간격만헤어라인 없음
박스
없음
통계 스트립
6/8확정
D-2남은 일수
₩8,000참가비
리스트 행간격만, 8px 패딩
🎾
행 제목보조 텍스트
🏟️
두 번째 행구분선은 섹션 사이에만, 행 사이는 간격만
확정
살아남는 회색 하나
Well, Callout, Banner, 빈 상태, 선반 타일 — 움푹 들어간 자리에만 틴트. 페이지 바닥은 절대 아님.
단 하나의 CTA
DetailShell
ground: 'tinted' | 'canvas' · footer?: a pinned action band (BottomCtaBand) rendered outside the scroll body
FlatColumn
top=16(hero→first section) · gap=24 around a full-bleed 1px section divider · lead?: a hero-zone block (StatStrip, a bare fact) with one divider below it, none above · divider={false} opts out
SectionBlock
variant='flat' → navTitle role, 17px/700, on the 16 rail, no min-height, 8px title→content gap · meta · right · link={{label,onPress}}
FactRow / FactList
label · value · tone · trailing? · edge?: 'first'|'last'|'only' drops that row's own top/bottom padding at a section boundary — never a hairline
RowList
draws no separators between rows by default; `dividers` opts a hairline back in only when each row is itself a multi-component block (e.g. post-detail's comment rows)
ListRow
leading · title · sub · trailing · onPress · chevron (shown by default when onPress && !trailing) · dense · edge
CellRow / StatStrip
equal flex:1 columns, content centered, 8px gap + 8px padding inside each cell, optional centered divider; StatStrip is a 2–4 cell item renderer over CellRow
BottomCtaBand
16px top/sides/bottom padding (one value on every edge), 1px top hairline, $card background
MediaHero
media{kind, uri} — fixed 132px band by default · title · badges: ONE row directly under the title, before meta, fixed order state → verification → category · meta: string | string[], one fact per line · avatar flush left/top
TypeHero
eyebrow (spans the rail above the identity row) · title · badges (same one-row grammar as MediaHero) · meta: string | string[] · leading: a top-aligned 56px avatar/logo · right: trailing count/ring only
Meta text grammar
primary meta = cardBody/$textSecondary · secondary/sub/eyebrow = cardMeta/$textTertiary (eyebrow at weight 700) · caption = cardBody/$textSecondary, smaller — one tier per slot
ActionIconRow
items: {icon,label,onPress}[3–5] — equal-ratio flex:1 cells, pressed state fills the whole cell
Well
tone: neutral | primary | warning · onPress + required accessibilityLabel renders as Pressable variant='card' affordance='chevron' — the one surviving grey
Pressable
affordance?: 'chevron'|'button'|'link'|'layer'|'none' (lint-enforced) · variant='row'/'card' carries a state layer by default
RemoteImage
uri · contentFit · cachePolicy · transition · onError · fallback — the one expo-image wrapper
SEAM
packages/ui/src/seams.ts — element 4 · item 8 · block/chrome 12 · rail/hero/band 16 · section 24 · tail 48. The one source every primitive above reads its seam from.
lint
@twomore/no-card-on-canvas · @twomore/one-flat-column · @twomore/pressable-needs-affordance
SEAMSSpacing & alignment — one owner per seam
A 6-rung seam ladder (element 4 · item 8 · block/chrome 12 · rail/hero 16 · section 24 · scroll tail 48) plus two alignment rails — TEXT at 16 (every text/control) and FULL-BLEED at 0 (media, dividers, the CTA top hairline). packages/ui/src/seams.ts.
간격 정렬
🗺️
히어로 제목 pageTitle
배지
메타 cardBody배지는 아래 줄
image→identity 12 (block)
hero title pageTitle 24/32
6/8flex 1 균등
₩8,000간격만, 선 없음
12:00셀 패딩 없음
섹션 제목 17/700meta
사실 행, 세로 8, 최소 44
값 오른쪽
행 사이
간격만, 헤어라인 없음
리스트 행 40/36
🙂
리딩 30 + 간격 12텍스트는 rail+42에서 시작
Well, 안쪽 12, 반경 12
hero→first 16 (rail, its own rung)
stat strip: flex 1, 8px gap (divider centered: 4/line/4), 8px padding inside, no vertical padding
section→section 24 + 1px divider + 24 (sections only)
title→content 8 (no min-height)
row height 40 (hitSlop → 44), dense 36, fact row 36
row→row 0, edge rule drops it at the section boundary
rail = 16
CTA 밴드, 위 16, 옆 16, 아래 16 + inset
PAGESThe ten detail pages
Every detail screen built on the grammar above — zero cards across all of them.
토요 정기 모임
🗺️
토요 정기 모임
참가 확정D-2☀️ 26°야외 좋음
8월 30일 (토) 14:00–17:00올림픽공원 테니스장 3, 4번 코트
6/8확정 인원
₩8,000내 참가비
8/30까지참석 마감
📣라켓 여분 2개 챙겨 갑니다. 늦으시는 분은 채팅으로 알려주세요 🙏
참여6명 확정예비 1
전체 보기
🙂
김민준 (나)3.5
호스트확정
🙂
이서연4.0
확정
🙂
박도윤3.0
예비
경기복식 로테이션
⚔️
팀 자동 구성대진은 시작 30분 전에 확정
4세트
정보
종목
복식
경기 방식
친선
티어
3.0–4.0
ELO
반영
참가비
₩8,000
취소 규정
24시간 전
⚖️The hero absorbs venue, time, and weather at once. The individual relationship is one stat-strip row. The notice signals via the one surviving grey (Well). Everything else is rows and fact rows directly on the canvas — no cards.
입금 완료
세션 · Session detail
테니스장
⋯ header menu = name/address correction suggestion + report (HeaderActionsMenu) — nothing sits below the title
🗺️
올림픽공원 테니스장
풀코트
서울 송파구 올림픽로 4243.2km
10면코트
실외/하드종류
₩6,000~1시간
🧭
길찾기
📞
전화
🔖
저장
공유
운영 시간지금 영업 중
Naver 기준
평일
06:00–22:00
주말/공휴일
06:00–20:00
이용 요금
주간(1시간)
₩6,000
야간(1시간)
₩9,000
예약
서울시 공공예약
편의시설
주차 샤워 조명 볼머신
여기서 열린 일정이번 달 4회
전체 보기
9/7
토요 정기 모임강남 테니스 클럽
6/8
⚖️Court count/type/rate are a strip. The facility-kind badge ("풀코트") sits in the one badge row under the title — no media overlay, no unverified/verified badge (verified shows only on an owner-verified venue). Actions are 4 equal-ratio cells; everything else is fact rows. No cards.
이 테니스장에서 일정 만들기
테니스장 · Venue detail
프로필
⚙️
🙂
김민준
🌿🥇NTRP 3.5–4.0
강남 테니스 클럽2024년부터
1,420ELO
24승 9패전적
92%출석
업적3개
전체 보기
🏆
🔥
🎯
출석최근 8주92%
친구12명
전체 보기
+1
📊
기록
⚙️
설정
✉️
친구 초대
⚖️An identity with no photo becomes a typographic hero — the name is the hero. One stat-strip row; achievements/attendance are recessed tiles (the one surviving grey); the menu is spacing-only rows. No cards.
프로필 · Profile (self)
이서연
✉️
🙂
이서연
🌿🥈NTRP 3.0–3.5
송파구잠실
1,380ELO
31총 세션
88%출석률
🌿신뢰 등급
플레이
선호 형식
복식
승률
최근 50경기 58%
🤝 이미 친구예요 — isFriend일 때만 렌더
⚖️A typographic hero replaces the two-card layout; the trust-tier badge (🌿) and the ELO tier badge share the one badge row under the title. The friend button's 6-state matrix stays on the band.
친구 추가
공개 프로필 · Public profile
강남 테니스 클럽
📷
🎾
강남 테니스 클럽
승인 가입3.0–4.5경쟁
강남구2023년 설립
+1
회원 42명
클럽 정보
종목
복식 위주
실력
3.0–4.5
가입 방식
승인 후 가입
홈 코트
올림픽공원 테니스장
회비
월 30,000원
정기 일정토 14:00외 1개
9/7
토요 정기 모임올림픽공원
6/8
소개
매주 토요일 올림픽공원에서 복식 위주로 칩니다. 3.0 이상이면 누구나 환영!
갤러리12장
전체 보기
⚖️A full-bleed album + identity below replaces the one-card hero. The joinability badge ("승인 가입") leads the one badge row under the title; tier range and format follow it. The member-faces cluster sits in FlatColumn's `lead` row under the identity, not a media overlay. The join CTA's 7-state matrix stays as-is; the 승인 대기 (pending) state gained a "총무가 승인하면 알려드릴게요" hint line under the disabled button (2026-09-16, `join-cta-band.tsx`) — not drawn here (this phone shows the initial 가입 신청하기 state only).
가입 신청하기
클럽 공개 프로필 · Public club
게시글
🙂
김민준
🌿
강남 테니스 클럽2시간 전
이번 주 토요일 모임 코트 변경됐어요. 3, 4번 → 5, 6번 코트로 바뀌었으니 착오 없으시길 바랍니다!
📅
연결된 일정토요 정기 모임 보러가기
❤️12
💬4
댓글4
🙂
이서연1시간 전
코트 변경 확인했습니다 감사합니다!답글
🙂
박도윤40분 전
5, 6번 코트 주차가 더 편하네요 👍답글
⚖️Rebuilt on the same system as every other flat detail page: TypeHero identity, one FlColumn for body→session-link→action-row→"댓글 N", comment rows as block peers (the one allowed peer divider), and the composer as DetailShell's pinned `footer`.
댓글을 입력하세요
게시글 · Post detail
9월
9월 1일 – 9월 30일
승률 62%
24승 9패 3무36경기
전체 복식 단식 클럽
+38ELO 변동
14일활동일
5연승연승
활동경기 수
지표 ▾
상대 전적상위 5
🙂
이서연4승 1패
80%
🙂
박도윤2승 2패
50%
더 보기
경기 목록36경기
필터(2) ▾
9월 28일 (일)
6–3김민준/이서연 vs 박도윤/최지우
복식+12
4–6김민준/박도윤 vs 이서연/최지우
복식−8
⚖️The summary is a typographic hero + donut on the canvas — no card. The activity calendar and opponent/match lists are spacing-only rows. The one navigation is calendar cell → day detail. No CTA.
기록 · Record
가입 심사
🙂
정하늘
게스트첫 참석 8월 3일4회 참석
근거
첫 참석
8월 3일
마지막 참석
8월 31일
함께 친 멤버
매너 태그
🙏 매너 3🎯 집중 1
추천
신청 메시지
토요 모임에 네 번 나갔는데 분위기가 정말 좋았어요. 정식으로 함께하고 싶습니다!
⚖️Attendance count folds into the hero meta line, not a badge — counts are meta text, never badges. A face stack fills a fact row's value slot. Approve:reject is a 2:1 ratio, both inactive while pending. 거절 gained a confirm gate 2026-09-17 ("(name)님의 신청을 거절할까요?", cancel/거절 destructive) before it fires — approve stays a single tap (reversible: remove the member); only reject notifies the applicant and closes the request for good.
거절
승인
가입 심사 · Applicant / join review
대진표
8월 30일 (토)
토요 정기 모임
8명2코트복식 로테이션
올림픽공원 테니스장
⚠️ 참가비 미정산 1명은 대진에서 제외돼요
1R 2R + 라운드 추가 팀 재배치
2라운드3/4 완료
코트 1김민준/이서연 vs 박도윤/최지우
진행 중
코트 2정하늘/강준호 vs 윤서아/임태현
6–4
⚖️A typographic hero + progress ring replaces the summary card. Round tools sit inline; only the lifecycle CTA (start match/end session) stays on the band. The match card is the one genuine box kept, because it's a real shape inside a list. 2026-09-17: a page-level "진행 중" `SessionLiveAnchor` band now mounts ABOVE the hero whenever a round is live (not drawn on this phone — see wireframes/live-anchor.jsx, the canonical board for it); separately, the "시작" CTA (not shown here — this session already has rounds) is disabled with a "대진을 먼저 확정해 주세요" caption above it until a confirmed draw exists (U-16b).
세션 종료
대진표 · Match board (+ tournament)
PALETTEPalette — one fixed hue, live in app tokens
Brand and gray ladders are OKLCH-defined at one fixed hue (152.7°, the brand's own hue) with ~even lightness steps; dark gray is its own designed ladder, not a reversal of the light ten hexes; dark-mode primary is the tone-80 step (#70D5A2). Live in packages/ui/src/tokens.ts and classic-grass.theme.ts.
Brand ladder
50#E5FAEA
100#C8EED2
200#9DDCAF
300#6EC489
400#3BA764
500#058A48
600#006D37
700#015228
800#003B1B
900#022711
Gray ladder — light
50#F8FBF9
100#F0F5F1
200#DFE7E1
300#CAD4CC
400#A8B5AB
500#7E8A80
600#626C64
700#4F5851
800#393F3A
900#232824
950#0D100E
Gray ladder — dark
50#090C0A
100#111512
200#1A201B
300#262D28
400#374039
500#515B53
600#707B72
700#919B94
800#B5BDB7
900#D7DCD8
950#EFF3F0
Semantic tokens — light / dark, with contrast on the canonical surface
tokenlightratiodarkratio
text
#0D100E
19.13
#EFF3F0
14.81
textSecondary
#4F5851
7.38
#D7DCD8
11.94
textTertiary
#626C64
5.46
#B5BDB7
8.63
border
#CAD4CC
#515B53
borderSubtle
#DFE7E1
#262D28
borderStrong
#7E8A80
3.60
#707B72
3.76
background
#F8FBF9
#090C0A
card
#FFFFFF
#1A201B
surfaceSecondary
#F0F5F1
#262D28
primary
#006D37
#70D5A2
onPrimary
#FFFFFF
6.48
#090C0A
10.98
primarySubtle
#E5FAEA
#262D28
HERO ANATOMYHero composition — the canonical anatomy
Every hero opens with SEAM.hero (16) under the chrome hairline or the media band — never the generic SEAM.chrome (12) — sits on the 16 text rail, and carries no bottom padding. The eyebrow spans the rail above the identity row, so a `leading` graphic top-aligns with the title, never the eyebrow.
히어로 해부
9월 7일 (토)
🙂
토요 정기 모임
모집중
14:00–17:00올림픽공원 테니스장
chrome hairline
16 = SEAM.hero, never SEAM.chrome (12)
leading 56 on the rail · 12 to text
no bottom padding
참여6명 확정
종목
복식
Spec (grounding, anatomy)

Detail pages render as flat components directly on the page canvas — no cards, no tinted ground — under one shared grammar of full-bleed heroes, spacing-only rows, and a single section divider. Decision history for every ruling behind this board is in docs/design/decision-log-detail-pages.md.

grounded in 31 source file(s)
packages/ui/src/detail-shell.tsxpackages/ui/src/place-shell.tsxpackages/ui/src/card.tsxpackages/ui/src/section-block.tsxpackages/ui/src/section-header.tsxpackages/ui/src/stat-tile.tsxpackages/ui/src/bottom-cta-band.tsxpackages/ui/src/well.tsxpackages/features/sessions/src/session-detail-screen.tsxpackages/features/sessions/src/session-detail/sections/info-section.tsxpackages/features/sessions/src/session-detail/sections/participation-section.tsxpackages/features/sessions/src/directory-venue-detail/directory-venue-detail-screen.tsxpackages/features/profile/src/profile-screen.tsxpackages/features/profile/src/profile-hero-card.tsxpackages/features/clubs/src/post-detail-screen.tsxpackages/features/records/src/record-detail-screen.tsxpackages/features/clubs/src/join-review-detail-screen.tsxpackages/features/sessions/src/match-board-screen.tsxpackages/features/clubs/src/public-club-profile-screen.tsxpackages/features/profile/src/public-profile-screen.tsxpackages/features/sessions/src/session-detail/actions/primary-action.tsxpackages/features/clubs/src/public-club-profile/join-cta-band.tsxpackages/features/sessions/src/session-detail/sections/session-hero-section.tsxpackages/ui/src/media-hero.tsxpackages/ui/src/type-hero.tsxpackages/ui/src/type-hero-visual.tspackages/ui/src/media-hero-visual.tspackages/ui/src/seams.tspackages/ui/src/tokens.tspackages/app/src/config/themes/classic-grass.theme.tsdocs/canon/components.md