feat(skill): route fixes, delivery report, composition diversity, platform refresh

- route table: extension route for products with an existing visual
  language, feedback row, derived state tokens stay local
- completion: blocking 0 plus reported unverified; hard-gate surface is
  the touched scope; delivery report starts with what and how to open
- design.md created at step 0 with a skeleton, fingerprint line and
  change log; product-surface rubric reading; direction delegation
  states the chosen card and reason
- layout: hero families, nav forms, footer forms; art-direction page
  convergence 6 axes; genre sections; image treatments; placeholder
  conventions; multi-screen order; Tailwind theme mapping
- platform: scroll-driven examples, anchor positioning, canvas OT,
  three/R3F pins, shadcn style prefixes, Safari 26 notes, subgrid
- design-gate: four result states, exit codes, cwd module resolution,
  --url/--out-dir/--strict, oklch contrast, dead selector word match
- Korean font budget split from the Latin baseline
This commit is contained in:
Yun Chan 2026-10-06 16:55:38 +09:00
parent 9bcb45cbd3
commit 88c11682b1
51 changed files with 5834 additions and 2259 deletions

View file

@ -1,8 +1,53 @@
# preflight — 출시 전 감사
# preflight — 단일 납품 체크리스트
5단계에서 읽는다. **자기 결과물을 남의 것처럼 본다.** 통과 못 한 항목은 4단계로 돌아가 고친다.
슬롭 지문 검출과 자가 채점표는 `references/antipatterns.md` 에 있다. 이 문서는 **그 외 전부**를 다룬다.
**이 문서가 5단계의 유일한 납품 체크리스트다.** 다른 references 문서에 있는 체크리스트(motion §5, typography §8 등)는 구현 중 자기 점검이다. 납품 판정은 이 문서의 행 ID로만 한다. 같은 점검이 두 곳에 있으면 이 문서의 문구가 납품 기준이고, 세부 방법은 각 행이 가리키는 정본 문서를 따른다.
슬롭 지문 grep 목록과 맥락 질문은 [antipatterns.md](antipatterns.md), 시각 품질의 축별 판정은 [critique.md](critique.md) §2-1 루브릭, 게이트 설치와 측정 계약은 [audit-gate.md](audit-gate.md)에 있다.
---
## 이 문서를 읽는 법
| 절 | 행 ID | 언제 |
|---|---|---|
| §0 기계 검사 | M-A~M-E | 전체·연장은 항상. 국소는 아래 "경로별로 돌 범위" |
| §1 접근성 | A1~A25 | 항상(하드 게이트) |
| §2 성능 | P1~P8 | 항상. 국소 경로는 손댄 자산·코드가 예산에 닿을 때 |
| §3 반응형 | W1~W16 | 전체·연장은 항상. 국소는 손댄 요소가 놓인 뷰만 |
| §4 콘텐츠 | C1~C8 | 카피·데이터를 바꿨을 때 |
| §4-1 상태 완결성 | S1~S11 | 입력·파괴·열림이 있는 화면 |
| §4-2 진실 완결성 | T1~T11 | 가상·예시·AI·규제 화면 |
| §4-3 썸네일·OG | O1~O4 | 썸네일·OG 이미지를 만들 때 |
| §4-4 조건부 모듈 | X1~X19 | 해당 기술·표면을 썼을 때 |
| §5 시각 판정 | V1~V7 | 전체·연장 경로, 국소는 손댄 축만 |
| §6 최종 게이트 | G-1~G-12 | 완료 선언 직전 |
`G-n`은 이 문서의 최종 게이트 행이고, 하이픈 없는 `Gn`(G20, G32 등)은 [audit-gate-rules.md](audit-gate-rules.md)의 게이트 규칙 번호다.
### 경로별로 돌 범위
| 경로 | 돌 행 |
|---|---|
| 전체·연장 | §0~§6 전부. §4-2·§4-3·§4-4는 조건이 맞는 행만 |
| 국소 | 하드 게이트 행은 전부 돈다. 그 밖의 행은 손댄 영역이 걸친 것만 돈다. §5 루브릭은 손댄 영역이 걸친 축만 채점한다 |
| 리뷰(5′) | 리뷰는 읽기 전용이다([change-review.md](change-review.md) §1-1). 사용자 프로젝트를 바꾸지 않고 잴 수 있는 행만 돈다. 패키지 설치·설정 파일 생성이 필요한 행은 미검증으로 남긴다. 범위 밖으로 뺀 행은 미점검이다([critique.md](critique.md) §7·§8) |
**하드 게이트의 "전부"는 행을 뜻한다. 표면은 이번 작업이 정한다.**
- 전체 경로: 만든 화면 전부.
- 연장·국소: 손댄 요소, 그 요소가 놓인 뷰, 그 변경이 닿는 상태(hover·focus·열림·오류)와 폭. 토큰을 바꿨으면 그 토큰을 쓰는 곳까지다.
- 범위 밖에서 발견한 위반은 **기존 결함**으로 분리한다. 고치지 않고, 완료 보고의 "기존 결함" 목록에 위치·기준·제안을 적는다. 하드 게이트 집계(충족/미검증/차단)에 넣지 않는다. 사용자가 고쳐 달라고 하면 그때 경로를 다시 판정한다. 분류 기준은 change-review의 Introduced·Regression·Pre-existing과 같다.
이렇게 나누는 이유: 표면을 페이지 전체로 읽으면 hover를 고치러 들어간 작업이 푸터의 기존 대비 위반 때문에 끝나지 않거나, 요청받지 않은 곳을 고치다 경로까지 올라간다.
### 행의 상태와 위계
- 상태는 넷이다: **충족 / 미검증 / 실패 / 해당 없음**. 도구·기기가 없어 확인하지 못한 행은 미검증이다. 미검증은 충족이 아니다.
- 위계 표시: `[하드 게이트]`, `[계약]`(프로젝트 계약), `[휴리스틱]`(관찰 후보), `[방향]`(critique §4 Direction). §1의 표시 없는 행은 하드 게이트, 그 밖의 표시 없는 행은 계약이다.
- 실패 행은 [critique.md](critique.md) §3 형식으로 finding을 만들고 §4 심각도를 매긴다.
- 같은 결함의 수정 시도는 3회까지다. 넘으면 멈추고 사용자에게 보고한다([audit-gate.md](audit-gate.md) "루프와 재작업 상한").
---
@ -11,13 +56,13 @@
**무엇에 돌리는가부터 정해라.**
- 정적 HTML/CSS 프로젝트 → 소스 그대로
- **React·Vue·Astro 등 프레임워크 프로젝트 → 프로덕션 빌드 결과에 돌린다.** 소스만 보면 렌더된 DOM 이 없어 대비·가로 스크롤·LCP 를 잴 수 없다. `pnpm build && pnpm preview` 로 띄우고 그 URL 을 검사해라
- **소스만 보고 "측정 불가"로 남기지 마라.** 미측정은 통과가 아니다
- **소스만 보고 "측정 불가"로 남기지 마라.** 렌더할 수단이 정말 없을 때만 미검증이고, 미검증은 통과가 아니다
grep 검사는 **주석과 문자열을 제외**해라. "100vh 금지"라고 쓴 주석이 '지원 범위의 뷰포트에서 의도하지 않은 가로 스크롤·겹침·조작 불능이 없다' 하드 게이트 검사에 걸리는 오탐이 실제로 나왔다.
grep 검사는 **주석과 문자열을 제외**해라. "100vh 금지"라고 쓴 주석이 하드 게이트 검사에 걸리는 오탐이 실제로 나왔다.
사람 눈보다 정확하고 빠르다. 셋 다 돌려라.
전체·연장은 M-A~M-E를 모두 돌린다. 국소는 손댄 것에 닿는 것만 돌린다(hover 수정이면 M-A와 M-D).
### A. 이펙트 전부 끄기
### M-A. 이펙트 전부 끄기
```css
/* 임시로 주입해서 확인한다 */
*, *::before, *::after {
@ -29,114 +74,96 @@ grep 검사는 **주석과 문자열을 제외**해라. "100vh 금지"라고 쓴
}
canvas { display: none !important; }
```
이 상태에서 **페이지가 여전히 읽히고 구조가 살아 있어야 한다.** 무너지면 이펙트가 구조를 대신하고 있는 것이다. 4-1로 돌아가라.
이 상태에서 **페이지가 여전히 읽히고 구조가 살아 있어야 한다.** 무너지면 이펙트가 구조를 대신하고 있는 것이다. 4단계로 돌아가 구조부터 고친다. 필터·3D·캔버스·패럴랙스를 쓴 경우의 추가 확인은 §4-4다.
### B. 스타일 후보 grep
`references/antipatterns.md`의 grep 목록을 소스에 돌려 보라 CTA, 전체 대문자 헤드라인, 번호 매긴 단계 같은 반복 후보를 찾는다. 검출은 실패가 아니다. 프로젝트 계약·브리프·실제 렌더를 대조해 결정을 기록한다.
### M-B. 스타일 후보 grep `[휴리스틱]`
[antipatterns.md](antipatterns.md)의 grep 목록을 소스에 돌려 보라 CTA, 전체 대문자 헤드라인, 번호 매긴 단계 같은 반복 후보를 찾는다. 검출은 실패가 아니다. 프로젝트 계약·브리프·실제 렌더를 대조해 유지·수정·제거를 기록한다.
### C. 카피 경쟁사 치환
브랜드가 특별하다고 주장하는 문구에만 제품·회사 이름을 경쟁사 이름으로 바꿔 읽는 **진단 질문**을 쓴다. 다른 업체에도 그대로 성립하면 근거와 구체성을 재검토한다. 수업·가격·대상·위치·FAQ·CTA 같은 안내는 치환만으로 실패시키지 않는다. 독자의 실제 질문에 대한 구체적 답, 검증 가능한 사실, 자연스러운 업종 표현을 먼저 확인한다.
### M-C. 카피 경쟁사 치환
브랜드가 특별하다고 주장하는 문구에만 제품·회사 이름을 경쟁사 이름으로 바꿔 읽는 **진단 질문**을 쓴다. 다른 업체에도 그대로 성립하면 근거와 구체성을 재검토한다. 헤드라인·히어로 본문이 그대로 성립하면 루브릭 R7 0점이다(§5). 수업·가격·대상·위치·FAQ·CTA 같은 안내는 치환만으로 실패시키지 않는다. 독자의 실제 질문에 대한 구체적 답, 검증 가능한 사실, 자연스러운 업종 표현을 먼저 확인한다.
낯선 업종이나 추상 표현의 용법이 불확실할 때만 공식 실제 사례를 소규모로 조사해 용어를 참고한다. 그 사례의 주장·실적·후기를 가져오지 않으며, 현재 프로젝트의 사실로 바꾸어 말하지 않는다.
낯선 업종이나 추상 표현의 용법이 불확실하면 공식 실제 사례를 소규모로 조사해 용어를 참고한다. 그 사례의 주장·실적·후기를 가져오지 않으며, 현재 프로젝트의 사실로 바꾸어 말하지 않는다.
### D. 죽은 코드·토큰 위생 (design-gate 내장 검사)
### M-D. 죽은 코드·토큰 위생 (design-gate 내장 검사)
- CSS 클래스가 HTML·JS 어디에도 등장하지 않으면 죽은 것이다. 컴포넌트를 없앨 때 HTML·CSS·JS 를 **삼위일체로** 지웠는지 대조한다(동적 클래스 `is-${type}`·`seg-${n}` 처럼 조립되는 접두사는 게이트 설정의 `deadCssIgnore` 로 제외한다)
- 토큰 파일이 있는데 다른 CSS 파일에 색 리터럴(hex·rgb)이 남아 있으면 실패다. 반지름 리터럴도 마찬가지 — 눈 검사로는 절대 전부 못 잡는다(실측: 8건의 상수색이 눈 검사를 통과했다)
- `theme-color` 메타가 실제 배경색과 다르면 **낡은 토큰**이다. 프리셋 전환에서 브랜드 표면(theme-color·파비콘·og:image)이 누락됐다는 뜻이다
- 토큰 파일이 있는데 다른 CSS 파일에 색 리터럴(hex·rgb)이 남아 있으면 실패다. 반지름 리터럴은 게이트가 `thresholds.radiusLiterals`(기본 3종) 이상일 때 실패로 본다 — 그보다 적으면 토큰으로 옮길지 직접 판정한다. 눈 검사로는 전부 못 잡는다
- `theme-color` 메타가 실제 배경색과 다르면 **낡은 토큰**이다. 브랜드 표면(theme-color·파비콘·og:image)이 함께 갱신됐는지 본다
### E. 왼쪽 인셋 리듬 (모바일)
오버플로 검사는 오른쪽만 본다 — **왼쪽이 붙어 깨지는 결함은 통과한다**(실측: 제목 16px·앱바·카드 24px 혼재가 게이트를 통과해 사용자 보고로 나왔다). 모바일 폭(320·390)에서:
### M-E. 왼쪽 인셋 리듬 (모바일)
오버플로 검사는 오른쪽만 본다. **왼쪽이 붙어 깨지는 결함은 통과한다.** 게이트의 `leftInset` 검사가 재는 폭과 항목은 [audit-gate.md](audit-gate.md)의 검사별 측정 폭을 본다 — 게이트가 재지 않는 폭과 "같은 축" 판정은 직접 잰다. 모바일 폭(320·390)에서:
- 화면의 모든 보이는 텍스트 시작점 ≥ 12px
- 헤더 텍스트·화면 제목·카드 텍스트가 **같은 축**(±10px)
- 측정은 전폭 컨테이너의 보더박스가 아니라 **텍스트 시작점**, `display:none` 요소(rect=0)는 제외하고
- 측정은 전폭 컨테이너의 보더박스가 아니라 **텍스트 시작점**으로 하고, `display:none` 요소(rect=0)는 제외한다
---
## 1. 접근성 — 타협 없음
| 항목 | 확인 방법 | 기준 |
|---|---|---|
| 키보드 도달 | Tab 만으로 순회 | 모든 인터랙티브 요소에 도달, 순서가 시각 순서와 일치 |
| 포커스 표시 | Tab 하며 눈으로 | 항상 보인다. `outline: none` 만 있고 대체가 없으면 실패 |
| 대비 | 측정 도구 (아래 주의) | 본문 4.5:1, 큰 글자·UI 3:1 |
| 이미지 대체텍스트 | 소스 확인 | 의미 있는 이미지에 alt, 장식은 `alt=""` |
| 폼 레이블 | 소스 확인 | 모든 입력에 연결된 `<label>`. placeholder 는 레이블이 아니다 |
| 제목 구조 | 개요 확인 | h1 하나, 건너뛰지 않음 |
| 모션 감소 | `prefers-reduced-motion` 켜고 확인 | **전부 끄는 게 정답이 아니다** — 위치 이동·시차·자동재생을 죽이고, 페이드·상태 변화는 남긴다 |
| 자동재생 | 확인 | 5초 이상 반복되는 애니메이션에는 정지 수단이 있다 |
| 캔버스/WebGL 실패 | JS 끄고 확인 | 콘텐츠가 여전히 보인다 |
| 네이티브 요소 | 소스 확인 | 이동은 `<a href>`, 액션은 `<button>`, 상태 없는 클릭 영역에 `<div onClick>`을 쓰지 않는다. 근거·예외는 [accessibility.md](accessibility.md) [SKILL-BETTER-A11Y] |
| 아이콘 버튼 이름 · Label in Name | 소스 확인 | 텍스트 라벨 없는 버튼·링크는 `aria-label` 또는 숨김 텍스트 필수, 장식 아이콘은 `aria-hidden="true"`. 보이는 라벨이 있으면 접근 가능한 이름이 그 텍스트를 포함한다 [WCAG-LABEL-NAME] [SKILL-WEB-A11Y] |
| 포커스 가림 | Tab 순회, 스티키 헤더·CTA 바 확인 | 포커스된 요소가 고정 바에 완전히 가려지지 않는다. `scroll-margin-top`을 헤더 높이 토큰에 연결 [WCAG-FOCUS-OBSCURED] |
| forced-colors | Windows 고대비 모드로 확인 | 강제 색상 모드에서도 컨트롤 경계·상태가 구분된다(Baseline: 2022-09~ 전 주요 브라우저 [WEB-BASELINE]). `forced-color-adjust: none`은 검증된 곳에서만 [SKILL-BETTER-A11Y] |
| 스킵 링크 · 랜드마크 | 첫 Tab, 소스 확인 | 반복 내비를 건너뛰는 링크가 첫 포커스 요소, 보이는 `<main>` 랜드마크 하나. 세부는 [accessibility.md](accessibility.md) [SKILL-WEB-A11Y] [SKILL-BETTER-A11Y] |
| 키보드 위젯 패턴 | 실제 키보드 조작 | 탭·메뉴버튼·콤보박스·리스트박스·다이얼로그는 WAI-ARIA APG 패턴을 따른다. 세부는 [accessibility.md](accessibility.md) [ARIA-APG] |
| 폼 — autocomplete·오류 | 소스·조작 확인 | autocomplete 값이 필드 의미와 맞고(무표시는 하드 게이트), 오류는 필드 옆에서 수정법을 말한다. 세부는 [accessibility.md](accessibility.md) [WCAG-INPUT-PURPOSE] [SKILL-BETTER-A11Y] |
| **[프로젝트 계약]** 폼 — 제출 버튼 | 소스·조작 확인 | 제출 버튼은 요청 시작 전까지 비활성화하지 않으며, `disabled`와 `aria-disabled`를 구분해 쓴다. 세부는 [accessibility.md](accessibility.md) [SKILL-BETTER-A11Y] |
| 상태 메시지 | 조작 후 확인 | 포커스 이동 없이 전달되는 상태 변화도 role·속성으로 보조기술에 전달된다. 라이브 리전 선택 기준은 [accessibility.md](accessibility.md) [WCAG-STATUS] [SKILL-BETTER-A11Y] |
| 깜빡임 | 초당 프레임 확인 | 1초에 3회를 넘게 번쩍이거나 일반·적색 섬광 한계를 넘는 콘텐츠가 없다 [WCAG-FLASH] |
| 호버 콘텐츠 | 마우스오버 확인 | 호버·포커스로 나타나는 콘텐츠는 해제 가능·호버 가능·계속 보임 세 조건을 만족한다 [WCAG-HOVER] |
| 드래그 대안 | 조작 확인 | 드래그로만 되는 기능은 단일 포인터 조작(탭 등)으로도 된다. 예외는 필수 드래그·UA 기본 스크롤 [WCAG-DRAG] |
| 잘린 텍스트 도달 수단 | 소스·렌더 확인 | `text-overflow: ellipsis` 등으로 시각적으로 잘린 텍스트·표 셀·태그에는 title·툴팁·상세보기 같은 전체 값 도달 수단이 있다 [SKILL-BETTER-INTERFACE] |
| **[프로젝트 계약]** 비활성 · placeholder 대비 | 렌더 확인 | 비활성 컨트롤은 WCAG 대비 예외 대상이지만, 활성처럼 보여 사용자를 속이지 않을 만큼은 구분한다. placeholder는 라벨이 아니며 지나치게 옅어 읽을 수 없게 두지 않는다 [SKILL-BETTER-COLORS] |
판정 원칙과 구현 세부의 정본은 [accessibility.md](accessibility.md)다. 이 표는 납품 판정 행만 둔다.
이 표에 새로 더한 행은 비활성/placeholder 대비(별도 표시한 프로젝트 계약)를 빼면 모두 하드 게이트다. forced-colors 도 하드 게이트다 — 판정 근거는 [accessibility.md](accessibility.md) §12. 대비가 실패로 나오면 수정 절차(색상환 고정·명도부터 조정)는 [color.md](color.md)를 따른다.
| ID | 항목 | 확인 방법 | 기준 |
|---|---|---|---|
| A1 | 키보드 도달 | Tab 만으로 순회 | 모든 인터랙티브 요소에 도달, 순서가 시각 순서와 일치 |
| A2 | 포커스 표시 | Tab 하며 눈으로 | 항상 보인다. `outline: none` 만 있고 대체가 없으면 실패 |
| A3 | 대비 | 측정 도구 | 본문 4.5:1, 큰 글자·UI 3:1. 라이트·다크 등 지원하는 외형마다 잰다. 반투명 배경은 합성해서 잰다([audit-gate-rules.md](audit-gate-rules.md) §3) |
| A4 | 이미지 대체텍스트 | 소스 확인 | 의미 있는 이미지에 alt, 장식은 `alt=""` |
| A5 | 폼 레이블 | 소스 확인 | 모든 입력에 연결된 `<label>`. placeholder 는 레이블이 아니다 |
| A6 | 제목 구조 | 개요 확인 | h1 하나, 건너뛰지 않음 |
| A7 | 모션 감소 | `prefers-reduced-motion` 켜고 확인 | **전부 끄는 게 정답이 아니다** — 위치 이동·시차·자동재생을 죽이고, 페이드·상태 변화는 남긴다 |
| A8 | 자동재생 | 확인 | 5초 넘게 자동으로 움직이는 콘텐츠에는 정지 수단이 있다 |
| A9 | 캔버스/WebGL 실패 | JS 끄고 확인 | 콘텐츠가 여전히 보인다 |
| A10 | 네이티브 요소 | 소스 확인 | 이동은 `<a href>`, 액션은 `<button>`, 상태 없는 클릭 영역에 `<div onClick>`을 쓰지 않는다 [SKILL-BETTER-A11Y] |
| A11 | 아이콘 버튼 이름 · Label in Name | 소스 확인 | 텍스트 라벨 없는 버튼·링크는 `aria-label` 또는 숨김 텍스트 필수, 장식 아이콘은 `aria-hidden="true"`. 보이는 라벨이 있으면 접근 가능한 이름이 그 텍스트를 포함한다 [WCAG-LABEL-NAME] [SKILL-WEB-A11Y] |
| A12 | 포커스 가림 | Tab 순회, 스티키 헤더·CTA 바 확인 | 포커스된 요소가 고정 바에 완전히 가려지지 않는다. `scroll-margin-top`을 헤더 높이 토큰에 연결 [WCAG-FOCUS-OBSCURED] |
| A13 | forced-colors | Windows 고대비 모드로 확인 | 강제 색상 모드에서도 컨트롤 경계·상태가 구분된다. 판정 근거는 accessibility §12 [WEB-BASELINE] [SKILL-BETTER-A11Y] |
| A14 | 반복 블록 우회 · 랜드마크 | 첫 Tab, 소스 확인 | 반복 블록 우회 수단(2.4.1)이 있다 — 스킵 링크 권장(반복 내비를 건너뛰는 링크가 첫 포커스 요소). `<main>` 랜드마크 하나. 판정 근거는 [accessibility.md](accessibility.md) §5 [SKILL-WEB-A11Y] [SKILL-BETTER-A11Y] |
| A15 | 키보드 위젯 패턴 | 실제 키보드 조작 | 탭·메뉴버튼·콤보박스·리스트박스·다이얼로그는 WAI-ARIA APG 패턴을 따른다 [ARIA-APG] |
| A16 | 폼 — autocomplete·오류 | 소스·조작 확인 | autocomplete 값이 필드 의미와 맞고(무표시는 하드 게이트), 오류는 필드 옆에서 수정법을 말한다 [WCAG-INPUT-PURPOSE] [SKILL-BETTER-A11Y] |
| A17 | `[계약]` 폼 — 제출 버튼 | 소스·조작 확인 | 제출 버튼은 요청 시작 전까지 비활성화하지 않으며, `disabled`와 `aria-disabled`를 구분해 쓴다 [SKILL-BETTER-A11Y] |
| A18 | 상태 메시지 | 조작 후 확인 | 포커스 이동 없이 전달되는 상태 변화도 role·속성으로 보조기술에 전달된다 [WCAG-STATUS] [SKILL-BETTER-A11Y] |
| A19 | 깜빡임 | 초당 프레임 확인 | 1초에 3회를 넘게 번쩍이거나 일반·적색 섬광 한계를 넘는 콘텐츠가 없다 [WCAG-FLASH] |
| A20 | 호버 콘텐츠 | 마우스오버 확인 | 호버·포커스로 나타나는 콘텐츠는 해제 가능·호버 가능·계속 보임 세 조건을 만족한다 [WCAG-HOVER] |
| A21 | 드래그 대안 | 조작 확인 | 드래그·스와이프로만 되는 기능은 단일 포인터 조작(탭 등)으로도 된다. 예외는 필수 드래그·UA 기본 스크롤 [WCAG-DRAG] |
| A22 | 잘린 텍스트 도달 수단 | 소스·렌더 확인 | `text-overflow: ellipsis` 등으로 잘린 텍스트·표 셀·태그에는 전체 값에 도달하는 수단(상세보기·펼치기·툴팁 등)이 있고, 키보드로도 도달한다 [SKILL-BETTER-INTERFACE] |
| A23 | `[계약]` 비활성 · placeholder 대비 | 렌더 확인 | 비활성 컨트롤은 WCAG 대비 예외 대상이지만, 활성처럼 보여 사용자를 속이지 않을 만큼은 구분한다. placeholder는 지나치게 옅어 읽을 수 없게 두지 않는다 [SKILL-BETTER-COLORS] |
| A24 | 색만으로 의미 전달 금지 | 회색조 캡처 확인 | 상태·의미가 색 말고도 아이콘·라벨·패턴으로 구분된다([color.md](color.md)) |
| A25 | 터치·포인터 타깃 | design-gate 터치 타깃 검사 또는 실측 | WCAG 2.5.8: 인라인이 아닌 대상은 24×24 CSS px 이상이거나 간격 예외를 충족한다. 44px는 아래 W9의 계약 층이다 [WCAG-TARGET] |
> **대비를 스크립트로 잴 때 — 반투명 배경을 불투명으로 계산하지 마라.**
> `getComputedStyle(el).backgroundColor` 로 조상을 거슬러 올라가며 "투명이 아닌 첫 값"을 배경으로 쓰면,
> `rgb(255 255 255 / 0.085)` 같은 유리 틴트를 **순백**으로 취급한다. 흰 글자가 1.15:1 로 나온다.
> 알파가 1 미만인 층은 계속 쌓고, **아래에서 위로 합성**한 뒤에 재라:
>
> ```js
> // 알파 < 1 이면 멈추지 말고 계속 쌓는다
> const stack = []; let e = el;
> while (e && e !== document.documentElement) {
> const p = parse(getComputedStyle(e).backgroundColor);
> if (p && p[3] > 0) { stack.push(p); if (p[3] >= 1) break; }
> e = e.parentElement;
> }
> let [r, g, b] = rootBg;
> for (let i = stack.length - 1; i >= 0; i--) {
> const [sr, sg, sb, sa] = stack[i];
> r = sr*sa + r*(1-sa); g = sg*sa + g*(1-sa); b = sb*sa + b*(1-sa);
> }
> ```
>
> 유리·오버레이·틴트를 쓰는 페이지에서는 이 차이가 **통과와 실패를 뒤집는다.**
> **검사 도구가 틀리면 통과도 실패도 의미가 없다.** 실패가 나오면 값을 고치기 전에 측정을 먼저 의심해라.
대비가 실패로 나오면 수정 절차(색상환 고정·명도부터 조정)는 [color.md](color.md)를 따른다. 실패가 나오면 값을 고치기 전에 측정을 먼저 의심한다.
**스크린리더를 실제로 켜볼 수 없다면** 최소한 소스의 읽기 순서를 확인해라. `order`·`position: absolute` 로 시각 순서를 바꿨다면 읽기 순서가 어긋난다.
**스크린리더를 실제로 켜볼 수 없다면** 최소한 소스의 읽기 순서를 확인해라. `order`·`position: absolute` 로 시각 순서를 바꿨다면 읽기 순서가 어긋난다. 스크린리더 통과 자체는 미검증으로 적는다.
---
## 2. 성능 — 3단계에서 정한 예산과 대조
**예산 값은 `tokens.md` §5 를 보라. 여기서 다시 적지 않는다** — 두 곳에 적으면 갈라진다.
이 문서가 하는 일은 **실측을 채우는 것**이다.
**예산 값은 [tokens.md](tokens.md) §5 를 보라. 여기서 다시 적지 않는다** — 두 곳에 적으면 갈라진다. 이 문서가 하는 일은 **실측을 채우는 것**이다.
| 항목 | 실측 | 판정 |
|---|---|---|
| 히어로까지 JS (gzip) | | |
| WebGL/3D 추가분 | | |
| 총 전송량 (첫 화면) | | |
| 첫 인터랙션 (모바일 4G) | | |
| LCP | | |
| CLS | | |
| 폰트 파일 수 | | |
| ID | 항목 | 실측 | 판정 |
|---|---|---|---|
| P1 | 히어로까지 JS (gzip) | | |
| P2 | WebGL/3D·모션 라이브러리 추가분 | | |
| P3 | 총 전송량 (첫 화면) | | |
| P4 | 입력 반응(INP 또는 대표 상호작용 지연, 모바일 4G) | | |
| P5 | LCP | | |
| P6 | CLS | | |
| P7 | 폰트 패밀리 수·첫 화면 전송량(파일 개수로 판정하지 않는다 — tokens §5) | | |
**측정하지 않았으면 통과가 아니다.** 예산을 넘겼다면 둘 중 하나다: 이펙트를 빼거나, 예산을 올린 이유를 명시하거나.
### 폰트 (`typography.md` §8 이 전체 목록)
### P8. 폰트·한글 조판 (세부 정본은 [typography.md](typography.md) §6-5·§8)
- [ ] 웹폰트를 쓰면 실제 교체 시점의 레이아웃을 측정하고, 폴백 메트릭·`font-display` 전략을 선택했다. 특정 전략만으로 CLS 유무를 단정하지 않는다
- [ ] 폰트를 preload했다면 첫 화면에서 실제로 쓰이고 요청에 맞는 `crossorigin`이 있다
- [ ] 수치 비교가 필요한 곳의 숫자 정렬을 확인했다. `tabular-nums`는 해당 서체의 지원과 과업에 맞춰 선택한다
- [ ] 한글 글꼴과 시스템 폴백을 실제로 확인했다. 웹폰트를 전송하는 경우 서브셋·전송량·글리프 범위를 검토했다
- [ ] 웹폰트를 임베딩하면 라이선스를 확인해 `design.md`에 적었다
- [ ] 웹폰트를 쓰면 **폰트를 못 받은 상태로 열어봤다.** 그 상태로도 읽힌다
- [ ] **P8a** 웹폰트를 쓰면 실제 교체 시점의 레이아웃을 측정하고, 폴백 메트릭·`font-display` 전략을 선택했다. 특정 전략만으로 CLS 유무를 단정하지 않는다
- [ ] **P8b** 폰트를 preload했다면 첫 화면에서 실제로 쓰이고 요청에 맞는 `crossorigin`이 있다
- [ ] **P8c** 없는 웨이트·스타일을 브라우저가 합성하고 있지 않다(합성 볼드·합성 이탤릭). 한글 서체에 이탤릭 면이 없으면 기울임으로 강조하지 않는다
- [ ] **P8d** 수치 비교가 필요한 곳의 숫자 정렬을 확인했다. `tabular-nums`는 해당 서체의 지원과 과업에 맞춰 선택한다
- [ ] **P8e** 한글 글꼴과 시스템 폴백을 실제로 확인했다. 웹폰트를 전송하는 경우 서브셋·전송량·글리프 범위를 검토했다
- [ ] **P8f** 웹폰트를 임베딩하면 라이선스를 확인해 `design.md`에 적었다
- [ ] **P8g** 웹폰트를 쓰면 **폰트를 못 받은 상태로 열어봤다.** 그 상태로도 읽힌다
- [ ] **P8h** 한글이 있으면 [typography.md](typography.md) §6-5의 "배포 전에 한글 조판을 렌더로 확인하는 항목" 다섯 줄을 렌더로 확인했다. 기준은 그 절이 정본이며 여기서 다시 정의하지 않는다
### 자주 걸리는 것
- 전송하는 폰트의 크기·글리프 범위 미측정. 한글 웹폰트는 필요한 범위와 라이선스에 맞춰 서브셋을 검토한다
- 전송하는 폰트의 크기·글리프 범위 미측정
- 첫 화면 밖 이미지에 `loading="lazy"` 누락
- 3D/WebGL을 초기 번들에 포함 (뷰포트 진입 시 동적 import 해야 한다)
- 애니메이션이 `top`/`left`/`width` 를 건드림 → `transform`·`opacity`로 가능한지 먼저 검토하고, 유지한다면 비용·CLS·입력 간섭·감소 모션을 측정해 예산과 대조한다
@ -146,23 +173,25 @@ canvas { display: none !important; }
## 3. 반응형
- [ ] 프로젝트가 채택한 회귀 매트릭스에서 가로 스크롤 없음. 이 저장소의 기본 매트릭스는 **320·390·768·1440·1920·2560px**이며, 지원 기기·브리프가 다르면 그 계약을 바꾸고 이유를 기록한다
- [ ] 변경 영향에 따라 프로젝트가 채택한 뷰의 너비×높이, 짧은 화면, 긴 세로 흐름, 뷰포트 종횡비·피사체 보존·실제 clipping, 글자 확대를 추가로 검사한다. 크롬·모달·긴 폼·고정 CTA는 짧은 화면의 가시 영역과 scroll owner를, 목록·본문·이미지는 긴 세로 흐름과 피사체 보존을, 글꼴·카피·컨트롤은 지원 확대 상태를 잰다. 모션 상태와 clipping의 상세 계약은 [렌더 측정 계약](audit-gate.md)을 따른다
- [ ] **크롬(탑바·앱바·툴바)에 컨트롤을 더했다면 그 줄의 최소폭 합을 가장 좁은 폭에서 다시 잰다** — 버튼 하나가 320px를 뚫는 사고가 반복된다(실측: 인쇄 버튼 1개로 +18px). 줄이 넘으면 감싸기(2행 랩)가 기본 수습이다
- [ ] 프로젝트 회귀 매트릭스의 각 폭에서 실제 브라우저 렌더를 잰다. 기본 매트릭스(320·390·768·1440·1920·2560px)는 저장소의 회귀 범위이며 WCAG의 보편 폭 기준은 아니다. 최소 폭의 오버플로와 wide 화면의 카피 측정폭·제목 칼럼을 함께 확인한다
- [ ] WCAG 200% 확대 또는 운영체제의 실제 글꼴 확대가 지원 범위라면 실제 브라우저·기기에서 별도 검증한다. gate의 font-scale 행렬은 회귀 신호이며 px 고정 폰트의 OS 확대를 대체하지 않는다
- [ ] 1920px 이상에서는 본문 측정폭을 유지하고, 표·도면·작업대처럼 정보 밀도가 필요한 표면만 별도의 `--container-wide`로 넓힌다. 같은 `max-width`를 전부 풀어 넓은 화면을 빈 종이로 만들지 않는다
- [ ] 분할 히어로는 1920·2560에서 `hero / copy / media / 다음 정보 레일` rect를 기록했다. 사진의 최대 폭과 카피의 최소 폭을 페이지 토큰으로 정하고, 단지 viewport를 반으로 나눈 `1fr 1fr`가 사진을 무제한 키우지 않는지 확인한다
- [ ] hero 검증만으로 text-media 분할 검증을 끝내지 않는다. 과정·추천·가맹처럼 DOM/여백 규칙이 다른 모든 분할 표면에서 rail·copy·media·제목의 실제 rect와 줄 수를 기록한다. wide override가 있다면 기존 `padding-left/right: calc(100vw …)`를 **양쪽 모두** 재설정했는지, 상위 `max-width`의 선택자 우선순위가 named stage를 다시 줄이지 않는지 확인한다
- [ ] 중간 뷰포트(768~1024px)에서 레이아웃이 깨지지 않음 — **가장 자주 빠뜨리는 구간**
- [ ] 터치 타깃: **버튼·아이콘·카드 등 독립 컨트롤은 44×44px 이상**(Apple/Google 권고)
- [ ] 터치 타깃: **본문 안 인라인 텍스트 링크는 24×24px 이상** — 이 스킬의 프로젝트 계약(`inlineTargetMin` 24px)이다. WCAG 2.5.8은 문장 속 인라인 링크를 예외로 둔다. 여기에 44 를 요구하면 정상적인 내비 링크가 오탐된다
- [ ] **컨트롤의 내용 여백**: 버튼 라벨·입력값·select 표시·표 셀의 실제 렌더 글자가 테두리에 닿지 않는다. 이 저장소의 실렌더 기본값은 좌우 **8 CSS px** 이상이다. 이는 WCAG 적합성 수치가 아니라 검증 가능한 시각 품질 기본값이며, 아이콘 전용·체크박스·의도적인 데이터 정렬은 별도 근거를 적는다
- [ ] **폼 기하**: 연결된 label/name/autocomplete를 확인한 뒤 실제 렌더 rect로 입력끼리 겹치지 않는지 잰다. 좁은 폭에서는 필드를 세로로 쌓고, label·입력·오류 문장이 서로의 클릭/읽기 영역을 침범하지 않게 한다
- [ ] **표와 표처럼 보이는 flex/grid**: 데이터 관계에는 `th`/`td`와 `scope`를 쓰고, 모든 가시 셀에 좌우 읽기 여백을 둔다. 좁은 폭에서 스크롤·카드화로 형식이 바뀌어도 헤더-값 관계가 남아야 한다
- [ ] 호버로만 접근되는 기능 없음
- [ ] 긴 단어/URL이 넘치지 않음 (`overflow-wrap: anywhere`)
- [ ] 가로 모드에서 첫 화면이 잠기지 않음
- [ ] **W1** 프로젝트가 채택한 회귀 매트릭스의 각 폭에서 실제 브라우저 렌더를 재고, 가로 스크롤이 없다. 이 저장소의 기본 매트릭스는 **320·390·768·1440·1920·2560px**이며 WCAG의 보편 폭 기준은 아니다. 지원 기기·브리프가 다르면 계약을 바꾸고 이유를 기록한다 `[하드 게이트: 지원 범위의 가로 스크롤·겹침·조작 불능]`
- [ ] **W2** 변경 영향에 따라 뷰의 너비×높이, 짧은 화면, 긴 세로 흐름, 종횡비·피사체 보존·실제 clipping, 글자 확대를 추가로 검사한다. 크롬·모달·긴 폼·고정 CTA는 짧은 화면의 가시 영역과 scroll owner를, 목록·본문·이미지는 긴 세로 흐름과 피사체 보존을, 글꼴·카피·컨트롤은 지원 확대 상태를 잰다. 측정 계약은 [audit-gate.md](audit-gate.md) 렌더 측정 계약을 따른다
- [ ] **W3** 크롬(탑바·앱바·툴바)에 컨트롤을 더했다면 그 줄의 최소폭 합을 가장 좁은 폭에서 다시 잰다. 줄이 넘으면 감싸기(2행 랩)가 기본 수습이다
- [ ] **W4** WCAG 200% 확대 또는 운영체제의 실제 글꼴 확대가 지원 범위라면 실제 브라우저·기기에서 별도 검증한다. gate의 font-scale 행렬은 회귀 신호이며 px 고정 폰트의 OS 확대를 대체하지 않는다. 못 했으면 미검증이다
- [ ] **W5** 1920px 이상에서는 본문 측정폭을 유지하고, 표·도면·작업대처럼 정보 밀도가 필요한 표면만 별도의 `--container-wide`로 넓힌다
- [ ] **W6** 분할 히어로는 1920·2560에서 `hero / copy / media / 다음 정보 레일` rect를 기록했다. 사진의 최대 폭과 카피의 최소 폭을 페이지 토큰으로 정했다
- [ ] **W7** hero 검증만으로 text-media 분할 검증을 끝내지 않는다. DOM/여백 규칙이 다른 모든 분할 표면에서 rail·copy·media·제목의 실제 rect와 줄 수를 기록한다. wide override가 양쪽 padding을 모두 재설정했는지, 상위 `max-width`가 named stage를 다시 줄이지 않는지 확인한다
- [ ] **W8** 중간 뷰포트(768~1024px)에서 레이아웃이 깨지지 않음 — **가장 자주 빠뜨리는 구간**
- [ ] **W9** 터치 타깃 — 두 층으로 판정한다. 기준의 정본은 [accessibility.md](accessibility.md) §8, 게이트 동작은 [audit-gate.md](audit-gate.md) "터치 타깃 — 두 층 판정"이다.
- `[하드 게이트]` 모든 경우: 인라인이 아닌 대상은 WCAG 2.5.8의 24×24 CSS px 이상이거나 간격 예외를 충족한다(A25). 문장 속 인라인 링크는 WCAG 예외다.
- `[계약]` 브리프가 모바일 앱 수준이거나 터치가 주 입력인 화면: 버튼·아이콘 버튼·카드 등 독립 컨트롤은 44×44px 이상을 권장 기준으로 삼는다(Apple HIG 44pt·Material 48dp 관례). 랜딩·데스크톱 위주 화면에서 44px 미달은 그 자체로 결함이 아니다. 어느 쪽을 택했는지 `design.md`와 게이트의 `tapTargetPolicy`에 적는다.
- `[계약]` 본문 안 인라인 텍스트 링크는 이 스킬의 계약으로 24px 높이(`inlineTargetMin`)를 본다. 여기에 44를 요구하면 정상적인 내비 링크가 오탐된다.
- [ ] **W10** 컨트롤의 내용 여백: 버튼 라벨·입력값·select 표시·표 셀의 실제 렌더 글자가 테두리에 닿지 않는다. 이 저장소의 실렌더 기본값은 좌우 **8 CSS px** 이상이다. WCAG 수치가 아니라 시각 품질 기본값이며, 아이콘 전용·체크박스·의도적인 데이터 정렬은 별도 근거를 적는다
- [ ] **W11** 폼 기하: 실제 렌더 rect로 입력끼리 겹치지 않는지 잰다. 좁은 폭에서는 필드를 세로로 쌓고, label·입력·오류 문장이 서로의 클릭/읽기 영역을 침범하지 않게 한다
- [ ] **W12** 표와 표처럼 보이는 flex/grid: 데이터 관계에는 `th`/`td`와 `scope`를 쓰고, 모든 가시 셀에 좌우 읽기 여백을 둔다. 좁은 폭에서 형식이 바뀌어도 헤더-값 관계가 남는다
- [ ] **W13** 호버로만 접근되는 기능 없음 `[하드 게이트]`
- [ ] **W14** 긴 단어/URL이 넘치지 않음 (`overflow-wrap: anywhere` 등. 한글 본문의 줄바꿈 규칙은 [typography.md](typography.md))
- [ ] **W15** 가로 모드에서 첫 화면이 잠기지 않음
- [ ] **W16** 모바일 첫 화면과 이동 수단: 390×844 첫 화면에 브랜드·컨셉 증거(대표 사진·데모)·제목·다음 행동 중 셋 이상이 보이고, 주요 섹션으로 가는 수단(내비 또는 메뉴 버튼)이 보인다. 판정은 루브릭 R10([critique.md](critique.md) §2-1)
> 근거: [W3C WCAG 2.5.8 Target Size](https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum) (최소 24 CSS px/간격 예외), [Apple HIG Layout](https://developer.apple.com/design/human-interface-guidelines/layout) (읽기 폭·음의 공간·다양한 화면 크기), [Apple HIG Text Fields](https://developer.apple.com/design/human-interface-guidelines/text-fields?changes=_7) (일관된 필드 폭·간격·세로 배치), [W3C Tables Tutorial](https://www.w3.org/WAI/tutorials/tables/tips/) (반응형에서도 데이터 관계 보존), [NN/g Form White Space](https://www.nngroup.com/articles/form-design-white-space/) (인접 요소의 간격이 관계를 전달함). 조사일 2026-08-30.
@ -170,94 +199,121 @@ canvas { display: none !important; }
## 4. 콘텐츠
- [ ] 히어로가 **무엇인지** 말한다. 형용사만 있고 명사가 없으면 실패
- [ ] 로렘입숨이 남아 있지 않다
- [ ] 실제 콘텐츠 길이로 테스트했다 (짧은 제목만으로 맞춘 레이아웃은 긴 제목에서 깨진다)
- [ ] 빈 상태·오류 상태·로딩 상태가 있다
- [ ] 링크가 어디로 가는지 텍스트만 보고 알 수 있다 ("여기를 클릭" 금지)
- [ ] 이미지가 없거나 실패했을 때 레이아웃이 무너지지 않는다(`aspect-ratio` 지정)
- [ ] **C1** 히어로가 **무엇인지** 말한다. 형용사만 있고 명사가 없으면 실패
- [ ] **C2** 로렘입숨이 남아 있지 않다
- [ ] **C3** 실제 콘텐츠 길이로 테스트했다 (짧은 제목만으로 맞춘 레이아웃은 긴 제목에서 깨진다)
- [ ] **C4** 데이터를 불러오거나 목록·검색·폼이 있는 화면은 빈 상태·오류 상태·로딩 상태가 있다. 정적 콘텐츠만 있는 페이지는 해당 없음이다
- [ ] **C5** 링크가 어디로 가는지 텍스트만 보고 알 수 있다 ("여기를 클릭" 금지)
- [ ] **C6** 이미지가 없거나 실패했을 때 레이아웃이 무너지지 않는다(`aspect-ratio` 지정)
- [ ] **C7** `[하드 게이트]` 사용자가 주지 않은 사실(지표·통계·후기·고객 수, 사람 이름·약력·가격·주소)을 그럴듯한 값으로 넣지 않았다. placeholder는 [product-copy.md](product-copy.md) §16 규약대로 보이고 목록으로 보고했다. 처리 방법은 [SKILL.md](../SKILL.md) 하드 게이트 7
- [ ] **C8** 화면에 적은 수치는 전부 그 화면을 캡처해 잰 값이다(추정치·기대치가 섞여 있지 않다)
---
## 4-1. 상태 완결성 — 동작하는 화면의 빈틈
정적 마케팅 페이지에는 없는 검사다. **입력·파괴·열림**이 있는 화면(폼·콘솔·앱)은 네 가지를 갖춰야 프로덕션이다. 실제로 조작해 본다 — 눈으로 봐서는 안 보인다(실측: 범위 밖 101점이 조용히 평균에 반영되고 있었다).
정적 마케팅 페이지에는 없는 검사다. **입력·파괴·열림**이 있는 화면(폼·콘솔·앱)은 아래를 갖춰야 프로덕션이다. 실제로 조작해 본다 — 눈으로 봐서는 안 보인다.
- [ ] **수치 입력에는 범위가 있다** — 범위 밖 값은 인라인 오류로 이유를 말하고 제출을 막는다(`aria-invalid`). 스트랩 검증만 믿지 마라: `type="number"` 의 `min`/`max` 는 직접 입력을 막지 못한다
- [ ] **판정은 순수 함수다** — 가능/불가+사유의 판정 로직은 UI 핸들러에 두지 않고 `calc.js` 같은 모듈로 추출해 단위 테스트(L1)가 같은 판정을 공유하게 한다. UI 는 판정 결과를 문장으로 번역만 한다. 실행취소의 재심사도 같은 함수를 쓴다
- [ ] **파괴적 행동에는 되돌림이 있다** — 삭제·취소는 실행취소 또는 확인. 되돌리기가 확인보다 낫다(흐름이 끊기지 않는다). 재신청·복원은 원래 판정 함수로 다시 심사한다 — 사이에 끼어든 다른 변화가 있으면 정당하게 막혀야 한다
- [ ] **열리는 것은 Esc 로 닫힌다** — 모달만이 아니다. 드롭다운·팝오버·알림 메뉴도. 닫힐 때 포커스는 연 요소로 돌아간다
- [ ] native `<dialog>` 또는 동등 모달은 전역 reset 뒤에도 `position: fixed; inset: 0; margin: auto`와 viewport 상한/내부 scroll owner를 가진다. 390·1440·2560에서 중심점, 화면 안 rect, 배경 scroll lock을 실제로 단언한다. W3C의 modal dialog 키보드·포커스·닫기 계약을 따른다: [WAI-ARIA APG](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/), [MDN dialog](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog)
- [ ] **[프로젝트 계약]** 모달 액션 행은 스크롤 영역과 분리된 스티키 푸터에 둔다 — 콘텐츠가 스크롤되어도 확인·취소 같은 핵심 액션은 항상 보인다. 리사이즈 가능한 패널 바닥이나 고정 높이 모달의 폴드 아래처럼 잘릴 수 있는 자리에 크리티컬 액션을 두지 않는다 [SKILL-BETTER-LAYOUT]
- [ ] **로딩 상태는 실제 비동기에만** — 동기 인라인 데이터에 스켈레톤을 붙이는 건 저장을 흉내내는 것이다. 없는 지연을 만들지 마라
- [ ] **[프로젝트 계약]** 변형 상태 매트릭스 — 새 컴포넌트·variant는 hover·focus·active·disabled·loading·selected 여섯 상태를 갖추었는지 표로 확인한다. 일부 상태만 구현하고 넘어가는 것이 가장 흔한 누락이다 [SKILL-INTERFACE-REVIEW]
- [ ] **[프로젝트 계약]** 오류·빈 상태 문구는 "존재"만으로 충분하지 않다 — 무엇이 잘못됐는지·어떻게 고치는지를 비난하지 않는 톤으로 말하는 내용 기준은 [product-copy.md](product-copy.md)를 따른다 [SKILL-BETTER-WRITING]
- [ ] **[하드 게이트]** 낙관적 업데이트는 실패하면 되돌린다 — 즉시 반응이 필요한 저위험 토글(좋아요·북마크 등)에 낙관적 업데이트를 쓸 때는 실패 시 같은 판정 함수로 원상 롤백하고, 실패를 토스트·인라인 오류로 알린다. "판정은 순수 함수다"(위) 원칙과 연결된다 [SKILL-INTERACTION-DESIGN](근거: SKILL.md 하드 게이트 5 — 실패를 성공으로 남기지 않는다)
- [ ] **[프로젝트 계약]** 제스처·모션은 실제로 조작해 QA한다 — 커스텀 컨트롤의 제스처 QA는 [interaction-feel.md](interaction-feel.md) §11, 모션 QA(배속 재생·프레임 단위 점검)는 [motion.md](motion.md) §5를 따른다 [SKILL-EMIL-DESIGN-ENG] [SKILL-IMPECCABLE]
- [ ] **S1** **수치 입력에는 범위가 있다** — 범위 밖 값은 인라인 오류로 이유를 말하고 제출을 막는다(`aria-invalid`). `type="number"` 의 `min`/`max` 는 직접 입력을 막지 못한다
- [ ] **S2** **판정은 순수 함수다** — 가능/불가+사유의 판정 로직은 UI 핸들러에 두지 않고 `calc.js` 같은 모듈로 추출해 단위 테스트(L1)가 같은 판정을 공유하게 한다. UI 는 판정 결과를 문장으로 번역만 한다. 실행취소의 재심사도 같은 함수를 쓴다
- [ ] **S3** `[하드 게이트]` **파괴적 행동에는 되돌림이 있다** — 삭제·취소는 실행취소 또는 확인. 되돌리기가 확인보다 낫다. 재신청·복원은 원래 판정 함수로 다시 심사한다
- [ ] **S4** `[하드 게이트]` **열리는 것은 Esc 로 닫힌다** — 모달만이 아니다. 드롭다운·팝오버·알림 메뉴도. 닫힐 때 포커스는 연 요소로 돌아간다
- [ ] **S5** native `<dialog>` 또는 동등 모달은 전역 reset 뒤에도 `position: fixed; inset: 0; margin: auto`와 viewport 상한/내부 scroll owner를 가진다. 390·1440·2560에서 중심점, 화면 안 rect, 배경 scroll lock을 실제로 단언한다. 키보드·포커스·닫기 계약은 [WAI-ARIA APG](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/), [MDN dialog](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog)를 따른다
- [ ] **S6** 모달 액션 행은 스크롤 영역과 분리된 스티키 푸터에 둔다 — 확인·취소 같은 핵심 액션은 항상 보인다 [SKILL-BETTER-LAYOUT]
- [ ] **S7** **로딩 상태는 실제 비동기에만** — 동기 인라인 데이터에 스켈레톤을 붙이지 않는다
- [ ] **S8** 변형 상태 매트릭스 — 새 컴포넌트·variant는 hover·focus·active·disabled·loading·selected 여섯 상태를 갖추었는지 표로 확인한다 [SKILL-INTERFACE-REVIEW]
- [ ] **S9** 오류·빈 상태 문구는 무엇이 잘못됐는지·어떻게 고치는지를 비난하지 않는 톤으로 말한다. 내용 기준은 [product-copy.md](product-copy.md) [SKILL-BETTER-WRITING]
- [ ] **S10** `[하드 게이트]` 낙관적 업데이트는 실패하면 같은 판정 함수로 원상 롤백하고, 실패를 토스트·인라인 오류로 알린다(근거: SKILL.md 하드 게이트 5) [SKILL-INTERACTION-DESIGN]
- [ ] **S11** 제스처·모션은 실제로 조작해 QA한다 — 커스텀 컨트롤의 제스처 QA는 [interaction-feel.md](interaction-feel.md) §11, 모션 QA는 [motion.md](motion.md) §5를 따른다. 실기기를 못 쓰면 증거 출처(에뮬레이션·엔진명)를 적고 실기기 항목은 미검증으로 남긴다 [SKILL-EMIL-DESIGN-ENG] [SKILL-IMPECCABLE]
---
## 4-2. 진실 완결성 — 가상·예시·AI·규제 화면
가상 브랜드·예시 데이터·AI 분석·규제 주제를 실제 서비스처럼 보이게 만든 화면에만 돈다. 전역 데모 띠 하나로 끝내지 않는다. 상세 설계는 `trustworthy-showcases.md`.
가상 브랜드·예시 데이터·AI 분석·규제 주제를 실제 서비스처럼 보이게 만든 화면에만 돈다. 상세 설계는 [trustworthy-showcases.md](trustworthy-showcases.md). 이 절은 그 문서 §13 통과 조건의 납품 판정판이다.
- [ ] 모든 숫자·주소·상태·추천이 **사실 / 예시 / 추론 / 미정** 중 하나이고 `USER_SELF_REPORT / VERIFIED_SOURCE / SYSTEM_STATE / SYNTHETIC_FIXTURE` 출처 계층이 추적된다. 자기보고 값 자체를 검증 사실로 승격하지 않았다
- [ ] `예시 가격`, `거래 불가`, `실제 접수 아님`, `연출 이미지` 라벨이 오해가 생기는 범위 가까이에 있다. 같은 상태·출처·행동 가능성을 공유하는 자식은 가장 가까운 공통 부모가 한 번 소유하고, 다른 자식만 예외 라벨을 가진다
- [ ] 검증하지 않은 수익·가맹비·매장 수·등록번호·연락처를 만들지 않았다. 모르면 `미정`이다
- [ ] AI 결과에 입력 조건·출처가 붙은 입력/검증 사실·추론·불확실/누락·수정·사람 검토가 있고, 입력을 바꾸면 결과와 근거가 함께 바뀐다. 모델 출력 내용을 `SYSTEM_STATE` 사실로 두지 않았다
- [ ] 진단·개인화 추천·예약·전송·거래·게시·내보내기 등 실제 규제 행동은 행동별 필수 검증이 없을 때 **실패 폐쇄형**이며, 미등록 행동은 기본 거절되고 막힌 이유가 화면에 보인다
- [ ] 민감 입력과 파생값의 영속 저장·쿠키·분석 이벤트 포함·네트워크 전송 경계를 E2E로 검증했다
- [ ] 공개 화면은 사용자 해석·제한·다음 행동을, 관리자는 내부 원장·검증 이벤트·채널 판정·감사 로그를 소유한다. 사용자 행동에 영향을 주는 제한을 관리자에만 숨기지 않았다
- [ ] 지도·차트·도식의 같은 정보가 DOM 목록·표·설명으로도 제공되고 데이터 원천이 하나다
- [ ] 생성 이미지는 가격·주소·점수의 증거로 쓰이지 않으며, 프로젝트가 채택한 모바일 뷰와 종횡비에서 핵심 피사체가 첫 화면의 의도와 맞는다
- [ ] URL별 390·1440 기본 화면과 핵심 입력/오류/모달 상태 캡처를 실제로 열어 보고 시각 PASS/RED를 따로 기록했다
- [ ] **T1** `[하드 게이트]` 모든 숫자·주소·상태·추천이 **사실 / 예시 / 추론 / 미정** 중 하나이고 `USER_SELF_REPORT / VERIFIED_SOURCE / SYSTEM_STATE / SYNTHETIC_FIXTURE` 출처 계층이 추적된다. 자기보고 값 자체를 검증 사실로 승격하지 않았다
- [ ] **T2** 진실 라벨이 오해가 생기는 범위 가까이에 있다. 같은 상태·출처·행동 가능성을 공유하는 자식은 가장 가까운 공통 부모가 한 번 소유하고, 다른 자식만 예외 라벨을 가진다. 라벨은 위치와 범위로 해결하고, 히어로 헤드라인·본문의 목소리를 대신하지 않는다(루브릭 R7). h1·히어로 본문·헤드라인의 고지는 0, 페이지 범위 고지는 두 곳 이하다([trustworthy-showcases.md](trustworthy-showcases.md) §4-b 라벨 예산)
- [ ] **T3** `[하드 게이트]` 검증하지 않은 수익·가맹비·매장 수·등록번호·연락처를 만들지 않았다. 모르면 `미정`이다
- [ ] **T4** AI 결과에 입력 조건·출처가 붙은 입력/검증 사실·추론·불확실/누락·수정·사람 검토가 있고, 입력을 바꾸면 결과와 근거가 함께 바뀐다
- [ ] **T5** 진단·개인화 추천·예약·전송·거래·게시·내보내기 등 실제 규제 행동은 필수 검증이 없을 때 **실패 폐쇄형**이며, 막힌 이유가 화면에 보인다
- [ ] **T6** 민감 입력과 파생값의 영속 저장·쿠키·분석 이벤트 포함·네트워크 전송 경계를 E2E로 검증했다
- [ ] **T7** 공개 화면은 사용자 해석·제한·다음 행동을, 관리자는 내부 원장·검증 이벤트·감사 로그를 소유한다. 사용자 행동에 영향을 주는 제한을 관리자에만 숨기지 않았다
- [ ] **T8** 지도·차트·도식의 같은 정보가 DOM 목록·표·설명으로도 제공되고 데이터 원천이 하나다
- [ ] **T9** `[하드 게이트]` 생성 이미지는 가격·주소·점수의 증거로 쓰이지 않는다. 실제 증거로 오인될 가짜 스크린샷·브라우저바·폰 프레임에 라벨이 있다
- [ ] **T10** 보이는 입력과 판정 의존성이 한 SSOT이고, 분석 전 완료 결과가 없다
- [ ] **T11** E2E 시나리오·단언·캡처 수가 잠겨 있고 재귀 정적 검사가 새 서브페이지를 포함한다(G20·G21)
## 4-3. 썸네일·OG 캡처 — 움직이는 화면을 대표하지 않는다
썸네일은 '페이지가 열렸다'는 증거가 아니라 사람들이 처음 보는 **정지된 브랜드 표면**이다. 로딩 중인 폰트, 이미지 디코드 전 프레임, 입장 모션 중간, 다음 WebGL 프레임을 파일로 남기면 정상 사이트도 카드에서 망가져 보인다.
썸네일은 사람들이 처음 보는 **정지된 브랜드 표면**이다. 로딩 중인 폰트, 디코드 전 이미지, 입장 모션 중간, 다음 WebGL 프레임을 파일로 남기면 정상 사이트도 카드에서 망가져 보인다.
- [ ] `file://` 즉시 캡처 대신 실제 배포와 같은 상대 URL을 해석하는 로컬 HTTP 서버에서 고정 viewport로 연다. 로컬 4xx, runtime error, `naturalWidth === 0` 이미지는 캡처 성공이 아니라 실패다
- [ ] `document.fonts.ready`와 모든 가시 이미지의 `decode()`를 기다린 뒤, 페이지별로 정한 **bounded settle 시간**을 지난다. `document.fonts.ready`는 사용 중인 폰트의 로드와 레이아웃 작업이 끝날 때 이행한다
- [ ] 캡처 환경은 `prefers-reduced-motion: reduce`를 에뮬레이션하고, 영상·오디오는 정지하며 CSS/WAAPI 애니메이션과 rAF 기반 canvas를 **정착한 한 프레임**에 멈춘다. `animation: none`으로 초기 상태를 되돌려 빈 화면을 만들지 말고, 실제로 완성된 프레임을 pause/finish 한다
- [ ] 기본 컷·모달/결과 컷·OG 모두 같은 계약으로 찍고, 파일의 규격·0바이트·해시 중복을 검사한 뒤 적어도 움직임이 있던 대표 컷을 눈으로 연다. 생성 이미지가 있으면 기존 진실 라벨 계약도 그대로 보존한다
- [ ] **O1** `file://` 즉시 캡처 대신 실제 배포와 같은 상대 URL을 해석하는 로컬 HTTP 서버에서 고정 viewport로 연다. 로컬 4xx, runtime error, `naturalWidth === 0` 이미지는 캡처 실패다
- [ ] **O2** `document.fonts.ready`와 모든 가시 이미지의 `decode()`를 기다린 뒤, 페이지별로 정한 **bounded settle 시간**을 지난다
- [ ] **O3** 캡처 환경은 `prefers-reduced-motion: reduce`를 에뮬레이션하고, 영상·오디오는 정지하며 CSS/WAAPI 애니메이션과 rAF 기반 canvas를 **정착한 한 프레임**에 멈춘다. `animation: none`으로 초기 상태를 되돌려 빈 화면을 만들지 않는다
- [ ] **O4** 기본 컷·모달/결과 컷·OG 모두 같은 계약으로 찍고, 파일의 규격·0바이트·해시 중복을 검사한 뒤 움직임이 있던 대표 컷을 눈으로 연다
> 근거: [MDN `Document.fonts`](https://developer.mozilla.org/en-US/docs/Web/API/Document/fonts) (`ready`는 사용 중인 폰트의 로드와 레이아웃 완료 뒤 이행), [MDN `HTMLImageElement.decode()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/decode), [MDN `prefers-reduced-motion`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion) (불필요한 모션을 줄이거나 대체하는 사용자 선호). 조사일 2026-08-30.
> 근거: [MDN `Document.fonts`](https://developer.mozilla.org/en-US/docs/Web/API/Document/fonts) (`ready`는 사용 중인 폰트의 로드와 레이아웃 완료 뒤 이행), [MDN `HTMLImageElement.decode()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/decode), [MDN `prefers-reduced-motion`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion). 조사일 2026-08-30.
## 4-4. 조건부 모듈 — 쓴 기술·표면이 있을 때만
해당하는 행만 돈다. 각 행은 그 문서의 체크리스트 중 **납품 때 확인해야 하는 것**만 모았다. 구현 중 세부 점검은 정본 문서에서 한다.
| ID | 조건 | 납품 판정 | 정본 |
|---|---|---|---|
| X1 | 모션·전환을 썼다 | duration·이징이 토큰(`var(--dur-*)`·`var(--ease-*)`)이다 / 첫 화면 콘텐츠가 애니메이션 없이 즉시 읽힌다 / 스크롤 리빌이 위아래로 오갈 때 재생되지 않는다 / 감소 모션은 A7, 자동재생은 A8, 깜빡임은 A19로 판정한다 / 라이브러리 인스턴스가 이탈 시 정리된다 / 채택·기각한 모션을 `design.md`에 적었다 | [motion.md](motion.md) §5 |
| X2 | 패럴랙스·스크롤 연동 깊이를 썼다 | 한 층 이상이 고정 기준면이다 / `background-attachment: fixed`가 없다 / 감소 모션에서 정지 배경으로 대체된다 | [motion-parallax.md](motion-parallax.md) §12 |
| X3 | SVG 필터·`backdrop-filter`를 썼다 | 필터가 만드는 재질을 한 단어로 말할 수 있다 / 필터 건 요소 안에 본문 텍스트나 `position: fixed` 자손이 없다 / `backdrop-filter`가 뷰포트 전체를 덮지 않는다 / Safari·모바일 실기기 또는 CPU 스로틀링에서 확인했고, 못 했으면 미검증이다 유리면 가장 밝은 뒤 장면과 가장 어두운 뒤 장면 둘 다에서 대비를 쟀다(A3) | [svg-filters.md](svg-filters.md) §8, [svg-glass.md](svg-glass.md) |
| X4 | three.js·WebGL을 썼다 | `three`가 초기 번들에 없다 / 추가분을 빌드 후 gzip으로 실측했다(P2) / 캔버스에 `aria-hidden="true"`, 히어로 텍스트는 DOM에 있다 / 컨텍스트 로스 때 폴백이 뜬다 / 라우트 이탈 시 `dispose()` / 실기기 프레임률은 확인했거나 미검증으로 적었다 | [three.md](three.md) §11 |
| X5 | HTML-in-Canvas를 얹었다 | 캔버스를 지워도 콘텐츠가 보이고 동작한다 / Tab 순서가 시각 순서와 같다 / 셰이더 적용 후에 텍스트 대비를 쟀다 / 캔버스 안에서 한글 입력을 요구하지 않는다 | [experimental-canvas.md](experimental-canvas.md) §8 |
| X6 | 색 체계를 새로 만들거나 바꿨다, 또는 테마가 둘 이상인 프로젝트에 화면을 더했다 | 지원하는 모든 외형(라이트·다크)에서 대비를 실측했다(A3) / 다크모드를 반전이 아니라 재조정했다 / 테마 전환 메커니즘이 하나다 / P3를 썼다면 sRGB 폴백이 먼저 선언돼 있다 | [color.md](color.md) §12 |
| X7 | UI 카피를 새로 쓰거나 바꿨다 | 같은 개념을 화면마다 다른 말로 부르지 않는다 / 한 화면에서 해요체·합니다체가 섞이지 않는다 / 버튼 라벨이 동사형이고 결과를 말한다 / 내부 구현 용어가 사용자 라벨로 새지 않았다 / 변수 주변에 문장 조각을 조립하지 않았다 | [product-copy.md](product-copy.md) §15 |
| X8 | 아이콘을 넣거나 바꿨다 | 한 표면에 아이콘 세트가 섞이지 않는다 / 가장 작은 렌더 크기에서 실제로 확인했다 / 아이콘+텍스트의 광학 정렬을 렌더에서 확인했다 | [icons.md](icons.md) §10 |
| X9 | 컴포넌트 라이브러리(shadcn 등)를 썼거나 폼·데이터 테이블을 만들었다 | 설정 파일과 정보 출력을 실제로 확인했다 / Dialog·Sheet·Drawer에 접근 가능한 제목이 있다 `[하드 게이트]` / 덮어쓰기 옵션을 승인 없이 쓰지 않았다 / 폼은 §10(배치·필수 표시·오류 위치·한국 폼 관례), 표는 §11(헤더 관계·정렬 상태·좁은 폭 전략)을 따랐다 | [component-systems.md](component-systems.md) §12 |
| X10 | 커스텀 인터랙션(드래그·스와이프·토스트·프레스 피드백)을 만들었다 | hover 전용 스타일이 `(hover: hover) and (pointer: fine)`로 게이트돼 있다 / 상태 변화마다 모션이 꺼져도 남는 정적 신호가 있다 / 행동이 있는 토스트는 자동 소멸하지 않는다 / 드래그 대안은 A21 | [interaction-feel.md](interaction-feel.md) §12 |
| X11 | 인쇄 화면을 지원한다 | `@media print`로 앱 크롬을 숨겼다 / 카드·표 행이 페이지 경계에서 잘리지 않는다 / 인쇄 미리보기·PDF를 실제로 열어 봤거나 미검증으로 적었다 | [print-email.md](print-email.md) §5 |
| X12 | HTML 이메일을 만든다 | 단일 컬럼 약 600px / 이미지가 막혀도 핵심 텍스트가 읽힌다 / 광고성 메일이면 제목 첫머리 `(광고)`·전송자 명칭과 연락처·수신거부 링크가 있다 `[하드 게이트]` / 실제 메일 클라이언트에서 확인했거나 미검증으로 적었다 | [print-email.md](print-email.md) §5 |
| X13 | 레이아웃 단계를 새로 거쳤다 | CSS/HTML만으로 페이지가 완성되고 JS를 꺼도 읽힌다 / 본문에 측정폭 토큰이 적용됐다 / 역할이 다른 연속 섹션 셋이 같은 골격이 아니다(G24) | [layout.md](layout.md) §5 |
| X14 | 외부 스크립트를 불렀거나 사용자·외부 데이터를 DOM에 넣었다 | 외부 스크립트는 버전 고정 + SRI(import map `integrity` 포함) 또는 self-host다 / 사용자·외부 데이터를 `innerHTML`·템플릿 문자열 보간으로 넣지 않는다(`textContent`·DOM API) | [experimental-canvas.md](experimental-canvas.md) §7, [motion.md](motion.md) 코드 G |
| X15 | 모바일 앱 수준·PWA다 | 뒤로가기가 앱 내 히스토리를 따른다(앱을 닫지 않는다) / safe area가 적용됐다 / 입력 포커스에서 화면이 확대되지 않는다 / 뷰 전환이 스크롤을 리셋한다 / 실기기·에뮬레이터로 확인했거나 미검증으로 적었다 | [mobile-app-ux.md](mobile-app-ux.md) |
| X16 | 두 스타일을 섞었다(리믹스) | 지배 식별·보조 식별·경계 세 캡처를 확인했다 | [remix.md](remix.md) 리믹스 렌더 검증 |
| X17 | 커머스·SaaS·에디토리얼·포트폴리오·동네 가게·전시와 행사·B2B 서비스 장르다 | 해당 절의 필수 블록이 있거나 빠진 이유를 적었다 / 결정을 바꾸는 정보가 행동 버튼 근처에 있다 | [genre-conventions.md](genre-conventions.md) §7(장르 절은 §1~§6·§8~§10) |
| X18 | 그림자·반투명 표면을 정의했다 | 지원하는 테마마다 깊이 단계가 구분된다 / forced-colors에서 경계가 남는다 | [elevation.md](elevation.md) §9 |
| X19 | 화면·페이지가 둘 이상이다 | 공통 셸(헤더·내비·푸터·페이지 폭)이 화면마다 같다 / 문서 머리(`title`·`description`)가 페이지마다 다르다 / 없는 주소(404) 화면이 있다 / 루브릭의 대표 화면을 보고에 적었다 | [layout.md](layout.md) §4-m |
---
## 5. 디자인 자체
## 5. 디자인 자체 — 시각 판정
기계로 못 잡는 부분이다. **정직하게 답해라.**
기계로 못 잡는 부분이다. **정직하게 답해라.** 판정 방법은 [critique.md](critique.md) §2-1 루브릭과 §2-2 스크린샷 절차다. 첫인상·실제 과업 행동·전문가 시각 검토의 기록 방식은 [design-foundations.md](design-foundations.md)의 평가 절차를 따른다.
첫인상·실제 과업 행동·접근성 적합성·전문가 시각 검토가 필요한 경우, [design foundations의 평가 절차](design-foundations.md)를 읽고 각 결과를 분리해 기록한다.
1. **이 사이트를 다른 회사의 것으로 바꿔도 그대로 쓸 수 있는가?** → 그렇다면 브리프에 맞춘 것이 없다
2. **2단계에서 정한 한 문장 컨셉이 화면에서 보이는가?** → 안 보이면 컨셉이 문서에만 있는 것이다
3. **감수하기로 한 리스크가 실제로 들어갔는가?** → 구현 중에 안전한 쪽으로 후퇴하지 않았는지
4. **사용한 레퍼런스의 원칙이 브리프와 렌더 결과에 맞게 번역됐는가?** → 슬롯 수나 업종 차이 자체로 판단하지 않는다
5. **강조색·radius·정렬·반복이 프로젝트 토큰과 과업에 맞는가?**
6. **여백이 의도적인가, 남은 것인가?**
7. **감수하기로 한 리스크는 몇 개이고, 하나로 압축할 수 있는가?** → 여러 개를 동시에 감수했다면 서로 상쇄해 사실상 아무 것도 감수하지 않은 것과 같다 [SKILL-FRONTEND-DESIGN]
반사실 제네릭 점검("이 선택이 다른 업종의 브리프에도 그대로 나왔을 것인가")은 2단계(방향 결정)에서 먼저 하고 design.md에 기록한다. 여기서는 그 결정이 구현 중 안전한 쪽으로 후퇴하지 않았는지만 다시 본다.
- [ ] **V1** `[방향]` 스크린샷 기반 자기 평가(critique §2-2)를 실제 캡처로 돌렸고, 루브릭 통과선(0점 축 없음, R1·R8 중 2점 하나 이상 — 제품 표면은 R1·R2, 합계 70% 이상)을 넘었다. 축별 점수와 근거를 약한 축부터 `design.md`에 적었다. 캡처를 열 수 없었다면 미검증이다
- [ ] **V2** `[방향]` 반사실·수렴 점검([art-direction.md](art-direction.md) §3-2)을 실제 렌더로 다시 돌렸다. 다른 클라이언트에게 옮기면 어색해지는 요소가 첫 화면에 둘 이상 있고, 같은 저장소의 다른 산출물과 구성 수렴 5축 중 셋 이상, 페이지 수렴 6축 중 넷 이상이 같지 않다(art-direction §3-2 B-1·B-2). 토큰 수렴 4축(서체·배경 명도·강조 hue·radius — presets/README)도 실제 토큰 값으로 다시 대조했다
- [ ] **V3** `[방향]` 2단계에서 정한 한 문장 컨셉이 1440·390 첫 화면에서 보인다. 안 보이면 컨셉이 문서에만 있는 것이다
- [ ] **V4** 감수하기로 한 리스크가 실제로 들어갔다. 구현 중에 안전한 쪽으로 후퇴하지 않았다. 여러 리스크를 동시에 감수했다면 서로 상쇄하지 않는지 보고, 하나로 압축할 수 있는지 검토했다 [SKILL-FRONTEND-DESIGN]
- [ ] **V5** 사용한 레퍼런스의 원칙이 브리프와 렌더 결과에 맞게 번역됐다. 슬롯 수나 업종 차이 자체로 판단하지 않는다
- [ ] **V6** `[휴리스틱]` 강조색·radius·정렬·반복·여백이 프로젝트 토큰과 과업에 맞고, 여백이 남은 것이 아니라 의도된 것이다. [antipatterns.md](antipatterns.md) 맥락 질문의 결정을 기록했다
- [ ] **V7** `[방향]` 열린 Direction finding이 없다. 있다면 2단계(연장·국소는 3·4단계)로 돌아가 재작업했거나, 사용자가 캡처를 보고 수용한 기록이 `design.md`에 있다(critique §4 해제 조건)
---
## 6. 최종 게이트
전부 통과해야 끝난다.
실패 행이 하나도 없어야 끝난다. 미검증 행은 G-12대로 보고하면 납품할 수 있고, 충족으로 세지 않는다. 실기기·스크린리더처럼 대부분의 하네스에서 확인할 수 없는 행 때문에 완료가 막히지는 않는다 — 숨기지 않고 적는 것이 조건이다.
- [ ] 0장 기계 검사 4종 통과
- [ ] 접근성 표 전 항목 통과
- [ ] 성능 예산 내 (또는 초과 이유 명시)
- [ ] 반응형 체크리스트 통과
- [ ] 콘텐츠 체크리스트 통과
- [ ] 상태 완결성(§4-1) — 입력 검증·되돌림·Esc 를 실제 조작으로 확인
- [ ] 해당하면 진실 완결성(§4-2) — 예시·AI·규제·생성 이미지의 오해 경로를 실제 상태로 확인
- [ ] 정적 CSS/JS의 stable URL을 바꿨다면 HTML 링크와 `@import` 의 버전을 함께 올렸고, 공개 CDN에서 실제 내려온 stylesheet URL과 새 토큰/규칙을 확인했다. HTML만 cache-bust하고 CSS가 이전 버전이면 배포 성공이 아니다
- [ ] 기능·접근성 자동 검사의 결과와 시각 평가(위계·브랜드·타입·구도·이미지·리듬)의 결과를 별도로 기록했다. 한쪽 PASS를 다른 쪽 PASS로 바꾸어 말하지 않았다
- [ ] 5장 여섯 질문과 `antipatterns.md` 검토를 프로젝트 계약·실제 렌더 근거로 기록했다
- [ ] **G-1** §0 M-A~M-E 기계 검사 통과
- [ ] **G-2** §1 접근성 표에 실패 행이 없다(미검증 행은 따로 센다)
- [ ] **G-3** 성능 예산 내 (또는 초과 이유 명시)
- [ ] **G-4** §3 반응형 체크리스트 충족
- [ ] **G-5** §4 콘텐츠 체크리스트 충족
- [ ] **G-6** 해당하면 상태 완결성(§4-1) — 입력 검증·되돌림·Esc 를 실제 조작으로 확인
- [ ] **G-7** 해당하면 진실 완결성(§4-2) — 예시·AI·규제·생성 이미지의 오해 경로를 실제 상태로 확인
- [ ] **G-8** 해당하는 조건부 행(§4-3·§4-4) 충족
- [ ] **G-9** 정적 CSS/JS의 stable URL을 바꿨다면 HTML 링크와 `@import` 의 버전을 함께 올렸고, 공개 CDN에서 실제 내려온 stylesheet URL과 새 토큰/규칙을 확인했다(G32)
- [ ] **G-10** 기능·접근성 자동 검사의 결과와 시각 평가(§5, 루브릭 축별 점수)를 별도로 기록했다. 한쪽 PASS를 다른 쪽 PASS로 바꾸어 말하지 않았다
- [ ] **G-11** §5 V1~V7 충족. 열린 Direction finding 0건
- [ ] **G-12** 납품 보고를 [critique.md](critique.md) §13-b 형식으로 썼고, 검증 줄은 audit-gate "완료 보고 첫 줄 — 정본"의 형식이다. M > 0 이면 "모두 통과", "완료"라고 쓰지 않고 무엇이 있어야 검증되는지 적었다([audit-gate.md](audit-gate.md))
**하나라도 실패하면 출시가 아니라 4단계 복귀다.**
**하나라도 실패하면 출시가 아니라 4단계 복귀다.** Direction 실패는 2단계 복귀다. 같은 결함을 세 번 고쳐도 실패하면 네 번째로 고치지 말고 멈춰서 사용자에게 선택지를 내놓는다([audit-gate.md](audit-gate.md) "루프와 재작업 상한").
> 근거: research/references/04-ai-slop-signatures.md, research/motion/01-principles.md, research/three/04-performance.md (조사일 2026-08-20)
> 근거: 이 체크리스트의 슬롭 지문·모션·3D 성능 항목은 designpaca 저장소의 조사 기록(2026-08-20)에서 나왔다. 그 기록은 설치본에 포함되지 않는다. 항목별 출처 ID는 [evidence-ledger.md](evidence-ledger.md)에서 찾는다.