designpaca/research/dogfood-saas/03-tokens.md
Yun Chan 8808c672dc designpaca 초기 구현 — 스킬 · 설치 CLI · 배포 파이프라인
웹 디자인 파이프라인 스킬과 이를 5개 에이전트에 설치하는 CLI 를 담은 모노레포.

스킬 (packages/skill)
- SKILL.md 261줄 + 참조 문서 16개 3,349줄. progressive disclosure 로
  본문은 절차와 인덱스만, 지식은 references/ 로 분리
- 0~6단계 파이프라인. 규모에 따라 전체·연장·국소 세 경로로 분기
- 하드 게이트 12개는 grep·카운트로 검증 가능한 것만. 취향 판단은 제외
- 미학 프리셋 5종, AI 슬롭 지문 목록, 한글 조판 규칙,
  SVG 필터·three.js·인터랙티브 모션·HTML-in-Canvas 실전 지침

설치 CLI (packages/cli, packages/core)
- npx designpaca 온보딩 TUI. Claude Code · Codex · Cursor · Windsurf · AGENTS.md
- 매니페스트에 설치 시점 해시를 기록해 사용자가 고친 파일은 update 가 건너뛴다
- 타깃별로 본문의 references/ 경로를 실제 설치 위치로 재작성
- AGENTS.md 는 항상 로드되므로 본문 대신 303자 포인터만 주입
- Windsurf 는 12,000자 상한 초과 시 설치를 차단

배포 (build/ci, .forgejo/workflows)
- 태그 v* → 검사·테스트·빌드 → npmjs 배포 + Forgejo 레지스트리 미러
  → draft 릴리스 → Cloudflare Pages. 재실행 멱등

근거 (research/)
- 약 250개 웹 소스 조사 결과와 도그푸딩 검증 2건. 스킬의 모든 수치는 여기서 나온다

테스트 22개 통과 (core 16 · cli 6)
2026-08-20 10:48:00 +09:00

160 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 3단계 — 디자인 토큰과 예산 (Pulsegate)
## ⚠ 작업 중 만난 충돌 — 타입 스케일
세 문서가 **산술적으로 동시에 만족 불가능한** 값을 준다.
| 문서 | 요구 |
|---|---|
| `presets/dark-instrument.md` | 비율 **1.200~1.250** (정보 밀도가 높으니 위계 차이를 작게) |
| `antipatterns.md` §2 | **최대÷본문 4배 이상** (17→68). 단계 **8개 이상은 통제 실패** |
| `reference-method.md` 축2 | 단계 **35가 건강** |
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초** (기본값, 상향 없음)