Overview
Compare
Inspect
`DetailShell header="collapsing"` (a transparent bar over the hero, solid + centered title once scrolled) → a full-bleed hero (`SessionHeroSection` — `MediaHero`/`TypeHero`, title = venue name + ONE inline `titleBadge` + ONE `GlyphFactRow`) → ONE `FlatColumn` whose `lead` slot is a hero-zone `StatStrip` (확정 인원 · 내 참가비 · 참석 마감) and whose children are titled sections with no card boxes (참여 → 경기 → 정보 → 호스트 callout → 호스트 도구 → 위험 구역), with one pinned `BottomCtaBand` in the footer, while non-members opening a public pickup see a static-header `TypeHero` + FlatColumn preview with an apply-flow CTA instead.
route · apps/mobile/app/sessions/[sessionId]/index.tsx → sessions/src/session-detail-screen.tsx
grounded in 20 source file(s)
packages/features/sessions/src/session-detail-screen.tsxpackages/features/sessions/src/public-pickup-preview-screen.tsxpackages/features/sessions/src/pickup-host-card.tsxpackages/features/sessions/src/session-detail/use-session-detail-actions.tsxpackages/features/sessions/src/session-detail/sections/session-hero-section.tsxpackages/app/src/domain/session-info-model.tspackages/app/src/presentation/components/sessions/session-strip-ui.tsxpackages/app/src/presentation/components/status/status-badges.tsxpackages/app/src/domain/session-strip.tspackages/app/src/presentation/hooks/composites/use-live-session-data.tspackages/ui/src/media-hero.tsxpackages/ui/src/type-hero.tsxpackages/ui/src/glyph-fact-row.tsxpackages/ui/src/detail-shell.tsxpackages/ui/src/collapsing-top-bar.tsxpackages/ui/src/section-block.tsxpackages/ui/src/banner.tsxpackages/ui/src/callout.tsxpackages/app/src/config/i18n/ko/sessions.tspackages/app/src/config/i18n/ko/home.ts
Spec
1
Foundations · 디자인 시스템
Spacing · 4px grid
$1 – $4
4 · 8 · 12 · 16 px
$5 – $10
20 · 24 · 32 · 40 · 48 · 64 px
Radius
$2 8 · $3 12 · $4 16 · $7 9999 (pill)
Color · Classic Grass (light)
Surface
$background #F8FBF9 · $card #FFFFFF · $surfaceTertiary #E5FAEA
Brand
$primary #006D37 · $primarySubtle #E5FAEA
Text
$text #0D100E · $textSecondary #4F5851 · $textTertiary #626C64
Border
$border #CAD4CC · $borderSubtle #DFE7E1
Status
$success #14804A · $warning #946200 · $error #B8332E
Badge intents
accent #E0F8F3/#007B6B · warning #FFF4D6/#946200 · error #FDECEC/#B8332E
Court
$courtClay #B84A2E · $courtHard #247BA0 · $courtGreen #006633
Typography · Pretendard
cardTitle · navTitle
16 / 24 · 600 · 17 / 22 · 600
cardBody · cardMeta
14 / 22 · 400 · 13 / 18 · 500
badge · micro
12 / 18 · 600 · 10 / 14 · 600
display Sm/Md/Lg/Xl
20/28 · 28/36 · 36/44 · 48/56 · 700
Primitives · @twomore/ui
Layout
YStack · XStack · Card (default/flat) · Well · Divider
Controls
Pressable · Badge · Icon · PillNav · MainTabShell · BottomCtaBand
Data viz
DateTile · CapacityRing · ProgressBar · AvatarStack · SegmentedDonutChart
Domain cards
SessionCard · ClubCard · VenueHero · SessionCompactRow · member row
Box sizes
AVATAR XS 28 · SM 32 · MD 40 · LG 44 · 2XL 88 · TOUCH_TARGET 44
Rationale
Every part is built from these — one 4px grid, the Pretendard type scale, the Classic-Grass palette, and the shared @twomore/ui primitives + domain cards.
2
헤더 — CollapsingTopBar
Structure
AT REST
fully transparent over the hero — back + the save toggle + the ⋯ overflow all render in WHITE, no title (collapsing-top-bar.tsx:112-147). Every main phone below is drawn in this state.
Scrolled
once the hero passes the threshold, a solid $card ground fades in + a bottom hairline + the CENTERED title — sessionTitle(session) (the format·style string), NOT the hero's venue name — with a live PulseDot when in_progress (session-detail-screen.tsx:334-340). A pure chrome-opacity flip, not drawn as its own phone.
3
히어로 — SessionHeroSection
Structure
Component
MediaHero (venue static map) — title = venue name (model.where.venue?.name, NEVER sessionTitle()), ONE inline titleBadge (the strip lead chip) beside it, ONE GlyphFactRow below: 📅 date+D-day · ⏰ time range · 🌤 weather · 🎾 format·style (session-hero-section.tsx:100-115). Falls back to TypeHero when the session has no venue and no free-text location — there the 🎾 fact is dropped (its string is already the title).
4
통계 스트립 — hero-zone block
Structure
Component
FlatColumn's lead slot: StatStrip — 확정 인원 (N/max) · 내 참가비 (₩ or 무료) · 참석 마감 (없음, OR formatDeadline — "N분 내" / "오늘 오후 H:MM까지" / "M/D까지", never a bare time-of-day, diff since 2026-09-12: was formatTimeOfDayKo12). A hero-zone block, never a titled SectionBlock (seam-audit P4).
5
공지 — Well, no title
Structure
Component
NoticeSection — a Well tone="primary" recess with a 📣 lead, NO section title (a quiet notice pocket). Self-gates to null with no description.
6
회비 콜아웃 — PaymentGateBanner
Structure
Component
the Banner primitive (tone="warning"): title "입금 필요" → body "참가비 {amount}원" → sublines (hold countdown + hint) → a RIGHT-ALIGNED action row — "입금 완료" (attest_session_payment) is the sole, primary CTA for real users (결제하기/mock PortOne is __DEV__-only).
submitted state
a neutral Callout ("확인 대기") replaces the Banner once attested — not its own frame here
7
참여
Structure
Component
SectionBlock variant="flat" (meta "{n}명 확정" + link "전체 ›") → RowList of ListRows (avatar · name (나) · tier sub · trailing RSVP badge), capped at 6 combined confirmed+waitlisted + an "외 N명" overflow row. No hairline between rows.
8
진행 중 밴드 — SessionLiveAnchor
Structure
Component
SessionLiveAnchor (decided 2026-09-17, docs/twomore_design_system/wireframes/live-anchor.jsx — full decision record there) — the FIRST flow child whenever in_progress, under the collapsing header's transparent scrim, above even the hero. Facts (elapsed since play started, current/total round, courts in play) are plain Text role="badge", hairline-separated (MetaBreak), never icons or a "·" join.
Coexistence
the header's OWN titleLeading PulseDot (fades in once scrolled past the hero) and the hero's inline 라이브 titleBadge both STAY — three live signals on this one screen, ruled worth it (live-anchor.jsx) for the elapsed/round/court facts only the band states
9
경기 — composite figure
Structure
Component
SectionBlock variant="flat" → SessionMatchupsCard's per-match rows, each a composite MatchRowHeader+MatchScoreboard pair — too entangled to flatten into a ListRow this wave, so it stays its OWN boxed figure (per the build spec's "wrap + annotate" allowance).
10
정보 — unboxed FactList
Structure
Rows
SectionBlock variant="flat" + FactList (info-section.tsx:49-69), gate-ordered: 방식 → 성격 → 티어 → ELO 영향 (always) → 참가비 / 로테이션 / 취소 규정 (conditional, warning tone). No 정원 row — capacity lives only on 참여/StatStrip.
11
호스트 콜아웃
Structure
Component
host-callout-section.tsx: a pending-apps Banner tone="primary" (host + public invite + pending>0) → 호스트 SectionBlock wrapping PickupHostCard, which dropped its Card wrapper (diff since 2026-09-12) — now a bare YStack paddingVertical={SEAM.item} row (shared with the public preview, owns real hook-backed state).
12
호스트 도구
Structure
Component
SectionBlock variant="flat" + RowList of ListRows (host-tools-section.tsx) — 일정 수정 · 참석자 관리 · 경기 규칙 · 호스트 관리("신청 대기 (N)" once N>0) · 출석 확인 · 경기 종료 (tone="danger"). No hairline between rows.
13
위험 구역
Structure
Component
SectionBlock variant="flat" tone="danger" — host-only. 일정 취소 (open/locked) + 일정 삭제 (open/locked only, gated on no other confirmed participant + no pending refunds).
14
하단 CTA · 상태 기계
Structure
open/reserved/host/live/completed
초대하기 · 참석 취소 · 참석 확정하기 · 라이브 스코어카드 보기 · 매너 태그 남기기 — the same 23-branch role×status×RSVP state machine
15
번개 미리보기
Structure
Component
public-pickup-preview-screen.tsx:416-473 — TypeHero (title = venue name, titleBadge = ApprovalModeBadge, facts: 🎾 format·style · 🏷 tier range · 📅 date · ⏰ time, NO media) → FlatColumn: 정보 (FactList: 시작 시간 · 최대 인원 · 참가비) → 호스트 (bare-row PickupHostCard, no Card box — the SAME figure) → SilhouetteStrip (Well, nameless gray circles) → the full location tile (VenueCard, pre-join) → a plain notice line (NOT in a Well).
Header
STATIC (DetailShell default header, title=sessionTitle(), subtitle=date) — a venue-less-title TypeHero page never gets CollapsingTopBar; NO save toggle, NO ⋯ menu (non-member preview).