# tokens — 디자인 토큰과 성능 예산 3단계에서 읽는다. **코드를 쓰기 전에 숫자를 정한다.** 구현하면서 색을 고르면 매번 다른 색이 나온다. 토큰은 취향이 아니라 **계약**이다. 한번 정하면 이후 모든 값은 여기서 나온다. 하드코딩된 값이 하나라도 있으면 수정 요청 한 번에 무너진다. --- ## 1. 타입 스케일 ### 비율을 먼저 고른다 | 비율 | 값 | 인상 | 언제 | |---|---|---|---| | Minor Third | 1.200 | 차분, 조밀 | 정보 밀도가 높은 페이지, 문서, 대시보드 | | Major Third | 1.250 | 안정 | 기본값. 대부분의 마케팅 사이트 | | Perfect Fourth | 1.333 | 또렷한 위계 | 랜딩 페이지, 제품 소개 | | Golden | 1.618 | 극적 | 포트폴리오, 에디토리얼. 중간 단계가 비어 본문이 외로워진다 | 본문 scale은 하나의 비율에서 시작하면 관리하기 쉽다. display가 다른 비율이나 별도 크기를 요구할 수 있으므로, 토큰 이름·역할·대표 폭에서의 위계 검증을 함께 기록한다. ### 실제 값으로 적는다 ```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 — 디스플레이 */ } ``` ### 유동 크기와 구조 전환을 구분한다 ```css --step-4: clamp(2.25rem, 1.5rem + 3.75vw, 3.157rem); ``` `clamp(최소, 기준+vw, 최대)`는 연속적으로 변해야 하는 값에 유용하다. 최소·최대는 대표 콘텐츠로 확인한다. grid 열, 내비, 작업대처럼 구조가 바뀌는 지점에는 미디어쿼리나 container query를 쓰고, 임의 기기 폭이 아니라 실제 실패 지점에서 정한다. ### 함께 정해야 하는 것 | 토큰 | 규칙 | |---|---| | `--leading-tight` | 1.1~1.2부터 검토 — 디스플레이/헤드라인 | | `--leading-normal` | 1.5~1.6(라틴), **1.6~1.8(한글)**부터 검토 — 본문 | | `--measure` | 한 줄 길이의 시작 범위. 라틴 60~75자, **한글 25~40자** | | 자간 | 큰 라틴 디스플레이에서만 작은 음수 자간을 후보로 두고 실제 렌더로 확인. 한글은 글꼴·크기·문맥별로 시험하며, 음수값을 기본 처방이나 절대 금지로 두지 않는다 | 행간과 measure 범위는 시작값이다. 한글 서체·문자·폭·실제 문단을 렌더하고 확대 상태까지 확인해 정한다. 폰트 수는 전송량·언어 범위·위계·라이선스를 함께 보고 예산으로 정한다. 두 텍스트 패밀리와 필요 시 모노는 흔한 시작점이지만, 세 번째 패밀리가 무조건 실패라는 뜻은 아니다. **관찰 후보 — 라틴 측정폭 60자 하한은 고정선이 아니다.** 사이드바·카드처럼 컬럼 자체가 좁을 때는 45~59자 구간도 시작값으로 허용한다. 다른 스킬은 45~75자 범위를 제시하지만, 이 스킬의 60~75자·한글 25~40자 값을 대체하는 것이 아니라 좁은 컬럼에서만 여는 예외다 [SKILL-DESIGN-REVIEW]. > **어떤 폰트를 어떻게 고르고 싣는지는 `typography.md` 가 정본이다.** 여기서는 스케일 값만 정한다. > 로딩 전략, 폴백 메트릭 보정, OpenType 기능, 한글 서브셋, 라이선스 확인이 그 문서에 있다. --- ## 2. 색 ### 역할로 정의한다. 팔레트로 정의하지 마라 "파랑 5단계"가 아니라 **무엇에 쓰이는 색인지**로 정의한다. ```css :root { --surface: /* 페이지 바탕 */ --surface-raised: /* 카드·패널 (바탕과 구분되되 튀지 않게) */ --ink: /* 본문 텍스트 */ --ink-muted: /* 보조 텍스트 — 대비 4.5:1 유지 */ --line: /* 경계선 */ --accent: /* 강조 역할이 필요할 때의 기본 토큰 */ --accent-ink: /* 강조 위에 올라가는 글자색 */ } ``` > **색 체계의 램프 생성·OKLCH 파생·그라디언트 보간·다크모드 재조정·대비 수정 절차는 [color.md](color.md) 가 정본이다.** 여기서는 역할 토큰 7개만 정한다. 여기 없는 역할이 필요할 때(상태색을 더 세분하거나 채색 액션을 늘릴 때) 확장하는 방법은 color.md §8을 본다. ### 규칙 1. 강조 역할은 한 색에서 시작해 경쟁 여부를 검증한다. 여러 강조 역할이 필요할 수 있으며, 그때는 행동·상태·정보 목적을 역할 토큰으로 구분한다. 수만으로 위계 실패를 판정하지 않는다 2. 색 맥락 관찰은 [visual-design-lineage.md](./visual-design-lineage.md)를 참고한다. 이 스킬에서는 브랜드 강도·정보 위계·접근성의 근거로 색을 고르고, 실제 상태·면적·인접색에서 텍스트 대비와 판독성을 별도로 검증한다 3. **중성색도 색이다.** 순수 회색과 색조를 띤 중성색을 모두 후보로 두고, 표면·브랜드·상태색과의 관계를 비교한다 4. **대비를 측정해라.** 본문 4.5:1, 큰 글자 3:1. 눈으로 판단하지 마라 ### 다크 모드 **다크가 슬롭인 게 아니라 고르지 않은 다크가 슬롭이다.** (`antipatterns.md` §1 — bun.sh 는 다크인데도 슬롭 항목을 거의 전부 회피한다) 다크를 기본으로 하려면 **근거를 한 줄로 대라.** 정당화되는 이유의 목록은 `presets/dark-instrument.md` 에 있다(야간 운영 환경, 밝은 데이터 시각화의 대비, 제품 자체가 어두움). 댈 수 없으면 라이트로 간다. 라이트/다크 모두를 지원하기로 한 제품은 역할 토큰을 각각 정의하고 실제 상태를 검증한다. 제품이 한 테마만 제공할 수는 있지만, 사용자 환경·브리프·운영 맥락과 접근성 영향을 명시한다. 지원할 때는 색을 뒤집는 게 아니라 **역할별로 다시 정의**한다. 순수 검정·흰색과 근접한 색은 제품·디스플레이·주변광에 따라 읽기 경험이 다르므로, 대비·눈부심·브랜드 표면을 실제 기기에서 확인한다. ```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배 같은 고정 비율은 시작 가설일 뿐, 콘텐츠와 폭에 따라 조절한다. - 요소를 정렬할 때 **간격이 아니라 정렬선**을 먼저 맞춰라 ### 제목 역할이 실제로 구분되는지 확인한다 히어로 제목을 줄이다가 실측에서 이렇게 됐다. | | 크기 | | |---|---|---| | h1 (히어로) | 41.5px | `--step-4` | | h2 (섹션) | 33.2px | `--step-3` | **8px 차이.** 스케일상으로는 한 단계 위지만 화면에서는 같은 크기로 읽힌다. 페이지에서 가장 중요한 문장이 아홉 개의 섹션 제목과 구별되지 않았다. 원인은 과잉 교정이었다. 처음에 `--display`(1440px 에서 112px)를 썼다가 배경 그래픽과 싸워서 줄였는데, 줄이다가 h2 와 붙는 데까지 왔다. **한 극단에서 도망치다 반대쪽 극단에 도착한 것이다.** 넓은 화면에서 히어로와 섹션 제목이 같은 위계로 읽히지 않는지 비교한다. 2배 안팎은 한 프로젝트의 관찰값일 뿐 기준선이 아니다. 문구 길이, 구도, 여백, 브랜드 타입에 따라 같은 크기 차도 다르게 보인다. ### clamp 값을 역할 관계와 함께 확인한다 여기서 한 번 더 틀렸다. 데스크톱만 보고 고쳤더니 이렇게 됐다. ```css .hero-title { font-size: clamp(2.2rem, 1.3rem + 3.1vw, 4.4rem); } ``` | 화면 | h1 | h2 | 비율 | |---|---|---|---| | 1440px | 65.4px | 33.2px | 1.97 배 | | 1024px | 52.5px | 33.2px | 1.58 배 | | 390px | **35.2px** | 33.2px | **1.06 배** | **h1 만 `vw` 에 비례해 줄고 h2 는 고정이라, 좁아질수록 둘이 만난다.** 데스크톱에서 고친 위계가 모바일에서 그대로 사라진다. 모바일에서 h1과 h2가 거의 같은 크기로 수렴하지 않는지 확인한다. 아래 값은 해당 조합의 예시이며 모든 제목에 적용하는 비율 규칙이 아니다. ```css /* 하한 3rem = 48px = h2(33.2px)의 1.45 배 */ .hero-title { font-size: clamp(3rem, 1.9rem + 3.1vw, 4.4rem); } ``` | 화면 | 320 | 390 | 768 | 1024 | 1440 | |---|---|---|---|---|---| | 비율 | 1.45 | 1.45 | 1.63 | 1.87 | 2.12 | 좁은 화면에서 2 배를 고집할 필요는 없다 — 폭이 좁으면 큰 글자가 줄만 늘린다. 최소·중간·최대 폭에서 제목 역할이 구분되는지 확인하고, 필요한 경우 h1과 h2를 함께 유동화하거나 문구·레이아웃을 조정한다. > 확인법: 한 화면 폭에서만 재지 마라. `clamp` 를 쓴 값은 **최소 폭·중간 폭·최대 폭 > 세 곳에서 계산해 보고**, 같이 변하지 않는 값(고정 스케일의 h2 등)과의 비율을 봐라. > 그리고 눈으로 비교하지 말고 크기를 재라 — 배경이 화려하면 큰 글자도 작아 보인다. ### 제목 다음 간격은 전역에서 한 번 정한다 `h2 { margin: 0 }` 리셋과 `p { margin: 0 0 X }` 를 같이 쓰면 **제목과 본문이 붙는다.** 위쪽 여백이 양쪽 다 0 이기 때문이다. 눈에 띄게 깨져 보이지만, 섹션을 하나씩 만들다 보면 어떤 섹션에는 `.mat-lead { margin-block: var(--space-4) ... }` 처럼 손으로 붙게 되고 어떤 섹션에는 안 붙는다. 실측에서 한 페이지 안에 0px 인 섹션 둘과 24px 인 섹션 셋이 공존했다. **간격이 컴포넌트마다 다르면 그건 SSOT 가 아니다.** ```css p { margin: 0 0 var(--space-3); } /* 제목 다음에 오는 것은 반드시 떨어진다 — 여기 한 곳에서만 정한다 */ h1 + *, h2 + *, h3 + *, .head + * { margin-top: var(--space-4); } ``` 세 가지 함정이 있다. 1. **`:where()` 로 감싸면 특이도가 0 이 되어 위의 `p` 규칙에 진다.** `:where(h2) + :where(.lead)` 는 0-0-0, `p` 는 0-0-1 이다. 아무 효과 없이 조용히 무시된다. 특이도를 갖추고, 캐스케이드에서 `p` **뒤에** 둬라. 2. **제목을 `
` 로 감싼 섹션에서는 인접 형제가 끊긴다.** 그 섹션만 혼자 0px 이 된다. 제목 묶음 클래스도 같은 규칙에 넣어라. 3. **섹션이 각자 `margin-top` 을 또 붙이면 전역이 무력해진다.** 위쪽은 전역이, 아래쪽만 섹션이 정한다 — `margin-block: A B` 가 아니라 `margin-bottom: B`. ### 반복되는 리듬은 값이 아니라 토큰 하나로 같은 리듬을 세 곳에서 쓰면 세 곳이 따로 논다. 실측 사례 — 타임라인에서 정거장 패딩은 `--space-5`, 마커 원의 `top` 은 `calc(var(--space-4) + 0.55em)`, 연결선의 `top/bottom` 은 `--space-4` 로 각자 적혀 있었다. 패딩만 한 단계 올리자 **원이 제목보다 24px 위에 떠 버렸다.** ```css .spine { --stop-pad: var(--space-5); /* 리듬의 출처는 한 곳 */ } .spine::before { top: var(--stop-pad); bottom: var(--stop-pad); } .stop { padding-block: var(--stop-pad); } .stop::before { top: calc(var(--stop-pad) + 0.55em); } ``` 규칙: **같은 값을 두 번 이상 적게 되면 그 순간 지역 토큰으로 올려라.** --- ## 3-b. 형태 (radius·선) '스타일 휴리스틱 — radius 어휘를 3종 미만으로 제한한다'가 검사하는 대상이다. **정의하지 않으면 검사할 수 없다.** ```css :root { --radius-sm: 2px; /* 입력, 배지 */ --radius-md: 8px; /* 카드, 패널 */ --radius-pill: 999px; --line-width: 1px; } ``` **서로 다른 radius 값은 3종 미만으로.** 어휘가 많을수록 우연히 결정된 것으로 보인다. 1종만 쓰는 것도 정당한 선택이다 — `swiss-minimal`·`anti-grid` 에서는 오히려 그쪽이 맞다. ### 동심 radius — 중첩된 둥근 요소의 계산식 **관찰 후보 — 취향이 아니라 기하학이다.** 둥근 요소 안에 다른 둥근 요소를 넣을 때(배지를 담은 카드, 아이콘을 담은 버튼) 바깥 radius를 안쪽 radius와 무관하게 고르면 두 모서리의 곡률이 어긋나 인터페이스가 어색해 보인다. 바깥 radius는 **안쪽 radius + 안쪽 요소까지의 패딩**으로 계산한다. ``` outer-radius = inner-radius + padding ``` 예: 안쪽 배지가 `--radius-sm`(2px)이고 카드 패딩이 `--space-3`(16px)이면 카드는 지역 토큰으로 이렇게 계산한다. ```css .card { --card-radius: calc(var(--radius-sm) + var(--space-3)); border-radius: var(--card-radius); } ``` 동심 계산으로 파생한 지역 값은 radius 어휘(3종 미만)에 새 종류로 세지 않는다 — 기준 radius에서 계산으로 나온 값이다. 패딩이 24px를 넘으면 이 관계가 시각적으로 느슨해지므로, 그 지점부터는 바깥 radius를 독립적으로 다시 고른다 [SKILL-BETTER-UI]. 그림자·깊이 토큰과 radius를 함께 짝짓는 규칙은 [elevation.md](elevation.md) §3(radius·표면과 짝짓기)을 따른다. --- ## 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 을 쓰면 페이지가 불안해 보인다. **프로젝트 계약 — 새 duration 리터럴을 만들지 않는다.** 외부 레퍼런스나 라이브러리 프리셋이 다른 수치를 제시해도(예: 프레스 피드백 `scale(0.96)`~`scale(0.98)`) 그 값을 위 4단계 duration 토큰 중 가장 가까운 것에 매핑해 쓴다 — 프레스처럼 즉시 반응해야 하는 상태 변화는 `--dur-instant`가 기본 후보다 [SKILL-APPLE-DESIGN][SKILL-BETTER-UI]. 값 자체(0.96 대 0.98)의 차이는 실질적이지 않으므로 번호를 맞추려 하지 마라. **관찰 후보 — 드로어류에는 별도 이징 곡선을 조건부로 둘 수 있다.** iOS 스타일 드로어가 브리프의 요구일 때만 위 3단계 이징에 추가하지 않고 드로어 컴포넌트 스코프에 로컬로 `--ease-drawer`를 정의해 쓴다. 값과 적용 범위는 `references/motion.md`(§1 이징 각주)와 일관되게 유지한다 [SKILL-EMIL-DESIGN-ENG]. **프로젝트 계약 — `prefers-contrast`에도 반응한다.** WCAG 성공 기준은 아니지만, `prefers-reduced-motion`만 처리하고 대비를 더 원하는 사용자 설정을 무시하면 색 토큰이 그 사용자에게는 그대로 방치된다. `prefers-contrast`의 지원 상태와 판정 위계는 [accessibility.md](accessibility.md) §12가 정본이고, 램프를 실제로 재조정할 때 쓰는 색 쪽 기법(명도 우선 조정·재측정)은 [color.md](color.md) §6을 따른다 [SKILL-APPLE-DESIGN]. --- ## 웨이트와 컨트롤 — 자주 빠지는 두 축 타입 스케일·색·간격은 대부분 토큰으로 잡는다. **웨이트와 컨트롤 치수는 거의 안 잡는다.** 그래서 SSOT 가 거기서 먼저 깨진다. ### 웨이트는 세 개다 ```css --weight-body: 400; --weight-medium: 500; --weight-strong: 600; ``` 컴포넌트마다 `font-weight: 560` 같은 값을 직접 쓰기 시작하면 페이지에 웨이트가 여섯 종이 된다. 실측 사례: designpaca 소개 페이지가 `400 / 500 / 560 / 600 / 620 / 660` 을 동시에 쓰고 있었고, 그중 셋은 토큰에 없는 값이었다. 가변폰트는 660 같은 중간값도 그려준다. 그래서 더 위험하다 — **표준 축(400/500/600/700)을 벗어나면 힌팅이 흐려지고, 폴백 폰트에서는 아예 다른 굵기로 떨어진다.** **웨이트로 위계를 만들려 들지 마라.** R1 실측(zed.dev 모바일)에서 `font-weight: 400` 이 832회, 나머지 전부 합쳐 12회였다. 위계는 크기와 색이 만든다. ### 버튼처럼 생긴 것은 전부 같은 치수를 쓴다 ```css --control-h: 2.75rem; /* 프로젝트의 편안한 기본 높이 예시 */ --control-h-sm: 2.25rem; /* 36px — 헤더 등 조밀한 자리 */ --control-pad-x: var(--space-3); ``` 같은 역할의 컴포넌트는 치수 토큰을 공유한다. 다른 밀도나 위험도는 별도 역할 토큰으로 설명한다. WCAG 2.2 AA의 포인터 target 최소 기준은 예외를 포함해 24×24 CSS px이며, 44px는 많은 터치 UI에서 쓰는 편안한 시작값이지 보편 적합성 수치는 아니다 (`evidence-ledger.md`의 `WCAG-TARGET`). 실측 사례: 같은 페이지의 두 버튼이 높이 36 vs 44, 패딩 16 vs 8, 테두리 1px vs 0, 웨이트 400 vs 500 이었다. 규칙이 없으니 전부 달랐다. 반대로 R1 의 두 버튼은 `h36 · pad-x10 · r4 · w400 · 14px` 로 **픽셀 단위까지 같았다.** ### 떠 있는 헤더는 높이를 토큰으로 내놔야 한다 ```css --header-h: calc(var(--space-3) + var(--space-2) * 2 + var(--control-h-sm)); ``` `position: fixed` 헤더는 문서 흐름에서 빠져 있어서, 첫 섹션의 상단 여백과 앵커 목적지 (`scroll-margin-top`)가 그 높이를 알아야 한다. 값을 각자 손으로 맞춰두면 **간격 토큰을 `clamp()` 로 바꾸는 순간 관계가 끊어져 제목이 헤더 뒤로 들어간다.** 값이 아니라 관계를 토큰으로 둬라. ### 큰 간격은 화면에 반응해야 한다 작은 값은 고정값부터, 큰 공간은 `clamp()`부터 검토할 수 있다. 어느 쪽이든 콘텐츠·확대·컨테이너 조건에서 관계가 유지되는지 확인한다. ```css --space-6: clamp(2.5rem, 1.6rem + 2.6vw, 4rem); --space-7: clamp(3.5rem, 1.9rem + 4.6vw, 7rem); --gutter: clamp(2rem, 1.4rem + 1.8vw, 2.5rem); ``` 고정으로 두면 모바일에서 섹션 상하 여백 112px 이 그대로 들어가 **화면 높이의 26% 를 여백이 먹는다.** 좌우 여백도 마찬가지다 — R1 모바일 실측은 32px 이었다. --- ## 5. 성능 예산 **여기서 정한다. 구현 후에 재면 이미 늦었다.** **이 표가 성능 예산의 유일한 원본이다.** SKILL.md 도 `preflight.md` 도 여기를 가리킨다. 다른 문서에 예산 표를 만들면 값이 갈라지고, 갈라진 순간 아무도 어느 쪽이 맞는지 모른다. | 항목 | 기본 | 데모/포트폴리오 | 5단계에서 | |---|---|---|---| | 히어로까지 JS (gzip) | 150KB | 400KB | 측정 | | WebGL/3D 추가분 | +200KB | +600KB | 측정 | | 총 전송량 (첫 화면) | 1MB | 2MB | 측정 | | 첫 인터랙션 (모바일 4G) | 3초 | 5초 | 측정 | | LCP | field p75 2.5초 이하를 목표로 검토 | 브리프에 명시 | RUM/CrUX 또는 local proxy 표기 | | CLS | field p75 0.1 이하를 목표로 검토 | 브리프에 명시 | RUM/CrUX 또는 local proxy 표기 | | 폰트 **패밀리** | 프로젝트별 전송량·언어 범위 예산 | 브리프에 명시 | 실제 요청·전송량 확인 | | 폰트 전송량(첫 화면) | 100KB | 200KB | 측정 | | 애니메이션 속성 | compositing 친화 속성을 우선 검토 | 브리프에 명시 | 성능·reduced-motion·상태 피드백 확인 | 예산을 올렸다면 **올렸다는 사실과 이유를 명시**해라. 조용히 넘기는 것이 가장 나쁘다. > **폰트는 파일 개수가 아니라 패밀리 수와 전송량으로 센다.** 한글 웹폰트를 유니코드 범위별로 > 수십 개 파일로 쪼개는 방식은 실제 사용 문자 범위·캐시·전송 waterfall에 따라 유효할 수 있다. 파일 개수 자체로 최적화를 판정하지 않는다. > 브라우저는 페이지에 실제로 쓰인 글자 범위만 받는다. 파일 개수를 줄이라고 요구하면 > 한글 프로젝트를 단일 대용량 파일이라는 잘못된 방향으로 몬다. ### LCP·CLS 를 어떻게 재나 `preflight.md` 가 이 값을 요구한다. 재는 법이 없으면 "미측정"으로 남고, 미측정은 통과가 아니다. ```html ``` 프로덕션 빌드에서 재는 편이 개발 서버보다 배포 조건에 가깝다. 다만 PerformanceObserver·Lighthouse 한 번은 **lab proxy**다. CWV의 적합성 주장은 실제 사용자의 field data에서 모바일·데스크톱별 75th percentile로 판단한다. local 값은 회귀 탐지에 쓰고 field 수치와 혼동하지 않는다 (`evidence-ledger.md`의 `CWV`, `CWV-METHOD`). ### 예산을 지키는 기본 수단 - 폰트: 상세는 `typography.md` §3~§4. 요약하면 `font-display` 를 의식적으로 고르고, preload 는 첫 화면에 실제로 쓰는 파일만, 폴백 메트릭을 보정하거나 `optional` 을 쓴다 - 이미지: 실제 표시 크기의 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 /* 형태 — '스타일 휴리스틱 — radius 어휘를 3종 미만으로 제한한다'가 이걸 검사한다 */ --radius-*, --line-width /* 모션 */ --dur-*, --ease-* } ``` 그리고 한 줄로: **성능 예산 = JS ___KB / 첫 인터랙션 ___초 / 폰트 패밀리 ___개** **정의하지 않은 토큰은 5단계에서 검사할 수 없다.** 쓰지 않을 항목은 "쓰지 않음"이라고 적어라 — 빈칸과 "없음"은 다르다. > 근거: research/references/03-trends-2026.md, 04-ai-slop-signatures.md (조사일 2026-08-20)