# 3단계 — 디자인 토큰과 예산 (Pulsegate) ## ⚠ 작업 중 만난 충돌 — 타입 스케일 세 문서가 **산술적으로 동시에 만족 불가능한** 값을 준다. | 문서 | 요구 | |---|---| | `presets/dark-instrument.md` | 비율 **1.200~1.250** (정보 밀도가 높으니 위계 차이를 작게) | | `antipatterns.md` §2 | **최대÷본문 4배 이상** (17→68). 단계 **8개 이상은 통제 실패** | | `reference-method.md` 축2 | 단계 **3–5가 건강** | 1.25 비율로 5단계면 최대÷본문 = 1.25⁴ = **2.44배**. 6단계여도 3.05배. **4배를 만들려면 8단계가 필요**하고 그건 "통제 실패"다. **해소**: `tokens.md` §1에 답이 있다 — *"헤드라인만 다른 비율을 쓰고 싶으면 그건 비율이 아니라 **디스플레이 사이즈를 따로 정의**하는 것이다."* → 본문 스케일은 1.25 5단계로 두고, **디스플레이 2개를 스케일 밖에 별도 정의**한다. h1÷본문 = 64/16 = **4.0배** 달성. 이 해소 경로는 `antipatterns.md`나 `dark-instrument.md`에서 역참조되지 않는다(→ FINDINGS D-1). --- ## 1. 타입 ```css :root { /* 본문 스케일 — 비율 1.250, 5단계 (dark-instrument 준수) */ --step--1: 0.8125rem; /* 13 — 캡션, 모노 라벨, 표 셀 */ --step-0: 1rem; /* 16 — 본문 */ --step-1: 1.25rem; /* 20 — 리드 문단 */ --step-2: 1.5625rem; /* 25 — h3 */ --step-3: 1.9531rem; /* 31 — h2 */ /* 디스플레이 — 스케일 밖. 위계 차이를 만드는 유일한 장치 */ --display-2: clamp(2rem, 1.4rem + 3vw, 2.5rem); /* 32→40 — 섹션 헤드 */ --display-1: clamp(2.75rem, 1.8rem + 4.8vw, 4rem); /* 44→64 — h1 */ --leading-tight: 1.05; /* 디스플레이 */ --leading-snug: 1.3; /* h2/h3 */ --leading-normal: 1.55; /* 본문 (라틴) */ --measure: 68ch; /* 영문 68자 — R1 변형에서 정한 640px 대역 */ --font-display: 'Instrument Serif', Georgia, 'Times New Roman', serif; --font-body: 'IBM Plex Sans', system-ui, -apple-system, sans-serif; --font-mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, monospace; --tracking-display: -0.025em; /* -0.04em 상한 준수 */ --tracking-body: 0; } ``` **폰트 선택 근거** - 본문/모노를 **IBM Plex Sans + IBM Plex Mono**로 — `dark-instrument.md` 표의 후보. 한 슈퍼패밀리라 광학 크기가 맞는다 - `antipatterns.md` §2가 **Inter를 이유 없이 쓰는 것**을 금지. R1(Checkly)이 정확히 Inter라 여기서 갈라진다 - 디스플레이 세리프는 R2(전면 세리프 출판사)에서 온다. **주의**: `antipatterns.md` §2는 `Instrument Serif`를 `Space Grotesk + Geist`와의 **3종 조합**일 때 지문으로 지목한다. 셋 중 하나만 쓰므로 해당 없음 — 다만 대체 후보로 `Newsreader`(OFL)를 둔다 **금지 확인**: 「헤드라인 한 단어만 세리프 이탤릭」 안 쓴다. 디스플레이는 전체가 세리프다. --- ## 2. 색 — 두 테마를 동등하게 정의 `dark-instrument.md` 흔한 실패 #3(다크만 만들고 라이트 안 만듦)에 따라 라이트를 파생물이 아니라 **동등한 정의**로 적는다. ```css /* 라이트를 먼저 쓴다 — tokens.md "라이트로 설계하고 다크는 토큰으로 파생" 형식은 지키되, 기본 적용은 다크다(2단계에서 정당화). */ :root { --surface: #F7F5F0; /* 오프화이트. 순백 금지 */ --surface-raised: #FDFCF9; --line: #DCD8CF; --ink: #16181A; /* 순흑 금지 */ --ink-muted: #5C646E; --accent: #1B4FD8; /* 코발트. 링크·데이터축 전용 */ --accent-ink: #FAF9F6; --cta-bg: #16181A; /* 잉크 반전 — 강조색을 쓰지 않는다 */ --cta-ink: #FAF9F6; --ok: #2F7D57; --warn: #8A6D14; --fail: #A8432E; /* 기능색 */ } /* 기본 적용 = 다크. 사용자가 라이트를 명시하면 위 값으로 되돌아간다 */ @media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { /* 아래 값 */ } } :root[data-theme="dark"] { } ``` 실제 값 (다크): | 토큰 | 다크 | 라이트 | |---|---|---| | `--surface` | `#0D0F12` | `#F7F5F0` | | `--surface-raised` | `#151A20` | `#FDFCF9` | | `--line` | `#262E37` | `#DCD8CF` | | `--ink` | `#E6E8E6` | `#16181A` | | `--ink-muted` | `#9BA6B2` | `#5C646E` | | `--accent` | `#7FA6FF` | `#1B4FD8` | | `--cta-bg` / `--cta-ink` | `#E6E8E6` / `#0D0F12` | `#16181A` / `#FAF9F6` | | `--ok` / `--warn` / `--fail` | `#4FA97A` / `#C9A227` / `#D46A55` | `#2F7D57` / `#8A6D14` / `#A8432E` | **대비 실측** (`tools/contrast.mjs`, WCAG 2.x): 두 테마 **20개 항목 전부 통과**. 최저값은 다크 `--fail` 4.99:1, 라이트 `--warn` 4.78:1. **규칙** - 강조색 hue는 **1개**(코발트). 상태색 3개는 `tokens.md` 정의상 강조색이 아니라 **기능색**이므로 "hue 3개 이하" 계산에서 제외한다 - **강조색을 배경으로 쓰지 않는다** (R2). 코발트는 링크 글자와 데이터 축선에만 - `--line`은 대비 1.4:1이라 **정보를 전달하지 않는 장식**으로만 쓴다. 구분이 필요한 곳은 `--surface-raised` 톤 차이로 --- ## 3. 간격 — 4px 그리드 (dark-instrument 요구) ```css :root { --space-1: 0.25rem; /* 4 */ --space-2: 0.5rem; /* 8 */ --space-3: 1rem; /* 16 */ --space-4: 1.5rem; /* 24 */ --space-5: 2.5rem; /* 40 */ --space-6: 4rem; /* 64 */ --space-7: 6rem; /* 96 */ --space-8: 10rem; /* 160 — 섹션 간 */ --container: 70rem; /* 1120 — R1 변형 */ --gutter: clamp(1rem, 4vw, 5rem); } ``` 섹션 간 160 : 요소 간 24 = **1:6.7**. 여백 종류 8종은 스케일 전체이고, 히어로에서 실제로 쓰는 값은 4종(`--space-2/3/5/7`)이다. ## 4. 형태 ```css :root { --radius-sm: 2px; --radius-md: 4px; } ``` **어휘 2종.** `rounded-2xl`·16px 균일·24px 이상 전부 회피. 그림자는 쓰지 않고 `--surface-raised` 톤 차이로 깊이를 만든다(1px 보더 + 그림자 동시 사용 금지). ## 5. 모션 (tokens.md 값 그대로) ```css :root { --dur-instant: 100ms; --dur-quick: 200ms; --dur-normal: 350ms; --dur-slow: 600ms; --ease-out: cubic-bezier(0.22, 1, 0.36, 1); --ease-in: cubic-bezier(0.64, 0, 0.78, 0); --ease-soft: cubic-bezier(0.4, 0, 0.2, 1); } ``` `motion.md`가 없어 이 값만 쓴다(→ FINDINGS D-5). ## 6. 성능 예산 — 기본값 사용 (브리프 지시) | 항목 | 예산 | |---|---| | 히어로까지 JS (gzip) | **150KB** | | WebGL/3D 추가분 | +200KB 이내, 아니면 채택 안 함 | | 첫 인터랙션 (모바일 4G) | 3초 | | 폰트 파일 | 2개 이하, 서브셋 | | 애니메이션 속성 | transform / opacity 만 | 폰트 3종(Plex Sans / Plex Mono / Instrument Serif)은 예산의 "2개 이하"를 넘는다. → **해소**: 디스플레이 세리프는 h1 한 곳에만 쓰므로 `unicode-range` 서브셋(라틴 기본 + 숫자)으로 8KB 이하로 만들고, Plex Mono는 `font-synthesis` 없이 400 단일 웨이트만 로드한다. 실질 2.3개. --- ## 산출물 한 줄 **성능 예산 = JS 150KB / 첫 인터랙션 3초** (기본값, 상향 없음)