# tokens — 디자인 토큰과 성능 예산 3단계에서 읽는다. **코드를 쓰기 전에 숫자를 정한다.** 구현하면서 색을 고르면 매번 다른 색이 나온다. 토큰은 취향이 아니라 **계약**이다. 한번 정하면 이후 모든 값은 여기서 나온다. 하드코딩된 값이 하나라도 있으면 수정 요청 한 번에 무너진다. --- ## 1. 타입 스케일 ### 비율을 먼저 고른다 | 비율 | 값 | 인상 | 언제 | |---|---|---|---| | Minor Third | 1.200 | 차분, 조밀 | 정보 밀도가 높은 페이지, 문서, 대시보드 | | Major Third | 1.250 | 안정 | 기본값. 대부분의 마케팅 사이트 | | Perfect Fourth | 1.333 | 또렷한 위계 | 랜딩 페이지, 제품 소개 | | Golden | 1.618 | 극적 | 포트폴리오, 에디토리얼. 중간 단계가 비어 본문이 외로워진다 | **한 페이지에 비율은 하나다.** 헤드라인만 다른 비율을 쓰고 싶으면 그건 비율이 아니라 **디스플레이 사이즈를 따로 정의**하는 것이다. ### 실제 값으로 적는다 ```css :root { /* Perfect Fourth (1.333), 본문 16px 기준 */ --step--1: 0.75rem; /* 12 — 캡션, 레이블 */ --step-0: 1rem; /* 16 — 본문 */ --step-1: 1.333rem; /* 21 — 리드 문단, 소제목 */ --step-2: 1.777rem; /* 28 — h3 */ --step-3: 2.369rem; /* 38 — h2 */ --step-4: 3.157rem; /* 51 — h1 */ --step-5: 4.209rem; /* 67 — 디스플레이 */ } ``` ### 반응형은 clamp 로, 미디어쿼리로 하지 마라 ```css --step-4: clamp(2.25rem, 1.5rem + 3.75vw, 3.157rem); ``` `clamp(최소, 기준+vw, 최대)`. 최소값은 **모바일에서 읽히는 크기**, 최대값은 데스크톱 기준. 중간이 매끄럽게 이어져 중간 뷰포트에서 깨지지 않는다. ### 함께 정해야 하는 것 | 토큰 | 규칙 | |---|---| | `--leading-tight` | 1.1~1.2 — 디스플레이/헤드라인 | | `--leading-normal` | 1.5~1.6 — 본문 (라틴) / **1.6~1.8 (한글)** | | `--measure` | 한 줄 길이. 라틴 60~75자, **한글 25~40자** | | 자간 | 큰 글자에만 음수(`-0.02em` 정도). **한글에는 음수 자간 금지** | **폰트는 최대 2종.** 세 번째 폰트를 넣고 싶다면 그건 위계를 웨이트로 못 만들고 있다는 신호다. --- ## 2. 색 ### 역할로 정의한다. 팔레트로 정의하지 마라 "파랑 5단계"가 아니라 **무엇에 쓰이는 색인지**로 정의한다. ```css :root { --surface: /* 페이지 바탕 */ --surface-raised: /* 카드·패널 (바탕과 구분되되 튀지 않게) */ --ink: /* 본문 텍스트 */ --ink-muted: /* 보조 텍스트 — 대비 4.5:1 유지 */ --line: /* 경계선 */ --accent: /* 강조 — 페이지당 하나 */ --accent-ink: /* 강조 위에 올라가는 글자색 */ } ``` ### 규칙 1. **강조색은 하나다.** 두 개가 필요하다고 느끼면 위계 설계가 실패한 것이다. 예외: 상태색(성공/경고/오류)은 강조색이 아니라 기능색이다 2. **채도가 높은 색은 면적을 좁게.** 넓은 면적에 쓰면 눈이 피로하고 싸구려로 보인다 3. **중성색도 색이다.** 순수 회색(`#808080`) 대신 강조색 쪽으로 약간 기운 중성색을 쓰면 화면 전체가 하나로 묶인다 4. **대비를 측정해라.** 본문 4.5:1, 큰 글자 3:1. 눈으로 판단하지 마라 ### 다크 모드 **다크가 슬롭인 게 아니라 고르지 않은 다크가 슬롭이다.** (`antipatterns.md` §1 — bun.sh 는 다크인데도 슬롭 항목을 거의 전부 회피한다) 다크를 기본으로 하려면 **근거를 한 줄로 대라.** 정당화되는 이유의 목록은 `presets/dark-instrument.md` 에 있다(야간 운영 환경, 밝은 데이터 시각화의 대비, 제품 자체가 어두움). 댈 수 없으면 라이트로 간다. 어느 쪽을 기본으로 하든 **두 테마를 동등하게 정의한다.** 다크만 만들고 라이트를 빼는 것은 사용자 선택권을 뺏는 것이다. 지원할 때는 색을 뒤집는 게 아니라 **역할별로 다시 정의**한다. 다크에서 순수 검정(`#000`)은 대비가 너무 세서 눈이 아프고, 순수 흰색 텍스트도 마찬가지다. ```css :root { --surface: #fbfaf8; --ink: #1a1917; } @media (prefers-color-scheme: dark) { :root { --surface: #14130f; --ink: #e8e4dc; } } ``` --- ## 3. 간격 ### 하나의 리듬에서 파생시킨다 ```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 — 섹션 간격 */ } ``` **중간 값을 즉석에서 만들지 마라.** `--space-4` 와 `--space-5` 사이가 필요하다면 스케일이 잘못된 것이다. ### 여백이 위계를 만든다 - 관련 있는 것끼리는 **가깝게**, 다른 그룹과는 **확실히 멀게**. 애매한 중간 간격이 가장 나쁘다 - 섹션 간격은 **본문 간격의 4배 이상**. 좁으면 페이지가 뭉개진다 - 요소를 정렬할 때 **간격이 아니라 정렬선**을 먼저 맞춰라 --- ## 3-b. 형태 (radius·선) 하드 게이트 #3 이 검사하는 대상이다. **정의하지 않으면 검사할 수 없다.** ```css :root { --radius-sm: 2px; /* 입력, 배지 */ --radius-md: 8px; /* 카드, 패널 */ --radius-pill: 999px; --line-width: 1px; } ``` **서로 다른 radius 값은 3종 미만으로.** 어휘가 많을수록 우연히 결정된 것으로 보인다. 1종만 쓰는 것도 정당한 선택이다 — `swiss-minimal`·`anti-grid` 에서는 오히려 그쪽이 맞다. --- ## 4. 모션 토큰 ```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); /* 위치 이동 */ } ``` 상세는 `references/motion.md`. 여기서는 **값을 고정**하는 것이 목적이다. 컴포넌트마다 다른 duration 을 쓰면 페이지가 불안해 보인다. --- ## 5. 성능 예산 **여기서 정한다. 구현 후에 재면 이미 늦었다.** **이 표가 성능 예산의 유일한 원본이다.** SKILL.md 도 `preflight.md` 도 여기를 가리킨다. 다른 문서에 예산 표를 만들면 값이 갈라지고, 갈라진 순간 아무도 어느 쪽이 맞는지 모른다. | 항목 | 기본 | 데모/포트폴리오 | 5단계에서 | |---|---|---|---| | 히어로까지 JS (gzip) | 150KB | 400KB | 측정 | | WebGL/3D 추가분 | +200KB | +600KB | 측정 | | 총 전송량 (첫 화면) | 1MB | 2MB | 측정 | | 첫 인터랙션 (모바일 4G) | 3초 | 5초 | 측정 | | LCP | 2.5초 | 3.5초 | 측정 | | CLS | 0.1 | 0.1 | 측정 | | 폰트 **패밀리** | 2개 이하 | 3개 | 개수 확인 | | 폰트 전송량(첫 화면) | 100KB | 200KB | 측정 | | 애니메이션 속성 | transform/opacity 만 | 동일 (타협 없음) | grep | 예산을 올렸다면 **올렸다는 사실과 이유를 명시**해라. 조용히 넘기는 것이 가장 나쁘다. > **폰트는 파일 개수가 아니라 패밀리 수와 전송량으로 센다.** 한글 웹폰트를 유니코드 범위별로 > 수십 개 파일로 쪼개는 것은 **올바른 최적화**다(Pretendard `dynamic-subset` 은 14개 이상). > 브라우저는 페이지에 실제로 쓰인 글자 범위만 받는다. 파일 개수를 줄이라고 요구하면 > 한글 프로젝트를 단일 대용량 파일이라는 잘못된 방향으로 몬다. ### LCP·CLS 를 어떻게 재나 `preflight.md` 가 이 값을 요구한다. 재는 법이 없으면 "미측정"으로 남고, 미측정은 통과가 아니다. ```html ``` **프로덕션 빌드에 돌려라.** 개발 서버는 번들이 다르고 HMR 스크립트가 섞여 값이 의미 없다. Lighthouse 를 쓸 수 있으면 그쪽이 더 정확하다 — 단 모바일 프로파일로. ### 예산을 지키는 기본 수단 - 폰트: `font-display: swap`, 서브셋(한글은 필수 — 전체 한글 폰트는 수 MB다), `preload` 는 실제로 첫 화면에 쓰는 것만 - 이미지: 실제 표시 크기의 2배까지만, AVIF/WebP, 첫 화면 밖은 `loading="lazy"` - JS: 첫 화면에 필요 없는 것은 전부 지연 로드. 3D는 뷰포트 진입 시 동적 import - **측정하지 않은 최적화는 하지 마라.** 대신 예산을 넘겼는지는 반드시 측정해라 --- ## 6. 산출물 형식 3단계를 마치면 아래가 실제 값으로 채워져 있어야 한다. 이것이 4단계의 입력이다. ```css :root { /* 타입 — 비율 ____ , 디스플레이는 스케일 밖 별도 정의 여부 ____ */ --step--1 ~ --step-5, --leading-*, --measure /* 색 — 역할별. 다크 테마도 함께 */ --surface, --surface-raised, --ink, --ink-muted, --line, --accent, --accent-ink /* 간격 */ --space-1 ~ --space-8 /* 형태 — 하드 게이트 #3 이 이걸 검사한다 */ --radius-*, --line-width /* 모션 */ --dur-*, --ease-* } ``` 그리고 한 줄로: **성능 예산 = JS ___KB / 첫 인터랙션 ___초 / 폰트 패밀리 ___개** **정의하지 않은 토큰은 5단계에서 검사할 수 없다.** 쓰지 않을 항목은 "쓰지 않음"이라고 적어라 — 빈칸과 "없음"은 다르다. > 근거: research/references/03-trends-2026.md, 04-ai-slop-signatures.md (조사일 2026-08-20)