# MDS 소비 가이드

미리디 디자인 시스템(MDS) V3를 로컬 모듈로 추출한 결과물의 사용 설명서.
"MDS로 만들어" 명령 시 **이 저장소만 참조**해 동일하게 재현한다. Figma를 다시 열지 않는다.

- 정본(원천): `~/Documents/mds/` (로컬)
- 원본: Figma V3.MDS.Foundation(`KEJ2XDl199HMabeIdj2pP7`) + V3.MDS.Components(`8AEfbEmjGVV67do9H3URy8`)
- 추출 방식: **Figma REST API 전수 스냅샷**(component_set·components·styles·노드 트리)이 정본이고, MCP `get_variable_defs` 원자료를 함께 쓴다. 둘 다 읽기 전용. 변수 API(`/variables/local`)는 Enterprise 전용이라 403이며, 값은 노드의 `boundVariables`·style 참조에서 역산했다.
- 원격 미러: claude.ai/design 프로젝트 `V3.MDS — 미리캔버스 디자인시스템`. 로컬이 원천이고 원격은 미러다.

## 구성

| 경로 | 내용 |
|---|---|
| `manifest.json` | 원천 전수 목록(변수·스타일·컴포넌트)과 추출 상태 |
| `tokens/index.css` | 전 토큰(1382)의 CSS 커스텀 프로퍼티(`--mds-*`). **소비 진입점** |
| `tokens/<category>.json` | 카테고리별 원천 값(color/typography/spacing/radius/sizing/shadow/opacity) |
| `tokens/dark.css` | 다크 테마 파생 토큰(584). `:root[data-theme="dark"]` |
| `tokens/_dark-derivation.json` | 다크 토큰별 유도 규칙·근거 |
| `tokens/_divergences.json` | 변수↔style 값이 갈린 항목과 원자료 내 다수결 기록 |
| `components/<name>/spec.md` | 컴포넌트별 Props·Variants·States·Tokens·Usage 명세(47종) |
| `icons/svg/*.svg` | 아이콘 707개. `icons/manifest.json` 으로 검색, `icons/inline.py` 로 인라인 |
| `icons/sprite.svg` | 원격용 스프라이트(707 심볼). `icons/build-sprite.py` 로 재생성 |
| `tokens-gallery.html` | 토큰 전수 갤러리(Foundation만) |
| `components-showcase.html` | Foundation·Icons·Components 통합 쇼케이스. 토큰 기반 시각 렌더 있는 컴포넌트는 실제 렌더, 없는 건 "구조형"으로 표시. spec.md에서 기계적으로 뽑은 Overview·Props 전량 포함 |
| `_raw/tokens/*.json` | 컴포넌트/페이지별 `get_variable_defs` 원자료(감사·재빌드용) |
| `_raw/rest-audit/` | Figma REST 전수 스냅샷·props 매트릭스·스케일·대비 보고서 |
| `_raw/publish-evidence/` | DesignSync 등록 재조회 증거 |
| `checks/` | 빌드·검증 스크립트 |

## 토큰

3계층 네임스페이스. 값은 Figma 실측이며 추정 없음.

- **`b/` 프리미티브(base)** — 원자값. Foundation 문서 표에서 전수를 떴기 때문에 컴포넌트가 소비하지 않는 것까지 있다(breakPoint 9·duration 8·opacity 18·borderWidth 5·색 램프 12계열). `b/fontSize/16`(16), `b/spacing/40`(8), `b/radius/pill`(999), `b/color/static/white`, `b/fontFamily/body/default`(Pretendard Variable). 직접 쓰기보다 시맨틱을 경유하는 게 원칙.
- **`s/` 시맨틱(system)** — 의미 기반. 소비 시 **1순위로 참조**.
  - color: `s/color/brand/primary/*`(#21afbf 계열), `s/color/neutral/*`(contents/background/surface/border/static, 40종), `s/color/semantic/*`(normal·normalDeep·negative, 12종), `s/color/visualization/*`(그래프 팔레트 30종).
  - typography: `s/typography/display/*`(7), `title/*`(7), `body/{default,strong,stronger}/*`(20), `label/*`(3). 폰트는 Pretendard Variable, size 10~96, weight regular(400)/medium(500)/semiBold(600)/bold(700).
- **`c/<component>/` 컴포넌트 스코프** — 특정 컴포넌트 전용. 예 `c/button/size/container/M`(40), `c/dropdownField/color/*`, `c/menu/*`. 해당 컴포넌트를 만들 때만 사용.

CSS 변수명 규칙: 원본 이름을 소문자화 → 공백·슬래시를 `-` → 기타 비영숫자 `-` → 연속 `-` 축약, 앞에 `--mds-`.
예) `s/color/brand/primary/default` → `--mds-s-color-brand-primary-default`.

```css
@import "tokens/index.css";

/* 채움 버튼 하나에 규칙이 다 들어간다.
   높이는 컴포넌트 규격, 타이포는 font 단축, 수치 토큰은 calc(*1px), 색은 토큰 그대로. */
.cta {
  height: calc(var(--mds-c-commonbutton-size-height-m) * 1px);   /* 40 */
  padding: 0 calc(var(--mds-b-spacing-70, 16) * 1px);
  border-radius: calc(var(--mds-b-radius-40) * 1px);             /* 8 */
  background: var(--mds-s-color-brand-primary-default);
  color: var(--mds-b-color-static-white);
  font: var(--mds-s-typography-body-strong-m);                   /* 500 14px/20px */
  box-shadow: var(--mds-b-shadow-10);
}
.cta:disabled { opacity: calc(var(--mds-s-opacity-disabled-default) / 100); }  /* 50% */
.cta:focus-visible { outline: 2px solid var(--mds-c-input-color-border-focused); outline-offset: 2px; }

/* 브랜드색을 '텍스트'로 쓸 때는 accessible 쪽이다(대비 2.64 → 3.58). */
.link { color: var(--mds-s-color-brand-primary-accessible); }
```

## 컴포넌트

**spec 47종**이 Figma published `component_set` **88개 전수**를 덮는다. 스펙 하나가 여러 세트를 묶는 경우가 있다(예: Button = common·iconOnly·text·elevated·commonWidth 5세트). 각 `components/<name>/spec.md`에 5개 필수 절(Props/Variants/States/Tokens/Usage)과 근거 node id, 그리고 REST 실측 props 전수표가 있다.

커버리지는 `checks/check-figma-parity.py`가 매번 대조한다. 88세트 중 하나라도 스펙에 근거가 없으면 검사가 실패한다.

컴포넌트 spec 읽는 법:
- **Props** = variant 심볼명 `prop=value`에서 도출한 속성 축.
- **Variants** = 축의 조합(전 데카르트 곱이 아닌 published 부분집합인 경우 명시됨).
- **States** = `state=`/`n-state=` 축(default/hover/focus/disabled/error/loading 등). 상호작용 축이 없으면 "해당 없음"으로 근거와 함께 기록.
- **Tokens** = 그 컴포넌트가 실제 소비하는 토큰(실측값).
- **Usage** = 기본값·크기·상태 처리 규칙.

## 사용 규칙

1. **토큰 우선순위**: `c/` > `s/` > `b/`. 컴포넌트를 만들 땐 그 컴포넌트의 `c/` 토큰을, 일반 UI는 `s/` 시맨틱을 쓴다. `b/` 직접 참조는 시맨틱이 없을 때만.
2. **창작 금지**: MDS에 없는 색·크기·컴포넌트를 임의로 만들지 않는다. 필요한 값이 없으면 spec의 "해당 없음/확인 필요" 표기를 존중하고 사용자에게 확인한다.
3. **disabled**: 컴포넌트별로 방식이 다르다. Button은 전체 opacity `s/opacity/disabled/default`(50%), Radio/Checkbox는 전용 disabled 색 토큰. 각 spec의 States/Usage를 따른다.
4. **shape=round**: radius를 `c/*/radius/round` 또는 `b/radius/pill`(999)로.
5. **버전 표기**: Figma 원본에서 `mds-` 접두사는 주로 현행 명명이고, Example의 `(V1)` 라벨이 실제 레거시 신호다. spec에 deprecated로 표기된 세트는 신규 화면에 쓰지 않는다.
6. **원본 오타 보존**: `Pannel`, `buleFillLight`, `charteuse`, 공백 포함 토큰명(`c/ buttonGroup/*`) 등은 Figma 원본 그대로다. 정규화는 CSS 변수 생성 시에만 적용된다.
7. **재현 절차**("MDS로 만들어"): ① `tokens/index.css` import → ② 대상 컴포넌트 `spec.md`의 Props/States로 구조 결정 → ③ Tokens 절의 토큰으로 스타일 → ④ `components-showcase.html`의 해당 컴포넌트 렌더와 대조.
8. **재빌드**: 토큰 원자료(`_raw/tokens/*.json`)를 고치면 `python3 checks/build-tokens.py`로 `tokens/`를, `python3 checks/build-manifest.py`로 `manifest.json`을 재생성한다.
9. **수치 토큰에는 단위가 없다**: `--mds-b-radius-40: 8`처럼 숫자만 들어 있다. CSS에서 쓸 때 `calc(var(--mds-b-radius-40) * 1px)`로 감싼다. 색 토큰은 그대로 쓴다.
10. **알파 채널을 지우지 않는다**: 색 토큰 상당수가 8자리 hex(`#58627617` = 9% 투명)다. 앞 6자리만 취하면 투명해야 할 배경이 불투명 덩어리가 된다. 값을 손으로 줄이지 말고 토큰을 그대로 참조한다.
11. **타이포 토큰은 `font:` 단축속성**이다. `font: var(--mds-s-typography-body-default-m)` 한 줄로 굵기·크기·행간·폰트스택이 다 적용된다. `font-size`에 넣으면 안 된다.
12. **아이콘은 `icons/inline.py`의 `icon()`으로만 넣는다.** SVG 원본이 본체를 `fill="black"`으로 굳혀 놓아서 그대로 붙이면 어떤 색 토큰을 줘도 검게 나오고, 반대로 모든 `fill`을 `currentColor`로 바꾸면 투톤의 밝은 면과 흰색 뚫기까지 메워져 덩어리가 된다. 헬퍼가 세 갈래로 처리한다 — 본체색(`black`·`#16181D`·`#23242A`)만 `currentColor`, 구조색(`none`·`white`)과 브랜드색(TikTok `#00F2EA` 등, 그라디언트 `url(#…)`)은 보존, `fill-opacity`는 손대지 않는다.
    - 검색은 `icons/manifest.json`. 세트에 없는 아이콘(예: minus)은 그리지 않고 문자 글리프(`−`)를 쓴다.
    - **이름이 뜻을 배신하는 것들**: `icon_line`은 선이 아니라 LINE 메신저 로고, `icon_main_menu`는 햄버거가 아니라 점 격자(햄버거는 `icon_menu`), `icon_project`는 외곽선이 아니라 채워진 서랍(레일용 외곽선은 `icon_box_twotone`).
    - **좌측 레일 아이콘은 `tabMenu` 그룹**(20종 `*_twotone`)이 전용 세트다. 범용 아이콘 대신 이쪽을 먼저 찾는다.
    - **원격(claude.ai/design)에는 `icons/sprite.svg` 하나로 올라간다.** `<svg width="20" height="20"><use href="icons/sprite.svg#icon_search"/></svg>` 로 쓴다. 707개 심볼이 들어 있고 색 처리는 로컬 헬퍼와 같은 규칙이다. 재생성은 `python3 icons/build-sprite.py`. 스프라이트 루트를 `display:none` 으로 숨기면 `<use>` 가 심볼을 복제하지 못해 아무것도 안 그려진다 — `position:absolute;width:0;height:0` 으로 숨긴다.
13. **선택·포커스 색은 `s/color/brand/primary/light`(#26c7d9)**다. 채움·강조는 `primary/default`(#21afbf). 실제 제품 CSS에서 확인된 구분이다.
14. **기본 텍스트색은 `#16181d`**, 폰트 스택은 `"Pretendard Variable", Figtree, "IBM Plex Sans JP", "Pretendard JP Variable", "Pretendard Std Variable"`. 제품(www.miricanvas.com) 실측이며 타이포 토큰에 이미 반영돼 있다.

15. **상태(state)는 spec의 State 절과 props 매트릭스가 정본**이다. hover·focus·pressed·disabled·error·readOnly를 임의로 만들지 않는다. 상태 축 이름이 세트마다 `state` 또는 `n-state`로 갈리니 둘 다 확인한다(한쪽만 보면 Switch처럼 상태를 통째로 놓친다). `components-showcase.html`은 정적 대표 상태만 렌더하며 hover·pressed 등 인터랙션 상태의 시각 예시는 아직 없다 — 그 축은 spec.md 텍스트가 유일한 정본이다.
16. **포커스는 브라우저 기본 링을 쓰지 않는다.** MDS는 `c/input/color/border/focused`(#26C7D9)로 규정한다. 크롬 기본값은 `rgb(0,95,204)`이며 이게 보이면 스타일이 빠진 것이다. `:focus-visible`로 걸어 키보드 포커스에만 보이게 한다.
17. **그림자는 토큰을 쓴다.** `box-shadow: var(--mds-b-shadow-10)` 처럼 바로 넣을 수 있게 해석돼 있다(원본은 `Effect(...)` 문자열이라 그대로는 못 쓴다). 용도별로 `b/shadow/10`(기본), `s/shadow/popover`(떠 있는 패널), `c/elevatedIconButton/shadow/2XL`(떠 있는 버튼). `rgba()` 하드코딩 금지.
18. **자간은 건드리지 않는다.** MDS엔 letterSpacing 토큰이 없고 타이포 서술도 `letterSpacing: 0` 이다. 임의 트래킹은 근거 없는 값이다.
19. **굵기는 400·500·600·700 뿐**이다. 800 은 MDS 소비 세트에 없다.
20. **비활성 불투명도는 `s/opacity/disabled/default`(50)** 하나뿐이다. `calc(var(--mds-s-opacity-disabled-default) / 100)` 으로 쓴다. .4/.6 같은 임의값 금지.
21. **컨트롤 높이는 컴포넌트 규격을 따른다.** button 24·28·32·40·48·56 / input·tabItem 32·40·48 / iconChip 32 / checkbox·radio 20. `b/size/static` 에 36·44 가 있어도 컨트롤엔 못 쓴다(스케일 숫자와 컴포넌트 규격은 다른 축이다).
22. **브랜드색을 텍스트에 쓸 땐 `primary/accessible`**(#1c95a2)이다. `primary/default`(#21afbf)는 흰 배경 대비 2.64로 큰 글자 기준도 못 넘는다. 대비 실측은 `_raw/rest-audit/contrast-report.md`.
23. **`[hidden]`은 클래스 규칙에 진다.** `.panel{display:flex}`가 UA의 `[hidden]{display:none}`을 이겨서 숨긴 패널이 레이아웃에 그대로 남은 사고가 있었다. 숨김이 필요한 곳에는 `[hidden]{display:none !important}`를 명시한다.

## 모션

MDS는 **duration만 정의하고 easing 곡선은 없다**(`b/duration/*` 8단계: 0·50·100·150·200·300·400·500ms,
easing 토큰 0개). 그래서 timing-function 은 CSS 기본 `ease` 를 쓰고 곡선을 지어내지 않는다.
어느 상호작용에 어느 단계를 쓸지도 MDS가 정하지 않아 3단계로 묶어 쓴다.

```css
--dur-fast: calc(var(--mds-b-duration-20) * 1ms);  /* 100ms — 색·불투명도 */
--dur-base: calc(var(--mds-b-duration-30) * 1ms);  /* 150ms — 크기·위치 */
--dur-slow: calc(var(--mds-b-duration-40) * 1ms);  /* 200ms — 패널 전환 */
```

`prefers-reduced-motion: reduce` 에서는 전환을 1ms로 끈다.
`audit-screens.mjs` 가 모든 `transition-duration` 이 duration 스케일 안인지 검사한다.

## 스테이트 레이어 (hover · pressed)

MDS는 hover/pressed 를 **색을 바꾸는 것이 아니라 레이어의 불투명도를 바꾸는 것**으로 규정한다
(`🟣 decorate/stateLayer/light`, node `10:15033`, state×color×onlyPressed 42 variant).

| 강도 | hover | pressed | 쓰는 곳 |
|---|---:|---:|---|
| light | `.05` | `.1` | 일반 밝은 면 |
| deep | `.15` | `.3` | 브랜드·어두운 면 위 |

색 토큰으로는 `.1` 을 만들 수 없다(surface 스케일에 .05·.15·.40만 있음). 그래서 오버레이로 구현한다.

```css
.sl{position:relative;isolation:isolate}
.sl::after{content:'';position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  background:var(--mds-s-color-neutral-contents-default);opacity:0;
  transition:opacity var(--dur-fast) ease}
.sl:hover::after  { opacity: var(--mds-s-opacity-statelayer-light-hover) }   /* .05 */
.sl:active::after { opacity: var(--mds-s-opacity-statelayer-light-pressed) } /* .1  */
.sl.on-brand::after{ background: var(--mds-b-color-static-white) }           /* 브랜드 면 위 */
```

`audit-screens.mjs` 가 실제로 hover/active 를 강제해 불투명도가 규격값인지 잰다
(전환이 100ms라 강제 후 기다렸다 재야 한다 — 바로 읽으면 0이 나온다).

## 다크 테마

**MDS(Figma)에는 다크 모드가 없다.** 컴포넌트 88세트에 `theme`/`mode`/`dark` 축이 하나도 없고
(`dark`는 `dimmed.strength` 의 스크림 농도뿐), Foundation 의 `Dark` 프레임은 비어 있으며,
`dark 배경과 함께 보기` 는 같은 토큰을 어두운 배경에 얹어 보는 확인 시트다.

`tokens/dark.css`(584개)는 그래서 **원본에 없는 파생값**이다. 임의로 고르지 않고 규칙으로
유도했고 근거는 `tokens/_dark-derivation.json` 에 토큰별로 남겼다. 재생성은
`python3 checks/build-dark.py`.

| 규칙 | 내용 | 건수 |
|---|---|---:|
| R1 | MDS 가 정의한 `inverse` 짝을 그대로 사용(유도 아님, 실측) | 13 |
| R1b | `inverse` 토큰은 일반 짝과 교환. 다크에선 '반대 면'이 밝은 면이다 | 13 |
| R2 | 반투명색은 베이스만 뒤집고 알파 유지. MDS 자체 규칙(`#23242a26`→`#ffffff26`) | 221 |
| R3 | 램프 반전. `b/color/<계열>/s` → 같은 계열 `90 - s`. 앵커는 MDS 의 `neutral/0 ↔ neutral/90` | 300 |
| R5 | 램프가 없거나 색상이 틀어지면 HSL 에서 H·S 보존하고 L 만 반전 | 37 |

- **색상 가드**: 램프 매칭이 색상을 20도 넘게 바꾸면 거부하고 R5 로 넘긴다. 안 그러면
  보라(#935CD6)가 파랑 램프에 붙는다. 유채색 토큰 전수에서 색상 틀어짐 0건을 확인했다.
- 사용: `<html data-theme="dark">`. `index.css` 뒤에 `dark.css` 를 불러온다.
- **주의**: 전경색을 하드코딩하면 테마를 안 따라가 글자가 배경에 묻는다. 반드시 토큰을 쓴다.
  `audit-screens.mjs` 가 다크에서 대비 2 미만인 텍스트를 실패로 잡는다.

## 반응형

중단점은 MDS `b/breakPoint/*` 실측값이다 — **320 · 480 · 768 · 1024 · 1280 · 1440 · 1680 · 1920 · 2560**.
CSS 미디어 쿼리는 `var()` 를 못 받으므로 숫자를 직접 적되 이 목록 밖의 값은 쓰지 않는다.

접는 순서(좁아질수록): 패널 폭 → 상단바 버튼 라벨 → 레일 라벨 → 패널을 오버레이로 →
상단바 보조 컨트롤 → 하단바 라벨.

| 중단점 | 동작 |
|---|---|
| 1440 | 패널 320px |
| 1280 | 패널 288px, 상단바 버튼은 아이콘만 |
| 1024 | 레일 56px·라벨 접힘, 크기 드롭다운 숨김 |
| 768 | 패널이 오버레이(스테이지 위에 뜸), 하단바 라벨 접힘 |
| 480 | 패널 전체 폭, 제목 영역 숨김, 외곽선 버튼 숨김 |
| 320 | 상단바 아이콘·아바타 정리, 하단바는 줌만 |

`audit-screens.mjs` 가 위 9개 폭 전부에서 가로 오버플로 0 을 검사한다.
그리드에서 `1fr` 은 기본 `min-width:auto` 라 내용이 열을 밀어낸다. 좁은 폭이 깨지면
`minmax(0,1fr)` 과 `min-width:0` 을 먼저 확인할 것(실제로 이것 때문에 상단바가 뷰포트 밖으로 나갔다).

## 검증

```
bash ~/Documents/MiriCanvas/checks/run-checks.sh loop/checks.list

### 검사 실행에 필요한 것

외부 패키지 의존은 없음. 전부 표준 라이브러리와 내장 모듈만 씀(package.json·requirements.txt 불요).

| 도구 | 용도 | 비고 |
|---|---|---|
| `python3` | 검사 2~6, 재생성 | 3.9 이상 |
| `node` | 화면 감사 | 내장 모듈만 사용 |
| Chrome 계열 | 화면 감사의 실제 렌더 | 자동 탐색. 못 찾으면 `MDS_CHROME=/path/to/chrome` 로 지정 |

CI(`.github/workflows/checks.yml`)는 push·PR마다 같은 검사를 돌림. 단 하네스(`$KIT_DIR`)에 의존하는
소켓 검사와 GUIDE 검사는 CI에서 제외되므로, **CI 통과가 전체 8종 통과와 같지 않음.** 완료 판정은 로컬 run-checks 로 함.
```
소켓 잔존, manifest 유효성, 토큰 커버리지(check-tokens), 컴포넌트 커버리지(check-components), GUIDE 필수 절, **Figma 원본 대조(check-figma-parity)**, **생성물 드리프트(check-regen)**, **QA 화면 감사(audit-screens)** 8종을 검사한다.

`audit-screens.mjs`는 실제 Chrome을 CDP로 띄워 의사상태를 강제하고 계산된 스타일을 읽는다. 정적 스크린샷으로는 안 보이는 것들을 잡는다 — 아이콘 본체색 굳음, 브라우저 기본 포커스 링, `hidden`이 안 먹어 생기는 레이아웃 붕괴, 토큰 아닌 하드코딩 색, 가로 오버플로(1920/1440/1280), 탭 전환 실패. **화면을 고쳤으면 사람에게 보이기 전에 이걸 먼저 돌린다.**

`check-figma-parity.py`는 `_raw/rest-audit/`의 Figma REST 전수 스냅샷을 기준으로 토큰 값(알파 포함)·컴포넌트 전수·props 축을 대조한다. 나머지 검사는 manifest ↔ 산출물의 내부 정합성만 보므로 manifest 자체가 오염·누락되면 통과해버린다. 원본이 갱신되면 스냅샷을 다시 받아야 한다.

## 알려진 한계

- **변수 API 미사용**: Figma `/v1/files/:key/variables/local`은 Enterprise 플랜 전용이라 403이다. 토큰 값은 노드 트리의 `boundVariables`·style 참조에서 역산한 실측이며, 이름당 관측 표본의 최빈값을 취한다(`_raw/rest-audit/figma_styles_resolved.json`의 `samples`·`confidence` 참조). 표본이 없는 미소비 프리미티브는 값을 확정하지 못해 싣지 않았다.
- **변수와 동명 style이 값이 다른 경우가 있다**(색 4건). `index.css`는 **변수**를 따른다 — 실제 제품 CSS가 변수 쪽과 일치했다(예: `s/color/brand/primary/surface` 변수 `#21afbf1a` = 제품, style `#26c7d91a`). 전체 목록은 `tokens/_divergences.json`이고, `check-figma-parity.py`가 이 목록에 없는 새 불일치가 생기면 실패시킨다.
- **스케일 밖 토큰 값이 하나 있다**: `s/typography/label/medium/XS` 와 `c/iconBadge/typography/S` 의 크기가 **11.008px**(행간 16.07)이다. MDS fontSize 스케일은 10·12·14…라 11은 없다. Figma 노드 180개가 신뢰도 1.0으로 일관되게 그 값이라 실측은 맞고, 스케일된 텍스트 레이어가 굳은 authoring 이슈로 보인다. 임의로 11이나 12로 반올림하지 않고 실측값을 싣는다. **디자인팀 확인이 필요한 항목.**
- **소수 표본 style에 낚이지 않는다**: `s/color/semantic/negative/default`은 원자료 10건 중 9건이 `#e54a1a`(Dropdown·Input·Snackbar·Button 등 전 컴포넌트)이고 `#e3371c`는 Progressbar 페이지 1건뿐이다. REST style 역산은 그 페이지 노드 4개만 잡아 `#e3371c`를 냈다. `index.css`는 다수 지지 + 제품 일치인 `#e54a1a`를 싣는다. 표본 수가 적은 style 값은 페이지 국지 예외일 수 있다.
- **Rating**: Figma에 variant 세트 없이 이미지 목업만 있는 플레이스홀더다. published 후 재추출이 필요하다.
- 일부 컴포넌트는 특정 size/state의 색·수치가 다크 프리뷰 컨텍스트로 잡히거나 variable이 노출되지 않아 spec에 "확인 필요"로 표기했다.
