release: v0.11.0

This commit is contained in:
Yun Chan 2026-09-12 15:28:46 +09:00
parent 3c39a1731d
commit 45ba92ea82
45 changed files with 1459 additions and 1887 deletions

View file

@ -1,5 +1,11 @@
# designpaca
## 0.11.0
### Minor Changes
- 40개 근거의 종류·적용 범위·한계를 분리한 ledger와 불확실성 라우팅, 구도·타입·이미지·스타일 가이드, 규범 적합성과 취향·과업 평가의 구분, 적용 가능한 게이트 계약을 번들에 반영했다.
## 0.10.0
### Minor Changes

View file

@ -1,6 +1,6 @@
{
"name": "designpaca",
"version": "0.10.0",
"version": "0.11.0",
"description": "웹 디자인 파이프라인 스킬 — Claude Code · Codex · Cursor 에 한 줄로 설치한다",
"keywords": [
"design",

View file

@ -1,5 +1,11 @@
# @designpaca/core
## 0.11.0
### Minor Changes
- 40개 근거의 종류·적용 범위·한계를 분리한 ledger와 불확실성 라우팅, 구도·타입·이미지·스타일 가이드, 규범 적합성과 취향·과업 평가의 구분, 적용 가능한 게이트 계약을 동기화했다.
## 0.10.0
## 0.9.2

View file

@ -1,6 +1,6 @@
{
"name": "@designpaca/core",
"version": "0.10.0",
"version": "0.11.0",
"private": true,
"description": "designpaca 설치 엔진 — 타깃 어댑터, 매니페스트, 드리프트 감지",
"type": "module",

View file

@ -1,5 +1,15 @@
# CHANGELOG
## 0.11.0
### Minor Changes
- 40개 근거의 종류·적용 범위·한계를 분리한 ledger와, 판단이 애매할 때 불확실성에 맞춰 참조 문서를 여는 라우팅을 추가했다. 구도·타입·이미지·스타일 가이드, 규범 적합성과 취향·과업 평가의 구분, 적용 가능한 게이트 계약도 정비했다.
- 초광폭을 별도 레이아웃 상태로 취급하도록 1920·2560px 실렌더 매트릭스, 읽는 `--measure`와 넓은 정보 표면의 분리, 컨트롤·입력·표 셀의 실제 인셋/겹침 검사를 추가했다. 8 CSS px 인셋은 WCAG 수치가 아니라 프로젝트 품질 기본값으로 명시했다.
- CSS Color 4 `color(srgb …)`·알파 합성과 sr-only/aria-hidden/overlay의 가시 텍스트 판정을 감사 하니스 규칙으로 승격했다. UI가 아니라 측정기가 틀린 경우도 RED fixture에서 먼저 재현하도록 한다.
- 근거는 W3C WCAG, W3C Tables Tutorial, Apple HIG Layout/Text Fields, Nielsen Norman Group의 폼 여백 연구를 반영했다.
## 0.10.0
### Minor Changes

View file

@ -33,14 +33,14 @@ description: "웹 디자인 전 과정을 끌고 가는 파이프라인 스킬.
1. **레퍼런스 없이 시작하지 않는다.** 전체 경로에서 1단계를 건너뛴 디자인은 5단계에서 실패 처리한다. 연장·국소 경로는 `design.md` 가 그 자리를 대신한다 — 그것도 없이 건너뛰면 실패다.
2. **모든 시각적 결정에는 브리프로 소급되는 이유가 있어야 한다.** "보통 이렇게 한다"는 이유가 아니다.
3. **화려함은 4순위다.** Awwwards 배점이 Design 40 / Usability 30 / Creativity 20 / Content 10이다. 3D를 얹는 것보다 타입 스케일을 정돈하는 게 결과에 두 배 유효하다.
4. **대담함은 한 곳에만.** 리스크는 하나다. 나머지는 조용히 받쳐준다. 두 곳에서 소리치면 둘 다 죽는다.
3. **화려함만으로 통과하지 않는다.** Awwwards의 공개 평가 비중은 Design 40 / Usability 30 / Creativity 20 / Content 10이다. 이 비중은 해당 어워드의 심사 틀일 뿐, 제품 성과나 특정 기법의 효과를 보장하지 않는다. 출처와 적용 조건은 `references/evidence-ledger.md`에 남긴다.
4. **대담함은 의도와 검증을 같이 가진다.** 강한 선택을 채택하면 어떤 사용 과업·브랜드 목표를 돕는지, 어느 화면에서 어떻게 확인할지 적는다. 리스크의 개수는 보편 규칙이 아니라 브리프·표면 밀도·렌더 결과로 판단한다.
5. **성능 예산을 넘기면 그 이펙트는 채택하지 않는다.** 예산은 3단계에서 정하고 5단계에서 검증한다. 데모·포트폴리오 브리프면 예산을 올려도 되지만 **올렸다고 말해야 한다**.
6. **완료의 정의는 게이트 통과다(자율 검증 폐쇄 루프).** 5단계에서 `tools/design-gate.mjs` 를 프로젝트에 심어 돌리고, 실패 항목이 있으면 4단계로 돌아가 고치고 **전체를 재실행**한다. 게이트 없이 6단계로 넘어가지 않는다. 사용자를 QA 로 쓰지 않는다 — 검증은 기본 내장, 사용자 피드백은 보조다. 상세는 `references/audit-gate.md`. **게이트는 계측이고 계측엔 공백이 있다**(왼쪽 인셋·시각적 무게는 오버플로 검사를 통과한다). 사용자 보고가 오면 방어하지 말라 — 스크린샷을 픽셀 판정으로 해부해 원인을 수치화하고, 그 검사를 게이트에 추가해 재발을 막는다(하니스 규칙 15~17).
6. **완료는 기능·프로젝트 계약·렌더 시각 평가가 함께 확인된 상태다(자율 검증 폐쇄 루프).** 5단계에서 변경 영향 범위에 맞는 자동 검사와 실제 렌더 평가를 수행한다. 실패 항목이 있으면 4단계로 돌아가 고치고, 고친 영역과 그에 연결된 회귀 위험을 다시 검사한다. 사용자를 QA 로 쓰지 않는다 — 검증은 기본 내장, 사용자 피드백은 보조다. 상세는 `references/audit-gate.md`. **게이트는 계측이고 계측엔 공백이 있다**(왼쪽 인셋·시각적 무게는 오버플로 검사를 통과한다). 사용자 보고가 오면 방어하지 말라 — 스크린샷을 픽셀 판정으로 해부해 원인을 수치화하고, 그 검사를 게이트에 추가해 재발을 막는다(하니스 규칙 15~17).
7. **OS·플랫폼 관습은 조사 없이 바꾸지 않는다.** 내비 구조·뒤로가기·시트·safe area·터치 타깃 같은 관습은 사용자가 이미 OS 전체에서 배운 것이다. "웹에서는 그랬다"를 폰에 옮기면 백 키가 앱을 종료하는 웹앱이 된다(실측 사고). 바꾸기 전에 HIG·M3·NN/g 에서 근거를 찾고, 모바일 앱 수준 브리프면 `references/mobile-app-ux.md` 를 구현 전에 읽는다.
8. **언어와 문화권은 컴퓨터 로케일을 기본값으로 삼는다.** 새로 쓰는 UI 카피·메타데이터·빈 상태·오류 문구·장식 텍스트·이미지 안의 글자는 OS 로케일의 언어로 만든다. 영어는 기술 용어·국제 브랜드·짧은 보조 표기처럼 필요한 범위에서만 허용한다. 그 밖의 언어·문자(한국어 로케일에서는 한자·중국어·일본어 포함)와 이를 흉내 낸 인장·세로쓰기·서예·동아시아 이국화 장식은 사용자가 명시적으로 요청하거나 원본 브랜드 자산으로 제공하지 않는 한 만들지 않는다. 원본에 있는 고유명사·인용문은 임의로 고치지 않되, 에이전트가 새 레이블로 증식시키지 않는다.
**접근성은 예외다. 이것만은 협상하지 않는다.** 키보드·포커스·대비·`prefers-reduced-motion`은 어떤 이펙트보다, 어떤 브리프보다 우선한다. 픽셀은 왜곡해도 DOM은 살린다.
**판정 위계를 섞지 않는다.** (1) WCAG·키보드·포커스·대비·감소 모션·진실성처럼 규범 또는 기능에 근거한 항목은 하드 게이트다. (2) 프로젝트의 브리프·`design.md`·토큰·성능 예산은 그 프로젝트의 계약이다. (3) 색·radius·정렬·레이아웃 반복·마퀴·제목 줄 수 같은 스타일 휴리스틱은 관찰 후보일 뿐이다. 후보는 맥락 적합성과 실제 렌더를 보고 채택·수정·유지한다. 픽셀은 왜곡해도 DOM은 살린다.
## 파이프라인
@ -48,11 +48,25 @@ description: "웹 디자인 전 과정을 끌고 가는 파이프라인 스킬.
참조 문서는 **해당 단계에 들어갈 때 읽는다.** 처음부터 전부 읽지 마라 — 컨텍스트 낭비다.
### 불확실성 라우팅
결정이 애매할 때는 감으로 규칙을 더 만들지 말고, 아래에서 **필요한 문서만** 연다. 읽은 근거는 프로젝트의 `design.md` 또는 evidence 기록에 `질문 → 근거/적용 범위 → 결정 → 렌더·과업 검증`으로 남긴다.
| 판단 질문 | 읽을 문서 | 꺼내 쓸 항목 / 남길 결정 |
|---|---|---|
| 무엇을 먼저 보이게 하고, 어떤 구도·이미지·크롭이 과업을 돕는가? | `references/art-direction.md` | 콘텐츠 우선순위, 시선·여백·이미지 역할, 크롭 비교와 선택 이유 |
| 이 원칙이 보편 규범인가, 특정 사례·실무 지침인가? | `references/design-foundations.md` + `references/evidence-ledger.md` | 주장 종류(원론·규범·실무·사례·전망·서지), 출처의 조건과 적용 여부 |
| 이 브리프에 맞는 시각 언어와 피해야 할 관습은 무엇인가? | `references/style-playbook.md` | 스타일의 표현 수단·주의점, 브리프에 맞춘 변형 |
| 실제 언어·글꼴·숫자에서 읽히는가? | `references/typography.md` | 실제 카피 proof sheet, 폰트·폭·행간·폴백 결정 |
| 무엇을 어떤 범위까지 검사하고 완료라 할 수 있는가? | `references/preflight.md` + `references/audit-gate.md` | 기능·접근성 검사, 같은 조건의 렌더 평가, 영향 범위 |
자료가 부족하거나 오래됐거나 출처끼리 충돌하면, 권위 있는 원문·공식 규범·해당 분야의 사례를 필요한 범위에서 추가 조사한다. 출처를 많이 모으는 대신 **주장, 적용 범위, 반대 근거, 검증 방법**을 기록한다. 출처와 프로젝트 계약이 충돌하면 기존 우선순위(`사용자 지시 → design.md → 기존 토큰·코드 → 기본값`)를 적용하고, 규범·기능 요구의 적용 수준과 예외를 확인한다. 기능·접근성 요구와 계약이 충돌하면 양쪽을 충족할 대안을 먼저 찾고, 해소되지 않으면 충돌을 명시한다. 취향 휴리스틱으로 필수 요구를 덮지 않는다. 이 과정은 현재 프로젝트의 결정에 쓰는 것이며, 설치된 스킬 자체를 매번 고치는 지시는 아니다.
---
### 0단계 — 브리프 게이트
**먼저 프로젝트 루트에 `design.md` 가 있는지 확인한다.** (프로젝트 루트 = 패키지 매니저 설정 파일이 있는 디렉터리. 모노레포면 **작업 대상 패키지의 루트**를 쓰고, 저장소 루트에도 있으면 둘 다 읽되 가까운 쪽이 이긴다) 있으면 읽고, 그 결정을 이 스킬의 기본값보다 우선한다. 같은 프로젝트를 두 번째로 작업할 때 지난번과 다른 디자인이 나오면 그건 실패다.
**먼저 프로젝트 루트에 `design.md` 가 있는지 확인한다.** (프로젝트 루트 = 패키지 매니저 설정 파일이 있는 디렉터리. 모노레포면 **작업 대상 패키지의 루트**를 쓰고, 저장소 루트에도 있으면 둘 다 읽되 가까운 쪽이 이긴다) 있으면 읽고, 그 결정을 이 스킬의 기본값보다 우선한다. 같은 프로젝트는 기존 계약과 사용자의 기대를 유지한다. 다만 사용자가 명시적으로 리디자인을 요청했거나 감사 결과가 방향 변경을 뒷받침하면, 바꾸는 이유·보존할 자산·검증 조건을 기록하고 새 방향으로 진행한다.
브리프를 세 줄로 압축한다. 세 줄을 못 쓰면 아직 작업을 시작할 수 없다.
@ -129,46 +143,35 @@ description: "웹 디자인 전 과정을 끌고 가는 파이프라인 스킬.
결과를 크게 바꾸는 축을 골라 **선택지와 함께** 한 화면에 낸다. 각 선택지에는
"이걸 고르면 무엇이 달라지는지"를 적어라. 고르는 사람이 결과를 예상할 수 있어야 한다.
거의 모든 브리프에서 다음이 결과를 가장 크게 바꾼다. **위 넷은 거의 항상 묻는다.**
브리프·기존 코드·브랜드 자산에서 답을 확인할 수 없고 결과를 크게 바꿀 때만 아래 항목을 묻는다. 이미 알려진 사실을 다시 묻지 않는다.
| 축 | 무엇이 달라지는가 | 안 물으면 |
|---|---|---|
| **업종·성격** | 정보 구조 전체. 같은 "꽃집"도 구독형과 하이엔드 스튜디오는 다른 사이트다 | 카테고리 평균이 나온다 |
| **목표 행동** | CTA 의 수와 위치, 어떤 섹션이 필요하고 어떤 게 군더더기인지 | CTA 가 넷이 되고 페이지가 무너진다 |
| **톤** | 2단계 프리셋과 감수할 리스크가 여기서 결정된다 | 내 기본 미학이 나온다 |
| **고유명사·실제 값** | 이름·지역·가격·연락처 | 지어내면 하드 게이트 11 |
| **고유명사·실제 값** | 이름·지역·가격·연락처 | 지어내면 규범·기능 하드 게이트 실패 |
| **기존 브랜드 색·로고** | 있으면 3단계 팔레트가 **거기서 시작한다** | 있는 자산을 무시하고 새로 만든다 |
| **좁은 화면의 내비** | 항목 5개 이상이면 구조가 달라진다 | 넓은 화면 메뉴를 그대로 접어 두 줄이 된다 |
| **좁은 화면의 내비** | 라벨 길이·번역·확대·폭·우선순위·깊이가 구조를 바꾼다 | 넓은 화면 메뉴를 그대로 접어 두 줄이 된다 |
### 색은 반드시 확인한다 — 없다는 답도 답이다
### 색은 이미 알려진 자산부터 확인한다
**"브랜드 색이 있나요"** 는 결과를 되돌릴 수 없게 바꾸는 질문이다.
이미 로고·간판·패키지에 색이 있는데 사이트만 다른 색이면, 잘 만들어도 **틀린 것**이다.
로고·간판·패키지·기존 토큰에 브랜드 색이 있으면 그것부터 확인한다. 없는지 또는 피할 색이 필요한지 이미 브리프·자산에서 알 수 있으면 재질문하지 않는다. 정보가 없고 색 선택이 브랜드 정합성을 크게 바꾸는 경우에만 한 번에 묻는다.
- **있다** → 3단계에서 그 색이 강조색이고, 레퍼런스는 그 색과 어울리는 곳으로 고른다
- **없다·상관없다** → 레퍼런스 실측에서 뽑는다. 이때도 **어떤 색을 피해야 하는지**는 물어라
(경쟁사가 쓰는 색, 대표가 싫어하는 색은 사용자만 안다)
- **기존 색이 있다** → 역할·대비·면적을 검토해 토큰에 반영한다. 반드시 강조색일 필요는 없다.
- **없다·상관없다** → 레퍼런스와 과업에서 색 역할을 정한다. 피할 색이 결과를 좌우하고 확인할 근거가 없을 때만 묻는다.
> 실측 사례: 꽃집 작업에서 색을 묻지 않고 레퍼런스 세 곳의 배경 평균(`#F8F6F0`)으로 정했다.
> 결과는 좋았지만 **운이 좋았던 것**이다. 브랜드 색이 있었다면 그걸 무시한 작업이 된다.
### 좁은 화면의 내비를 미리 정한다
내비 항목 수는 0단계에서 이미 정해진다(= 섹션 수). **구현 단계에서 발견하면 늦다.**
내비 구조는 항목 수만으로 정하지 않는다. 실제 라벨과 번역 길이, 글꼴 확대, 최소 화면 폭, 가장 중요한 이동 경로, 메뉴 깊이, 키보드 포커스 순서를 함께 본다. 한 줄·가로 스크롤·하단 바·여는 메뉴 중에서 그 조건에서 과업을 가장 덜 방해하는 방식을 고르고, 실제 좁은 화면과 확대 상태에서 검사한다.
| 항목 수 | 좁은 화면 처리 | 비용 |
|---|---|---|
| 2~4개 | 그대로 한 줄. 자간을 줄이고 `justify-content: space-between` | 0 |
| 5~6개 | 가로 스크롤 줄 또는 하단 고정 바 | 낮음 |
| 7개 이상 | **여는 메뉴**(햄버거) | `<details>` 로 JS 0바이트 |
**햄버거를 기본값으로 삼지 마라.** 항목이 넷인데 햄버거를 쓰면 한 번의 탭을 공짜로 뺏는 것이다.
반대로 여섯 개를 한 줄에 욱여넣으면 접힌다 — 실측에서 링크가 두 줄(top 46/77)에 걸렸다.
여는 메뉴가 필요하다고 판단되면 `references/layout.md` 의 **모바일 내비** 절을 본다.
여는 메뉴가 필요하다고 판단되면 `references/layout.md` 의 **모바일 내비** 절을 본다. 항목이 적어도 긴 번역·깊은 계층이면 여는 메뉴가 맞을 수 있고, 항목이 많아도 우선순위가 뚜렷하면 일부를 분리할 수 있다.
넷을 `multiSelect` 로 낼지 단일 선택으로 낼지 구분해라 — **목표 행동은 대개 복수**고,
톤과 성격은 하나여야 한다. 둘 다 고르면 방향이 서지 않는다.
톤과 성격에는 주된 방향을 두되, 브리프가 요구하면 서로 보완하는 속성을 함께 쓸 수 있다. 어떤 속성이 위계를 이끄는지 기록한다.
**물어도 되는 것과 물으면 안 되는 것**
@ -192,15 +195,17 @@ description: "웹 디자인 전 과정을 끌고 가는 파이프라인 스킬.
이 단계가 designpaca의 심장이다. 머릿속 기본값이 아니라 **실제로 존재하는 사이트**에서 시작한다. 연장·국소 경로는 0단계에서 이미 이 단계를 건너뛰기로 정했고, 그 근거는 `design.md` 다.
레퍼런스 **3개**를 서로 **다른 층위**에서 고른다:
전체 경로에서는 `references/design-foundations.md`를 읽어 이론을 브리프의 사용 과업·제약으로 번역하고, `references/evidence-ledger.md`에서 출처 ID와 기록 형식을 확인한다. 조사 결과는 패키지 문서에 쓰지 말고 프로젝트의 `design.md` 또는 프로젝트 evidence 기록에 남긴다.
레퍼런스 슬롯은 구조·톤·디테일을 **분리해 검토하는 장치**다. 새 화면에는 보통 아래 세 슬롯을 채우되, 세 개를 찾았다는 사실이 좋은 합성이나 결과를 보증하지 않는다. 브리프에 맞지 않는 슬롯은 비워 둔 이유와 대체 근거를 남긴다:
| 슬롯 | 무엇 | 규칙 |
|---|---|---|
| **R1 — 구조** | 같은 업종/목적의 사이트 | 정보 구조와 흐름을 가져온다 |
| **R2 — 톤** | **반드시 다른 업종** | 분위기·재질·타이포 감각을 가져온다 |
| **R2 — 톤** | 다른 업종 또는 매체를 우선 검토 | 분위기·재질·타이포 감각을 가져온다 |
| **R3 — 디테일** | 어디서든 | 하나의 구체적 기법(모션·타입·인터랙션) |
R1과 R2를 같은 업종에서 고르면 결과는 그 업종의 평균이 된다. SaaS 구조 + SaaS 톤 = 또 하나의 SaaS 슬롭이다.
R1과 R2의 업종이 같아도 평균을 무비판적으로 복제하지 말고, 가져올 원칙·적용 조건·실제 렌더 검증을 분리해 기록한다.
**목표 시장이 다른 레퍼런스는 자동으로 중립이 되지 않는다.** 단위·용어·문의 방식·사진 조도·신뢰 표지는 문화권의 일부다. 시장 적합성이 중요한 브리프는 R1을 같은 목표 시장에서 우선 찾고, R2는 **같은 문화권의 다른 업종**을 우선한다. 다른 문화권의 R3는 구현 기법 하나만 가져온다. 상세 계약은 `reference-method.md`와, 가상·AI·규제 쇼케이스면 `trustworthy-showcases.md`를 따른다.
@ -209,23 +214,24 @@ R1과 R2를 같은 업종에서 고르면 결과는 그 업종의 평균이 된
**갤러리 목록 페이지가 아니라 원본 사이트를 열어라.** 이미지를 볼 수 없어도 구조는 읽을 수 있다.
**403·404·타임아웃을 두 번 만나면 텍스트 페처를 버리고 브라우저를 띄워라.**
좋은 레퍼런스일수록 봇을 막는다. 대체 갤러리를 찾아 헤매는 것은 시간 낭비이고,
찾아낸 대체 소스는 애초에 보려던 것보다 나쁘다. `designpaca tools` 로 설치된
**403·404·타임아웃을 두 번 만나면 텍스트 페처만 반복하지 말고 브라우저 또는 다른 정본 출처를 검토한다.**
대체 출처는 처음 후보보다 자동으로 열등하지 않다. 출처가 제공하는 관찰 범위·신뢰성·브리프 적합성을 비교해 고른다. `designpaca tools` 로 설치된
headed 브라우저가 있으면 스크린샷과 실측값을 바로 받는다 — 방법은 `galleries.md` §5.
> 통과 조건: R1/R2/R3 각각의 URL과, 그것에서 **무엇을 가져올지** 한 줄씩. 형식은 `reference-method.md` 참조.
> 통과 조건: 사용한 슬롯마다 URL·출처 ID·원칙/조건·관찰·결정·검증 계획이 있다. 형식은 `reference-method.md`와 `evidence-ledger.md`를 따른다.
---
### 2단계 — 방향 결정
레퍼런스 3개를 하나의 방향으로 합성한다. 베끼지 않는다. **축을 정하고 그 축 위에서 결정한다.**
사용한 레퍼런스 슬롯을 하나의 방향으로 번역한다. 베끼지 않는다. **축을 정하고 그 축 위에서 결정한다.**
이 단계에서 `references/style-playbook.md`를 읽어 선택한 스타일의 표현 수단·주의점·적용 조건을 검토한다. 이론이나 스타일 이름은 결론이 아니므로, `design.md` 또는 프로젝트 evidence 기록에 출처 ID → 원칙/조건 → 관찰 → 결정 → 검증 계획을 남긴다.
정해야 할 것:
- **한 문장 컨셉** — 이 사이트가 주는 인상을 한 문장으로. ("고급 잡지의 여백", "계기판처럼 정확한", "밤의 스튜디오")
- **미학 프리셋** — 아래에서 고르거나, 브리프가 요구하면 새로 정의한다
- **감수할 리스크 하나** — 정당화할 수 있는 과감한 선택 하나. 없으면 그 디자인은 안전하고 잊힌다
- **표현 리스크** — 과감한 선택을 쓴다면 왜 이 과업에 맞는지와 실제 렌더 검증 방법. 리스크가 없다는 결정도 허용하되 이유를 기록한다
- **표면 다이얼** — 랜딩·관리자·가맹처럼 과업이 다른 화면이 함께 있으면 각 표면의 표현성·정보 밀도·모션·증거 노출을 0~3으로 따로 적는다. 공통 토큰은 공유하되 과업 밀도까지 같게 만들지 않는다
| 프리셋 | 한 줄 | 언제 |
@ -238,7 +244,7 @@ headed 브라우저가 있으면 스크린샷과 실측값을 바로 받는다
고른 프리셋 파일 **하나만** 읽어라. 다섯 다 읽지 마라. 고르는 기준은 `references/presets/README.md`.
> 통과 조건: 한 문장 컨셉 + 프리셋 + 리스크 하나가 적혔다.
> 통과 조건: 한 문장 컨셉 + 프리셋 + 표현 리스크의 채택/비채택 근거가 적혔다.
> **연장 경로는 이 단계를 돌지 않는다.** `design.md` 의 컨셉·프리셋·리스크를 그대로 이어받는다. 바꾸고 싶으면 전체 경로로 올린다.
---
@ -253,7 +259,7 @@ headed 브라우저가 있으면 스크린샷과 실측값을 바로 받는다
**성능 예산도 여기서 정한다.** 나중에 정하면 이미 늦는다. 전체 표는 `references/tokens.md` §5 하나뿐이다 — 다른 문서에 예산 표를 만들지 마라.
기본선: 히어로까지 JS **150KB(gzip)** / 첫 인터랙션 **3초** / 애니메이션은 `transform`·`opacity` 만.
기본선: 히어로까지 JS **150KB(gzip)** / 첫 인터랙션 **3초**. `transform`·`opacity`는 저비용 기본 선택이다. 다른 속성 또는 스크롤 기반 로직은 자동 금지가 아니라, 측정한 비용·입력 간섭·감소 모션 경로·프로젝트 예산을 기록하고 실제 기기/브라우저에서 검증한다.
브리프가 데모·포트폴리오라면 예산을 올려도 된다. **올린다는 사실과 이유를 명시해라.**
> 통과 조건: 토큰이 실제 값으로 적혔고, 성능 예산이 숫자로 정해졌다.
@ -266,9 +272,9 @@ headed 브라우저가 있으면 스크린샷과 실측값을 바로 받는다
**4-0. 이미지 조달** → `references/images.md`
자리를 만들기 전에 **무엇을 실을지** 정한다. 나중에 채우면 비율이 안 맞아 레이아웃을 다시 짠다.
사용자가 준 사진이 있으면 무조건 그것이고, 없으면 **codex 가 설치돼 있는지 확인해 생성**한다.
생성했으면 `design.md` 에 생성물이라고 적는다 — 나중에 실제 사진으로 바꿀 사람이 알아야 한다.
히어로·증거 컷·모바일 히어로를 각각 슬롯으로 적은 **이미지 바이블**을 먼저 만든다. 데스크톱 크롭에서 핵심 피사체가 폴드 아래로 밀리면 모바일은 별도 원본을 만든다.
사용자가 준 사진·기존 브랜드 자산·직접 제작·라이선스 가능한 자료·생성물 중에서, 이미지 슬롯의 역할과 품질·권리·진실성에 맞는 것을 고른다. 자산이 없다는 사실만으로 자동 생성하지 않는다.
생성물을 쓰면 `design.md` 에 생성물과 용도를 적는다 — 나중에 실제 사진으로 바꿀 사람이 알아야 한다.
히어로·증거 컷·모바일 히어로를 각각 슬롯으로 적은 **이미지 바이블**을 먼저 만든다. 슬롯별 실제 크롭에서 핵심 피사체와 맥락이 보존되는지 확인하고, 같은 원본의 초점 조정으로 해결되지 않을 때만 별도 자산을 고른다.
싣기 전에 반드시 줄인다(실측: 10.1MB → 603KB).
**4-1. 레이아웃과 타이포그래피** → `references/layout.md` (+ 폰트 적용은 `references/typography.md` §5, §6)
@ -285,7 +291,7 @@ headed 브라우저가 있으면 스크린샷과 실측값을 바로 받는다
**4-5. 서류로서의 완성** — 프리셋이 서류·원장·콘솔 계열(장부, 시간표, 관리 화면)일 때만 도는 단계다.
그 앱이 종이에서 하던 일을 화면이 이어받아야 프로덕션이다. 셋을 검토한다:
- **인쇄** — `@media print`. 앱 크롬(내비·툴바·버튼)은 물러나고 활성 화면이 서류가 된다. 모달이 열려 있으면 그 서류만(`body:has(dialog[open])` 로 앱을 숨긴다). 상태색은 `print-color-adjust: exact` 로 보존. 인쇄 버튼을 크롬에 더했다면 **그 줄의 최소폭 합**을 가장 좁은 폭에서 다시 잰다(하드 게이트 5)
- **인쇄** — `@media print`. 앱 크롬(내비·툴바·버튼)은 물러나고 활성 화면이 서류가 된다. 모달이 열려 있으면 그 서류만(`body:has(dialog[open])` 로 앱을 숨긴다). 상태색은 `print-color-adjust: exact` 로 보존. 인쇄 버튼을 크롬에 더했다면 프로젝트의 최소 지원 폭에서 실제 조작 가능성을 다시 잰다
- **키보드 단축키** — 콘솔의 손가락 문법. 숫자키 뷰 전환, `/` 검색 포커스. `kbd` 물리 키 칩으로 안내하고, 입력 요소에 포커스가 있을 때는 무력화한다
- **본연의 도메인 동작** — 학적부는 기록, 시간표는 격자, 주문은 원장. 그 도메인이 종이에서 하던 핵심 동작 하나가 빠져 있으면 그게 곧 '데모 티'다
@ -306,54 +312,52 @@ HTML-in-Canvas(`drawElementImage`)는 **폴백을 완성한 뒤에만** 얹는
→ `references/preflight.md` (전체 체크리스트)
→ `references/antipatterns.md` (슬롭 지문 목록 + grep 검출 + 자가 채점표)
특히 다음 다섯은 기계적으로 검사할 수 있다. **반드시 돌려라**:
다음 항목을 변경 영향과 프로젝트 검증 계약에 맞춰 확인한다. 자동 검사와 렌더 평가는 구분한다:
1. **슬롭 지문 grep** — 보라 CTA(`#6366f1`·`#8b5cf6` 계열), 전체 대문자 헤드라인, 번호 매긴 1·2·3 단계. 실측 검출률 상위 항목이다
1. **스타일 후보 grep** — 보라 CTA, 전체 대문자 헤드라인, 번호 매긴 단계처럼 반복되는 기본값을 찾아 근거 없이 남아 있는지 검토한다. 검출 자체는 실패가 아니며 `antipatterns.md`의 맥락 질문과 실제 렌더로 판정한다
2. **카피 경쟁사 치환 테스트** — 제품명을 경쟁사 이름으로 바꿔도 문장이 성립하면, 그 카피는 아무것도 말하지 않았다
3. **이펙트 전부 끄기** — CSS 필터·WebGL·애니메이션을 끈 상태에서 페이지가 여전히 읽히는가
4. **게이트 실행(폐쇄 루프)** — `tools/design-gate.mjs --init` 으로 설정을 만들고 돌린다. 오버플로·h1·수축·대비·SEO/meta·죽은 선택자·토큰 위생·스케일×폭까지 전 뷰 × 전 폭. L1(단위)·L5(탐색)·L6(시나리오 E2E)는 프로젝트 `tools/` 에 보강해 `npm run verify` 체인으로. **게이트 통과 없이 완료 선언 금지.**
4. **게이트 실행(폐쇄 루프)** — `tools/design-gate.mjs --init` 으로 설정을 만들고, 변경 영향과 프로젝트 계약에 맞춘 뷰·폭·상태에서 실행한다. 오버플로·제목 존재/가시성·수축·대비·SEO/meta·죽은 선택자·토큰 위생·스케일×폭을 해당 범위에서 검사한다. 명시한 양의 `h1MaxLines` 계약이 있을 때만 줄 수를 검사한다. L1(단위)·L5(탐색)·L6(시나리오 E2E)는 프로젝트에 필요한 경우 `tools/` 에 보강해 `npm run verify` 체인으로. 자동 검사 결과와 실제 렌더 평가를 함께 기록한다.
5. **상태 완결성 스윕** — 입력·파괴·열림이 있는 화면은 실제로 조작해 본다: 수치 입력에 범위 밖 값을 넣고, 파괴적 행동을 되돌려보고, 열린 메뉴를 Esc 로 닫는다. 기준은 `references/preflight.md` §4-1. 통과 못 하면 4단계로
6. **상태별 시각 E2E** — URL별 390·1440 기본 화면과 결과·오류·모달 같은 핵심 상태를 캡처한다. 하니스는 개수·규격·0바이트·중복 해시를 단언하고, 캡처를 실제로 열어 목표 시장 적합성·시선 위계·이미지 크롭·반복을 눈으로 판정한다. 구조 PASS를 시각 PASS로 바꾸어 말하지 않는다
> 게이트가 잡은 실제 사고 목록과 7계층 설계 근거는 `references/audit-gate.md` 다. 자기 검사 스크립트를 매번 새로 쓰지 마라 — 이 게이트를 심고 확장해라.
#### 하드 게이트 — 전부 "아니오"여야 한다
#### 규범·기능 하드 게이트 — 전부 충족해야 한다
취향이 아니라 **버그**다. 여기엔 오버라이드가 없다. 체크박스가 아니라 질문이니 실제로 검사해라.
아래는 취향이 아니라 적용 WCAG·플랫폼 접근성·사용자에게 약속한 기능의 실패다. 프로젝트 계약과 스타일 판단은 다음 절에서 별도 검토한다.
1. 토큰 밖에 인라인 hex/rgb/oklch 색상값이나 인라인 `font-family` 가 있는가?
2. 상태색(성공·경고·오류)을 제외하고, 한 페이지에서 강조색이 2개 이상인가?
3. `border-radius` 값이 토큰 밖에 있거나, 서로 다른 값이 3종 이상인가?
4. 페이지 중간에 테마(라이트/다크)가 뒤집히는데, 그 반전이 3단계 토큰에 규칙으로 정의돼 있지 않은가?
(리듬으로 의도한 반전은 통과다. 정의 없이 섹션마다 다른 것이 실패다)
5. 320~1920px 사이 어느 폭에서든 가로 스크롤이 생기는가?
6. 버튼 라벨·내비 링크가 2줄로 접히는 폭이 있는가?
7. 버튼 텍스트와 배경의 대비가 4.5:1 미만인가?
8. `transform`/`opacity` 외의 속성을 애니메이션하는가?
9. 풀하이트 섹션에 `100vh` 를 썼는가? (모바일 주소창 때문에 `100dvh` 여야 한다)
10. `window.addEventListener('scroll')` 을 썼는가? (`IntersectionObserver` 또는 scroll-driven animation 을 써라)
11. **사용자가 주지 않은 수치**(지표·통계·후기·고객 수)가 페이지에 **그럴듯한 값으로** 들어가 있는가?
1. 키보드만으로 모든 인터랙티브 요소에 도달하고, 보이는 포커스와 논리적 순서를 유지하는가?
2. 텍스트·UI·상태가 적용 WCAG 대비 기준을 충족하고, 의미 있는 이미지·폼·제목 구조·언어가 접근 가능한가?
3. 감소 모션 환경에서 움직임 민감도를 낮추면서 콘텐츠·상태·조작을 보존하는가?
4. 지원 범위의 뷰포트·글자 확대·입력 방식에서 의도하지 않은 가로 스크롤, 겹침, 조작 불능이 없는가?
5. 입력·파괴·열림 상태가 실제로 검증되고, 오류·되돌림·Esc·포커스 복귀가 필요한 곳에서 작동하는가?
6. 애니메이션 또는 스크롤 로직이 입력을 방해하지 않고, 정한 성능 예산과 지원 범위를 실제로 검증했는가?
7. **사용자가 주지 않은 수치**(지표·통계·후기·고객 수)가 페이지에 **그럴듯한 값으로** 들어가 있지 않은가?
걸렸으면 셋 중 하나다: (a) `{{SETUP_TIME}}` 같은 **명시적 placeholder** 로 바꾸고 6단계 `design.md` 미확정 목록에 올린다, (b) 사용자에게 실제 값을 묻고 멈춘다, (c) 그 섹션 자체를 다른 구조로 바꾼다.
**placeholder 는 통과다.** 숫자 모양의 구멍은 정직하고, 지어낸 숫자는 슬롭이다.
12. `<div>` 로 만든 가짜 스크린샷·가짜 브라우저바·가짜 폰 프레임이 있는가?
13. **토큰 체계·프리셋을 바꿨는데 브랜드 표면(theme-color 메타·파비콘·og:image)이 옛 값을 그대로 쓰고 있는가?** 실측 사고: 프리셋 전환 11버전 뒤에 종이 시대 파비콘·theme-color 가 살아 있었다. 토큰 마이그레이션은 이 셋을 갱신하기 전까지 끝난 게 아니다. `theme-color` 는 실제 배경색과 같아야 한다(게이트가 비교한다)
14. **HTML·JS 에서 사라진 컴포넌트의 CSS 규칙·JS 함수가 남아 있는가?** 제거는 HTML·CSS·JS 삼위일체다. 게이트의 죽은 선택자 검사가 클래스 사용률을 대조한다
15. **현재 로케일 기본값·명시 브리프와 무관한 제3언어 또는 문화권 클리셰를 에이전트가 새로 넣었는가?** 화면·메타·ARIA·예제 데이터·생성 이미지와 세로쓰기·인장·서예성 장식까지 실제 렌더에서 확인한다. 한국어 로케일에서는 한자·중국어·일본어의 신규 사용이 이에 해당한다. 영어와 사용자가 제공한 원문은 예외다.
8. 실제 증거로 오인될 수 있는 가짜 스크린샷·브라우저바·폰 프레임을 라벨 없이 만들지 않았는가?
#### 카운트 규칙
#### 프로젝트 계약 — `design.md`와 구현에서 대조한다
세어봐라. 넘으면 고친다.
- 토큰·테마 반전·브랜드 표면(theme-color·파비콘·og:image)·남은 코드가 프로젝트 결정을 반영하는가?
- 로케일·문화권·실제 수치·진실 라벨이 브리프와 제공 자산의 범위를 지키는가?
- 지원 viewport·성능 예산·상태별 시각 E2E는 프로젝트가 정한 매트릭스와 도구 한계 안에서 보고됐는가? 자동 도구가 측정하지 못한 항목은 통과로 추정하지 말고 수동 평가로 남긴다.
#### 스타일 휴리스틱 — 계수는 시작점이지 판정이 아니다
세어보고, 반복이 정보·브랜드·사용성에 맞는지 실제 렌더에서 평가한다. 아래 수치를 넘겼다는 사실만으로 고치거나 통과시키지 않는다.
- 작은 대문자 라벨(eyebrow) 개수 ≤ `ceil(섹션수 / 3)`
- 같은 이미지+텍스트 스플릿 레이아웃 연속 ≤ 2회
- 마퀴 ≤ 1개
- 마퀴: 반복·자동재생·정지 수단·감소 모션 경로를 검토한다
- 섹션이 6개 이상이면 서로 다른 레이아웃 패밀리가 최소 3개
- 히어로: 헤드라인 ≤ 2줄, 서브텍스트 ≤ 20단어, 텍스트 요소 ≤ 4개
- 히어로: 제목 줄 수·서브텍스트 길이·텍스트 밀도가 목표 행동과 읽기 폭에 맞는지 확인한다. 줄 수를 맞추려고 글자 크기를 줄이지 않는다
**국소 경로는 카운트를 페이지 전체로 다시 세지 않는다.** 내가 손댄 부분이 기존 카운트를 넘기게 만드는지만 본다.
**하드 게이트 15개는 경로와 무관하게 전부 돈다.**
**규범·기능 하드 게이트는 경로와 무관하게 전부 돈다.**
> 통과 조건: 하드 게이트 15개 전부 "아니오", 카운트 규칙 통과, `preflight.md` 체크리스트 통과. 실패 항목이 있으면 4단계로 돌아간다.
> 통과 조건: 규범·기능 하드 게이트 충족, 프로젝트 계약 대조 완료. 기능·접근성 자동 검사와 시각 평가(위계·브랜드·타입·구도·이미지·리듬)를 별도 보고하고, 스타일 휴리스틱의 보류·채택·수정 근거를 남긴다. 실패 항목이 있으면 4단계로 돌아간다.
---
@ -363,8 +367,9 @@ HTML-in-Canvas(`drawElementImage`)는 **폴백을 완성한 뒤에만** 얹는
```markdown
# design.md
브리프 3줄 / 레퍼런스 R1·R2·R3 URL / 한 문장 컨셉 / 프리셋 / 감수한 리스크 하나
브리프 3줄 / 레퍼런스 슬롯과 출처 ID / 한 문장 컨셉 / 프리셋 / 표현 리스크의 채택·비채택 근거
토큰 전체(타입·색·간격·모션) / 성능 예산과 실측치 / 채택한 이펙트와 그 폴백
출처 ID → 원칙·조건 → 관찰 → 결정 → 검증 결과를 프로젝트 evidence 기록에 연결 / 기능·접근성 자동 검사와 시각 평가의 별도 결과
의도적으로 하지 않은 것과 그 이유
```
@ -382,6 +387,9 @@ HTML-in-Canvas(`drawElementImage`)는 **폴백을 완성한 뒤에만** 얹는
|---|---|
| `references/galleries.md` | 1단계 — 어느 갤러리를 볼지 정할 때 / **1′ — 국소 브리프 표** |
| `references/reference-method.md` | 1단계 — 레퍼런스를 뜯어볼 때 |
| `references/design-foundations.md` | **1·2단계 — 이론을 프로젝트 조건으로 번역할 때** |
| `references/style-playbook.md` | **2·4단계 — 선택한 스타일의 표현 수단과 금기를 검토할 때** |
| `references/evidence-ledger.md` | **0·1·2·5·6단계 — 출처와 관찰·결정·검증을 추적할 때** |
| `references/presets/README.md` | 2단계 — 미학 방향을 고를 때 (고른 프리셋 하나만 추가로 읽는다) |
| `references/tokens.md` | 3단계 — 토큰을 정할 때 |
| `references/typography.md` | 3단계 — 폰트를 고를 때 / 4-1 — 적용할 때 |
@ -396,7 +404,7 @@ HTML-in-Canvas(`drawElementImage`)는 **폴백을 완성한 뒤에만** 얹는
| `references/preflight.md` | 5단계 — 감사 |
| `references/antipatterns.md` | 3·5단계 — 한글 조판 / 슬롭 검출 |
| `references/audit-gate.md` | **5단계 — 게이트(폐쇄 루프) 설계·설치·하니스 규칙** |
| `tools/design-gate.mjs` | **5단계 — 범용 게이트 실행기(SEO/meta·대비·수축·페이지별 h1 최대 줄 계약·시각·WebKit 옵션)** |
| `tools/design-gate.mjs` | **5단계 — 범용 게이트 실행기(SEO/meta·대비·수축·명시한 양의 h1 줄 수 계약·시각·WebKit 옵션)** |
## 작업 중 지켜야 할 것

View file

@ -1,6 +1,6 @@
{
"name": "@designpaca/skill",
"version": "0.10.0",
"version": "0.11.0",
"private": true,
"description": "designpaca 스킬 원본 — SKILL.md 와 참조 문서",
"scripts": {

View file

@ -1,47 +1,43 @@
# antipatterns.md — 금지 목록
# antipatterns.md — 점검 후보
AI가 만든 티는 **못 만들어서** 나는 게 아니라 **결정을 안 해서** 난다.
보라 그라디언트, Inter, 3열 아이콘 카드, `rounded-2xl shadow-lg`, "Get Started"는 전부 **미결정의 기본값**이다.
AI가 만든 티는 **못 만들어서** 나는 게 아니라 결정을 설명·검증하지 않을 때 난다.
보라 그라디언트, Inter, 3열 아이콘 카드, `rounded-2xl shadow-lg`, "Get Started"는 반복될 수 있는 출발점이다. 존재만으로 미결정이라고 단정하지 않고, 브리프·프로젝트 계약·실제 렌더로 판단한다.
프리플라이트에서 이 문서를 훑고 걸린 항목을 전부 해소한다. **4개 이상 걸리면 폐기하고 다시 시작한다.**
프리플라이트에서 이 문서를 훑어 후보를 찾는다. 검출은 실패가 아니다. 브리프·프로젝트 계약·실제 렌더를 대조해 유지·수정·제거를 결정하고 그 근거를 남긴다.
---
## 0. 최우선 — 측정된 상위 지문
## 0. 먼저 보는 반복 후보
Show HN 랜딩 1,590개 실측에서 검출률이 가장 높았던 셋이다. **이 셋부터 확인하라.**
원자료와 재현 가능한 표본이 없는 빈도 수치는 규칙의 근거가 될 수 없다. 아래 항목은 흔한 기본값을 찾는 검색 후보이며, 색·대문자·번호가 존재한다는 사실만으로 실패가 아니다.
| 순위 | 지문 | 검출률 | 왜 문제 | 대신 |
|---|---|---|---|---|
| 1 | **CTA 버튼이 보라·인디고 계열** (`bg-indigo-600`, `#6366f1`, `#8b5cf6`) | **10.7%** | Tailwind 기본 팔레트 = "색을 안 골랐다"는 신호 | 브랜드 색. 없으면 중립 대비가 가장 강한 색(잉크 위 화이트 반전) |
| 2 | **헤드라인·섹션 라벨이 전체 대문자** (`FEATURES`, `HOW IT WORKS`) | **10.5%** | 얻지 않은 권위를 빌리는 장치. 가독성도 낮음 | 라벨을 없애고 헤드라인만으로 섹션을 구분 |
| 3 | **번호 매긴 1·2·3 단계 섹션** | **9.4%** | 실제 순서가 아닌데 순서인 척 | 단계가 진짜 순서일 때만. 그 경우에도 번호보다 화면 캡처 3장이 낫다 |
전체 표본의 22%가 4개 이상 지문을 가진 "heavy slop"이었다.
| 후보 | 확인할 질문 |
|---|---|
| 보라·인디고 CTA | 브랜드 팔레트·상태 구분·대비·목표 행동을 설명하는가? |
| 전체 대문자 라벨 | 언어·가독성·정보 위계에 맞는가? |
| 번호 매긴 단계 | 사용자가 실제 순서로 수행하는 과업인가? |
---
## 1. 컬러
> **다크가 슬롭이 아니라 기본값이 슬롭이다.**
> bun.sh는 다크 배경인데도 이 절의 항목을 거의 전부 회피한다 — 순흑이 아닌 `#0D0A0C`, 텍스트 3단계, 보라 대신 마젠타 `#FF2E97`, radius 어휘 3종.
> A-01은 "다크를 쓰지 마라"가 아니라 **"고르지 않은 채로 다크에 떨어지지 마라"**는 뜻이다. 아래 전부 동일하다.
다크·그라디언트·강한 색은 그 자체로 실패가 아니다. 화면 역할, 브랜드, 대비, 실제 기기 렌더에서 이유를 설명할 수 있는지 점검한다.
| 지문 | 왜 문제 | 대신 |
|---|---|---|
| **다크 모드를 브리프 근거 없이 기본값으로** (`bg-slate-900`, `#0B0B0F`) | 생성 도구의 반사 반응. 단일 시그니처 검출 빈도 1위 | 라이트로 설계하고 다크는 토큰으로 파생. 사용자 82%가 다크를 쓴다는 데이터는 "지원하라"이지 "기본값으로 하라"가 아니다 |
| **보라→파랑 그라디언트 히어로** (`from-purple-600 to-blue-500`, `#6366f1→#a855f7`) | 수십만 튜토리얼의 기본값 | 브랜드 색 1개 + 중립. 그라디언트를 쓸 거면 같은 hue 내 명도 변화 (`#1B4332→#2D6A4F`) |
| **라벤더 퍼플이 어디에나** (`#a78bfa`, `#c4b5fd`) | 텍스트→랜딩 생성기의 출력 지문 | 보라를 쓰지 마라. 꼭 필요하면 채도를 낮추고 온도를 틀어라 (`#6B5B95`) |
| **네온 온 다크** — 시안(`#22d3ee`)·바이올렛이 검정 위에서 발광 | 게이밍·개발자 툴 브리프가 아닌데 나오면 즉시 티가 남 | 저채도 액센트 1개. 발광 대신 명도 차이로 위계 |
| **컬러 글로우** (`box-shadow: 0 0 80px rgba(139,92,246,.5)`) | 광원 논리 없이 깊이를 색으로 위조 | 중립 그림자 2단 (`0 1px 2px rgba(0,0,0,.06)`, `0 8px 24px rgba(0,0,0,.08)`) |
| **히어로 뒤 방사형 그라디언트 오브·헤일로** | 구성을 못 잡았을 때의 회피 수단 | 배경을 비우거나 실제 콘텐츠(제품 스크린샷, 타입)로 채워라 |
| **순백 `#ffffff` / 순흑 `#000000`** 을 배경·텍스트로 | 아무 결정도 하지 않았다는 뜻. 눈부심·번짐 유발 | 오프화이트 `#FAFAF7`, 잉크 `#111014`. 브랜드 hue를 2~4% 섞으면 더 좋다 |
| **Tailwind 기본 토큰 그대로** (`slate-900`, `gray-500`, `emerald-500`) | 누구나 알아본다 | `tailwind.config`의 색을 **교체**한다(확장 아님). `ink-900`, `ember-600` 같은 고유 이름 |
| **다크 모드를 브리프 근거 없이 기본값으로** (`bg-slate-900`, `#0B0B0F`) | 대상 환경·브랜드·콘텐츠에 맞는지 설명하지 못하면 선택이 기본값처럼 보일 수 있다 | 라이트·다크의 역할 토큰, 실제 기기 대비, 사용자 설정·테마 전환 필요성을 비교한다 |
| **보라→파랑 그라디언트 히어로** (`from-purple-600 to-blue-500`, `#6366f1→#a855f7`) | 제품·브랜드·정보 역할과 무관하면 배경 장식만 남을 수 있다 | 단색·동일 hue 변화·다중 hue를 같은 콘텐츠에서 비교하고, 텍스트 대비와 행동 위계를 확인한다 |
| **라벤더 퍼플이 어디에나** (`#a78bfa`, `#c4b5fd`) | 맥락 없는 반복은 기본값처럼 보일 수 있음 | 브랜드·접근성·표면 역할을 설명하고 실제 렌더에서 위계를 확인 |
| **네온 온 다크** — 시안(`#22d3ee`)·바이올렛이 검정 위에서 발광 | 색의 의미·대비·주변광에서의 읽기 비용을 확인해야 한다 | 상태·행동·콘텐츠 역할과 연결하고, 저채도/고채도·명도 대비를 실제 기기에서 비교한다 |
| **컬러 글로우** (`box-shadow: 0 0 80px rgba(139,92,246,.5)`) | 깊이·계층·상태를 글로우만으로 표현하면 의미가 모호해질 수 있다 | 그림자·경계·여백·글로우 후보 중 실제 계층과 성능을 가장 잘 드러내는 것을 선택한다 |
| **히어로 뒤 방사형 그라디언트 오브·헤일로** | 메시지·제품 증거·구도보다 장식이 먼저 읽히는지 점검한다 | 배경·제품 화면·타입 중심 구도를 같은 콘텐츠로 비교한다 |
| **순백 `#ffffff` / 순흑 `#000000`** 을 배경·텍스트로 | 극단값이 브랜드·매체·환경에 맞는지 점검한다 | 대상 기기·주변광·텍스트 대비와 실제 렌더의 눈부심을 확인한다. 색 자체를 실패로 취급하지 않는다. 출처: [WCAG 2.2 Contrast](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html)와 프로젝트 브리프 |
| **Tailwind 기본 토큰 그대로** (`slate-900`, `gray-500`, `emerald-500`) | 역할·브랜드·상태가 토큰 이름과 값에서 드러나는지 점검한다 | 기본 토큰을 유지·확장·교체할 수 있다. 의미 역할과 실제 렌더 근거를 기록한다 |
| **코드에서 색 이름을 직접 호출** (`purple-500`) | 색이 역할을 못 가짐 | 의미 토큰: `--color-action-primary`, `--color-surface-elevated`, `--color-text-secondary` |
| **그라디언트 텍스트** (`background-clip: text`) | 스캔성을 깎고 정보는 0. OG 이미지에서 깨짐 | 단색. 강조는 크기·굵기·여백으로 |
| **그라디언트 텍스트** (`background-clip: text`) | 작은 크기·이미지 배경·공유 이미지에서 읽기와 fallback을 점검한다 | 단색·그라디언트·다른 위계 수단을 비교하고, 실제 export와 대비를 확인한다 |
| **크림/베이지(`#FDF8F3`)를 "고급"의 기본값으로** | 보라를 대체한 신종 슬롭 | 크림을 쓸 거면 왜 크림인지 브리프와 연결하고 텍스트·액센트를 그에 맞춰 재설계 |
| **hue 4개 이상** | 결정 못 한 상태 | hue 3개 이하. 면적 60(지배)/30(중립)/10(강조) |
| **다크에서 본문 대비 미달** (`#0f172a` 위 `#94a3b8`) | 기능적 결함 | 본문 4.5:1 또는 APCA Lc ≥ 75. 다크의 본문은 순백 대신 `#E8E6E3` |
| **여러 hue가 경쟁함** | 행동·상태·정보의 우선순위가 색 때문에 흐려질 수 있다 | 색 역할과 화면 면적을 실제 상태에서 비교한다. hue 수·60/30/10은 출발 가설일 뿐 통과 기준이 아니다 |
| **다크에서 본문 대비 미달** | 적용 WCAG 성공 기준을 실제 배경 합성값으로 충족하는지 점검한다 | `#0f172a` 위 `#94a3b8`의 WCAG 대비는 약 **6.96:1**이므로 미달 사례가 아니다. WCAG 2.2 AA 본문 판정에는 contrast ratio를 사용하며 APCA는 이를 대체하지 않는다. 반투명·이미지 배경은 실제 렌더에서 계산한다. 출처: [WCAG 2.2 Contrast](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html) |
---
@ -49,18 +45,17 @@ Show HN 랜딩 1,590개 실측에서 검출률이 가장 높았던 셋이다. **
| 지문 | 왜 문제 | 대신 |
|---|---|---|
| **Inter를 이유 없이 기본값으로** (특히 중앙 정렬 히어로) | AI 인터페이스의 기본 서체 | Satoshi / Switzer / DM Sans / Work Sans. Inter는 초고밀도 다국어 UI 같은 명확한 이유가 있을 때만 |
| **Space Grotesk + Instrument Serif + Geist 조합** | 이 셋의 **반복 조합**이 생성 지문으로 특정됨 | 셋 중 **하나만** 쓰는 것은 허용. 둘 이상 함께 쓰면 실패. 파운드리를 바꾸면 더 낫다 (Fontshare, Velvetyne) |
| **헤드라인 한 단어만 세리프 이탤릭** (`Build <em>better</em> products`) | 가장 널리 퍼진 "성의 표시" 관용구. 이제는 성의가 아니라 지문 | 강조는 줄바꿈·크기·색·여백으로 |
| **페이지 전체에 폰트 패밀리 1개** | 위계를 크기로만 만들게 되어 밋밋 | 디스플레이 1 + 본문 1. 성격 차이를 크게 |
| **타입 스케일이 평평함** (16/18/20/24, 비율 1.15 미만) | 모든 게 똑같이 중요해 보임 | 비율 1.25 또는 1.333. **최대/본문 4배 이상** (17→68). 비율만으로 4배가 안 나오면(1.25 5단계 = 2.44배) **디스플레이 사이즈를 스케일 밖에 따로 정의한다**(`tokens.md` §1). **단계 수를 늘려서 맞추지 마라** — 그건 아래 행에 걸린다 |
| **타입 스케일 단계 8개 이상** | 통제 실패 | 3~5단계로 제한하고 각 단계에 역할 이름 부여 |
| **히어로 = pill 배지 + 거대 헤드라인** (`✨ Now in beta` + H1) | 가장 알아보기 쉬운 구조 지문 | 배지를 지워라. 정말 필요하면 문장 안이나 내비 옆으로 |
| **Inter 선택** (특히 중앙 정렬 히어로) | 서체가 언어·정보 밀도·브랜드 톤에 맞는지 점검한다 | 후보 서체와 숫자·한글·긴 제목을 같은 viewport에서 비교하고, 라이선스·fallback metric을 기록한다. Inter 자체를 실패로 취급하지 않는다. 출처: `references/typography.md`와 프로젝트 브리프 |
| **Space Grotesk + Instrument Serif + Geist 조합** | 조합이 실제 위계와 브랜드 톤을 만드는지 점검한다 | 서체 수가 아니라 역할·fallback·가독성을 실제 렌더에서 비교한다. 조합의 구성원 수만으로 실패시키지 않는다. 출처: `references/typography.md` |
| **헤드라인 한 단어만 세리프 이탤릭** (`Build <em>better</em> products`) | 강조가 브랜드 언어·문장 의미·읽기 순서를 실제로 돕는지 점검한다 | 이탤릭·줄바꿈·크기·색·여백을 같은 문구에서 비교하고 선택 이유를 기록한다 |
| **페이지 전체에 폰트 패밀리 1개** | 서체 수와 무관하게 제목·본문·데이터의 위계가 읽히는지 점검한다 | 한 패밀리 또는 여러 패밀리를 같은 콘텐츠·viewport·확대 상태에서 비교하고, 역할과 fallback을 기록한다. 출처: `references/typography.md` |
| **타입 스케일이 평평해 보임** | 최대/본문 배율이 아니라 실제 과업의 위계·읽기 폭·언어 적합성을 점검한다 | 제목·본문·보조 정보의 역할과 렌더된 우선순위를 비교한다. 배율·단계 수는 관찰값으로 기록하되 최대/본문 4배나 특정 비율을 보편 통과 조건으로 쓰지 않는다. 출처: `references/design-foundations.md`, `references/typography.md` |
| **타입 스케일 단계가 많음** | 같은 역할에 여러 크기가 섞이면 읽기 순서가 흐려질 수 있다 | 각 단계에 역할을 이름 붙이고, 대표 폭·확대 상태에서 실제 위계를 확인한다. 단계 수 자체로 실패시키지 않는다 |
| **히어로 = pill 배지 + 거대 헤드라인** (`✨ Now in beta` + H1) | 배지가 실제 상태·시간성·제약을 설명하는지 점검한다 | 배지·문장 안 상태·내비 상태·생략을 비교하고, 첫 행동에 필요한 경우만 남긴다 |
| **`_01_ _02_ _03_` 장식 번호 라벨** | 에디토리얼 흉내인데 구조는 없음 | 번호가 순서를 의미할 때만 |
| **본문 letter-spacing 0.05em 이상** | 단어 인식이 느려짐 | 본문 0. 소형 대문자 라벨에만 +0.08em |
| **대형 헤드라인 음수 자간 -0.06em 이상** | 글자가 서로 먹힘 | -0.02em ~ -0.04em |
| **본문 line-height 1.3 미만 / 본문 12px 이하** | 가독성·접근성 실패 | 라틴 본문 line-height 1.5–1.7, 크기 16–18px 권장. **예외**: 고밀도 라틴 UI(대시보드·개발자 도구)에서 13–14px는 의도된 선택일 수 있다 — bun.sh 본문 최다 크기가 13.5px다. 단 **한글에는 이 예외를 적용하지 마라.** 한글은 같은 px에서 라틴보다 작아 보여 16px가 하한이다 |
| **모노스페이스를 장식으로** (코드 아닌 라벨에 `font-mono`) | "테크 느낌"의 저비용 흉내 | 모노는 코드·데이터·식별자에만 |
| **본문/헤드라인 자간이 읽기를 방해함** | 글꼴·언어·크기·폭에 따라 글리프 충돌, 단어 인식, 줄바꿈이 달라진다 | 0, 양수, 음수 자간을 대표 문구·지원 viewport·200% 확대에서 비교한다. 단일 `em` 수치를 보편 기준으로 쓰지 않는다 |
| **본문 크기·행간이 과업에 맞지 않음** | 작은 글자나 촘촘한 행간은 읽기·확대·오류 복구 비용을 높일 수 있다 | 글꼴·언어·콘텐츠 밀도별 후보를 실제 기기·200% 확대·긴 문단에서 검증한다. 16px이나 고정 line-height를 보편 하한으로 선언하지 않는다 |
| **모노스페이스를 장식으로 사용** | 코드·데이터 외 용도에서도 의미·가독성·브랜드 톤을 돕는지 점검한다 | 대상 언어·크기·대비·긴 문자열을 실제 렌더에서 비교한다. 모노스페이스 자체나 용도만으로 실패시키지 않는다. 출처: `references/typography.md` |
---
@ -69,17 +64,14 @@ Show HN 랜딩 1,590개 실측에서 검출률이 가장 높았던 셋이다. **
| 지문 | 왜 문제 | 대신 |
|---|---|---|
| **표준 골격**: 히어로 → 3열 피처 → 로고월 → 요금제 → FAQ → 푸터 | shadcn/ui 예제·Tailwind UI·Vercel 템플릿의 순서 그대로 | 섹션 순서를 브리프의 설득 논리로 재배열. 예: 문제 제시 → 실제 사용 화면 → 반론 처리 → 가격 |
| **중앙 정렬 히어로** (텍스트 가운데 + CTA 2개 + 아래 스크린샷) | 가장 안전해서 가장 흔함 | 비대칭 그리드(5:7, 7:5), 좌측 정렬 대형 타입, 또는 텍스트를 화면 하단 1/3로 |
| **정확히 3열, 균등 폭 피처 카드** | 콘텐츠가 3개여서가 아니라 3이 예쁘게 떨어져서 3인 것 | 항목 수를 콘텐츠가 정하게. 폭도 중요도에 따라 다르게 |
| **아이콘이 카드 상단 중앙**에 있는 피처 카드 (아이콘 타일 + 제목 + 2문장) | 가장 확실한 단일 지문 | 아이콘을 없애고 숫자·스크린샷·실제 UI 조각으로. 쓸 거면 좌측 인라인 |
| **카드 좌측 3–4px 컬러 스트립** (`border-l-4 border-purple-500`) | em-dash에 맞먹는 신뢰도의 지문 | 삭제. 구분이 필요하면 배경 톤 차이나 여백 |
| **카드 안의 카드 안의 카드** | 시각적 소음. 깊이가 정보와 무관 | 중첩 최대 1단계. 안쪽은 구분선이나 여백으로 |
| **근거 없는 지표 배너** (`10,000+ users · 99.9% uptime · 4.9★`) | 검증 불가한 숫자 나열은 신뢰를 깎는다 | 숫자 1개만, 출처와 함께. 없으면 섹션 삭제 |
| **모든 여백이 같은 값** (전부 `p-6`, 전부 `gap-4`) | 리듬이 없어 강약이 사라짐 | 8pt 그리드 위 4~6종. 섹션 간 : 요소 간 ≈ 1:5 |
| **섹션마다 동일한 상하 패딩** (`py-20` 반복) | 어디가 중요한지 알 수 없음 | 중요한 섹션에 더 많은 여백. 여백이 강조다 |
| **벤토 그리드를 기본 선택지로** | 2026년 기준 표준이라 차별화가 아니다. "고민 안 했음"의 새 신호 | 타일 크기 차이가 정보 위계를 반영할 때만. 아니면 단순 리스트 |
| **콘텐츠가 뷰포트 가장자리에 붙음** | 모바일에서 특히 조악 | 모바일 16–24px, 데스크톱 24–80px 좌우 패딩 |
| **본문 컬럼이 화면 전체 폭** | 한 줄이 100자를 넘어 읽기가 무너짐 | 영문 `max-width: 65ch`. 국문은 25–40자 폭 |
| **중앙 정렬 히어로** (텍스트 가운데 + CTA 2개 + 아래 스크린샷) | 목표 행동·읽기 폭·브랜드의 구도를 돕는지 점검한다 | 중앙·비대칭·좌측 정렬 후보를 같은 콘텐츠와 viewport에서 비교하고, 선택 근거를 기록한다. 중앙 정렬 자체를 실패로 취급하지 않는다. 출처: `references/design-foundations.md`, 프로젝트 브리프 |
| **균등 카드 grid** | 항목 중요도·비교 과업·작은 폭 재배치가 균등 구조와 맞는지 점검한다 | 3열/비대칭/목록을 콘텐츠와 대표 과업에서 비교한다 |
| **아이콘 중심 피처 카드** | 아이콘이 실제 기능 이해를 돕는지, 이미지·수치·UI 증거가 더 나은지 점검한다 | 아이콘 위치·라벨·실제 제품 조각을 비교하고, 전체 카드 hitbox와 접근 가능한 이름을 확인한다 |
| **컬러 스트립·중첩 카드** | 경계와 깊이가 실제 정보 묶음을 나타내는지 점검한다 | 여백·선·표면·중첩을 비교한다. 중첩 횟수와 border 폭은 통과 기준이 아니다 |
| **근거 없는 지표 배너** (`10,000+ users · 99.9% uptime · 4.9★`) | 검증할 수 없는 수치는 사실처럼 제시하면 안 된다 | 출처·측정 조건·기간이 있는 수치만 쓰고, 수가 여러 개여도 목적에 맞는 묶음과 출처를 제시한다 |
| **여백 리듬이 정보 관계를 가림** | 같은/다른 간격이 관련성과 우선순위를 드러내지 못할 수 있다 | 토큰 수·비율을 고정하지 말고, 실제 콘텐츠와 회색조 렌더에서 그룹·섹션 관계를 확인한다 |
| **벤토 그리드를 기본 선택지로** | 타일 크기 차이가 정보 위계나 과업을 설명하지 못할 수 있다 | 벤토·단순 목록·비교 grid를 콘텐츠와 작은 폭 재배치에서 비교한다. 연도별 트렌드 주장은 근거가 아니다 |
| **콘텐츠 여백·본문 폭이 읽기를 방해함** | viewport·글꼴·언어·확대에 따라 줄 길이와 행동 영역이 달라진다 | gutter와 measure 후보를 320px·200% 확대·넓은 폭에서 렌더해 검증한다. 고정 px·ch 범위는 출발값일 뿐이다 |
| **푸터가 링크 4열 + 소셜 아이콘 + 카피라이트뿐** | AI가 가장 성의 없이 만드는 곳 | 실제 정보(연락처, 주소, 사업자 정보)를 넣어라. 링크가 4개뿐이면 4열로 만들지 마라 |
---
@ -88,19 +80,15 @@ Show HN 랜딩 1,590개 실측에서 검출률이 가장 높았던 셋이다. **
| 지문 | 왜 문제 | 대신 |
|---|---|---|
| **`rounded-2xl shadow-lg p-6` 를 손대지 않고 사용** | shadcn/ui 기본값. AI가 복붙하도록 설계된 값 | radius를 프로젝트 고유값으로(4px 또는 12px). 큰 그림자 대신 1px 보더 + 배경 톤 차이 |
| **모든 요소 border-radius 16px 균일 / 24px 이상 과대** | radius가 결정이 아니라 기본값임을 드러냄. 24px+ 는 블롭처럼 보임 | radius 어휘 2개 (컨테이너 12px, 인터랙티브 8px). 또는 0으로 통일 |
| **1px 회색 보더 + 넓게 퍼진 그림자 동시 사용** | 두 깊이 표현을 겹친 것. 광원 논리 없음 | 하나만 선택 |
| **글래스모피즘 전면 사용** (`backdrop-blur` 카드 다수) | 실기기 FPS 15~30% 하락 | 내비 바·모달로 제한. 카드에는 쓰지 마라 |
| **이모지를 아이콘 대신** (🚀 ⚡ 🎯 ✨) | 즉각적인 아마추어 신호 | 아이콘 세트 하나 고정 (Lucide, Phosphor, Radix). 이모지는 카피 안에서만 |
| **버튼이 항상 2개 나란히** (`Get started` + `Learn more`) | 주 행동을 정하지 못했다는 뜻 | 주 CTA 1개. 보조는 텍스트 링크로 격하 |
| **회색조 로고 6~8개 일렬 로고월** | 대부분 무관하거나 검증 불가 | 실제 고객이면 한 곳의 사례를 문장으로. 없으면 섹션 삭제 |
| **이니셜 원형 아바타 + 텍스트 후기 3개** | 검증 불가한 후기는 신뢰를 깎는다 | 실명 + 직함 + 원문 링크. 없으면 넣지 마라 |
| **가상 질문으로 채운 FAQ 6개** (`How does it work?`) | 실제로 받은 질문이 아님 | 실제 문의 3개. 답변에 구체적 숫자·조건 |
| **요금제 3열 카드 + 가운데 "Most popular" 배지** | 가장 재현율 높은 SaaS 템플릿 | 비교 표, 단일 가격, 또는 계산기 |
| **기본 radius·shadow·padding 조합을 그대로 사용** | 컴포넌트 역할·밀도·브랜드 표면이 토큰과 맞는지 점검한다 | radius·선·그림자·여백 후보를 실제 상태에서 비교하고, 역할 토큰으로 기록한다 |
| **여러 깊이 표현을 겹침** | 경계·그림자·표면 차이가 같은 계층을 중복 설명하는지 점검한다 | 하나 또는 여러 단서를 쓸 수 있다. 포커스·상태·인접 표면에서 계층이 읽히는지 검증한다 |
| **글래스모피즘 전면 사용** (`backdrop-blur` 카드 다수) | backdrop filter의 성능·대비·reduced-transparency 대안이 필요할 수 있다 | 대상 기기의 performance, fallback 표면, 텍스트 대비를 측정하고 역할이 있을 때만 채택한다 |
| **이모지를 아이콘 대신** (🚀 ⚡ 🎯 ✨) | 의미·locale·플랫폼 렌더링·접근 가능한 이름이 역할을 돕는지 점검한다 | 이모지·아이콘 세트·텍스트를 실제 대상과 보조기술에서 비교한다 |
| **여러 CTA가 같은 무게로 경쟁함** | 사용자가 다음 행동을 선택하기 어려울 수 있다 | 행동 우선순위·위험·되돌림에 맞춰 주/보조 CTA와 배치를 정하고 과업 테스트한다 |
| **로고월·후기·FAQ·요금제 템플릿** | 사실·질문·가격 구조가 현재 제품의 증거인지 점검한다 | 검증 가능한 출처와 실제 고객 질문·요금 조건을 사용한다. 개수·열 수·배지 존재로 실패시키지 않는다 |
| **펄스 애니메이션 상태 점** (초록 원 깜빡임 + "All systems operational") | 정적 정보에 장식 애니메이션 | 상태가 실제로 바뀔 때만 |
| **자동 스크롤 마퀴** (로고·후기·태그가 끝없이 흐름) | 읽기를 방해하고 주의를 강탈 | 정적 그리드. 많으면 페이지네이션 |
| **hover가 아무 반응 없거나 전 요소가 동일하게 `scale(1.02)`** | 인터랙션을 설계하지 않았다는 신호 | 요소마다 다른 반응(링크는 밑줄, 카드는 배경 톤, 버튼은 명도). **hover는 가장 먼저 설계한다** |
| **자동 스크롤 마퀴** (로고·후기·태그가 끝없이 흐름) | 읽기·움직임 민감도·입력과 충돌할 수 있음 | 목적·정지 수단·감소 모션·성능을 검증하고, 정적 그리드 또는 페이지네이션과 비교 |
| **hover 상태가 과업을 돕지 않음** | 포인터 반응이 행동 가능성·현재 상태·위험을 설명하지 못할 수 있다 | 링크·카드·버튼의 상태를 역할별로 설계하고, touch/keyboard에서도 같은 핵심 단서를 제공한다 |
| **편집 불가 히어로 카피 뒤 깜빡이는 커서 `|`** | 타이핑 흉내. 정보 없음 | 삭제 |
---
@ -109,14 +97,11 @@ Show HN 랜딩 1,590개 실측에서 검출률이 가장 높았던 셋이다. **
| 지문 | 왜 문제 | 대신 |
|---|---|---|
| **모든 요소에 동일한 fade-in-up** (`opacity 0→1`, `translateY 20px`, 같은 delay) | 모션 언어가 없다는 뜻. 페이지가 계속 떠오르기만 함 | 중요한 것에만. 순차 등장이 필요하면 stagger 40–60ms |
| **인터페이스에 bounce / elastic 이징** | 2010년대 감성. 즉시 촌스러움 | `ease-out` 또는 `cubic-bezier(0.2, 0, 0, 1)` |
| **이미지 hover 시 `scale` 또는 `rotate`** | 생성 UI의 반복 지문 | 이미지는 두고 캡션·오버레이·보더를 변화시켜라 |
| **duration이 전부 300ms** | 마이크로와 연출을 구분 못 함 | 마이크로 100–200 / 트랜지션 200–400 / 연출 600–1200ms |
| **같은 모션을 모든 요소에 적용** | 모션이 상태·관계·우선순위를 설명하지 못하고 읽기를 방해할 수 있다 | 중요한 변화에만 모션을 연결하고, duration/easing은 과업·거리·reduced-motion에서 검증한다. 고정 ms 범위는 출발값이다 |
| **bounce·elastic·scale·rotate 모션** | 장면의 물성·피드백 의미·멀미·성능과 맞는지 점검한다 | 이징과 속성은 실제 상호작용·reduced-motion·저사양 기기에서 비교한다 |
| **스크롤 reveal 실패 시 콘텐츠가 `opacity: 0`으로 남음** | JS 실패 시 빈 페이지가 됨 | 기본을 visible로 두고 JS가 숨긴 뒤 보여주는 방식. 또는 CSS scroll-driven animation |
| **`prefers-reduced-motion` 미대응** | 접근성 실패 | 모션 제거 경로를 반드시 제공 |
| **전 섹션 패럴랙스** | 스크롤 감각이 어긋나고 성능 저하 | 한 섹션만, 이동량 10–20px |
| **히어로에 3D/Spline 씬을 이유 없이** | JS 런타임 800KB~2MB. 모바일 4G에서 Core Web Vitals 실패 | 브리프가 요구할 때만. 아니면 정적 렌더 이미지 + 미세 모션 |
| **`prefers-reduced-motion` 미대응** | 사용자 모션 선호를 무시하면 중요한 상태를 이용하기 어려울 수 있다 | `prefers-reduced-motion`에서 비동작 대체 신호와 핵심 과업을 실제로 검증한다 (`evidence-ledger.md`의 `W3C-REDUCED`) |
| **전 섹션 패럴랙스·3D/Spline 씬** | 콘텐츠보다 연출이 우선되거나 저사양·느린 망에서 핵심 경로를 막을 수 있다 | 목적·성능 예산·정적 fallback·정지 제어를 정하고 local proxy와 field data를 구분해 측정한다 |
| **로딩할 게 없는데 프리로더 카운터** | 없는 대기를 만듦 | 삭제 |
---
@ -158,7 +143,7 @@ Show HN 랜딩 1,590개 실측에서 검출률이 가장 높았던 셋이다. **
|---|---|---|
| 표면은 CSS 가 정하고, 공간은 GPU 가 맡는다 | **텍스처 200KB 를 300바이트가 대신한다** | 구현 → 이득. 수치가 검증 가능 |
| Powerful Features | 엑셀 없이 정산이 끝난다 | 명사구 → 주장 |
| 빠르고 안정적인 배포 | 배포 실패가 롤백까지 90초 | 형용사 → 측정치 |
| 빠르고 안정적인 배포 | 실제 배포·롤백 결과와 조건 | 형용사 → 측정치. 예: 검증된 `p95 롤백 시간`, 기간, 환경 |
### 데모가 주장을 증명하는지 재라
@ -189,7 +174,7 @@ B 라면, 반응형에서 매번 바뀌는 것을 지목한 것이다. 지목하
| 지문 | 왜 문제 | 대신 |
|---|---|---|
| **`It's not X, it's Y` / `단순한 X가 아니라 Y입니다`** | ChatGPT 최대 지문. 400단어에 3번씩 나옴 | 그냥 Y를 말해라. 대조가 필요하면 비교 대상을 실명으로 |
| **`It's not X, it's Y` / `단순한 X가 아니라 Y입니다`** | 대조가 실제 차이·조건을 설명하지 못하면 문장이 비어 보일 수 있다 | Y를 직접 말하거나, 필요할 때 비교 대상과 검증 조건을 명시한다 |
| **스타카토 3연타** (`No fluff. No filler. No BS.` / `빠르게. 정확하게. 간단하게.`) | 리듬으로 내용 없음을 감춤 | 한 문장으로 구체적으로 |
| **em-dash(—) 한 문단에 2회 이상** | AI 문장 리듬의 대표 지문 | 마침표로 끊거나 쉼표로. 한국어에서 특히 부자연스럽다 |
| **`In today's fast-paced digital landscape...`** 도입 | 아무 말도 하지 않는 문단 | 첫 문장부터 본론. 도입 문단 삭제 |
@ -201,9 +186,9 @@ B 라면, 반응형에서 매번 바뀌는 것을 지목한 것이다. 지목하
| `Streamline / Empower / Supercharge / Unlock / Leverage / Seamless / Elevate` | 아무 제품에나 붙는 동사. 정보량 0 | 제품이 실제로 하는 동작 동사 (`정산한다`, `병합한다`, `4일을 6시간으로 줄인다`) |
| `world-class / cutting-edge / enterprise-grade / best-in-class` | 자기 평가 형용사는 증거가 아니다 | 인증명, 벤치마크 수치, 고객사 실명 |
| `Build the future of X` / `Your all-in-one platform` / `Scale without limits` | 경쟁사 이름으로 바꿔도 성립 | 치환 테스트를 통과하는 문장 (예: "Financial infrastructure for the internet") |
| 헤지 표현 (`may help you`, `~할 수 있습니다`) | 확신 없음 = 신뢰 없음 | 단정하거나 조건을 명시 (`10명 이하 팀에서는`) |
| 헤지 표현 (`may help you`, `~할 수 있습니다`) | 불확실성의 범위가 숨겨지면 독자가 주장 강도를 판단하기 어렵다 | 검증된 내용은 명확히 쓰고, 불확실하면 조건·근거·한계를 명시한다 |
| CTA가 `Get Started` / `Learn More` / `시작하기` | 무엇이 시작되는지 알 수 없음 | 결과를 말하는 CTA (`무료로 30일 써보기`, `가격표 보기`) |
| 모든 숫자가 어림수 (`10,000+`, `99.9%`, `50% faster`) | 검증 불가 | 정확한 수치와 측정 조건 (`p95 응답 240ms, 2026-07 기준`) |
| 출처·조건 없는 숫자 (`10,000+`, `99.9%`, `50% faster`) | 사실처럼 보이는 주장의 검증 경로가 없다 | 정확/범위 수치 모두 출처·측정 조건·기간을 붙인다. 조건 없는 수치는 제거하거나 예시임을 라벨링한다 |
| 모든 헤딩이 명사구 (`Powerful Features`, `Simple Pricing`) | 헤딩이 정보를 전달하지 않음 | 헤딩에 주장을 담아라 (`엑셀 없이 정산이 끝난다`) |
| 레이블·서브레이블·헬퍼가 같은 말 3번 (`이메일` / `이메일 주소` / `이메일 주소를 입력하세요`) | 화면 소음 | 하나만 남긴다 |
| 이모지로 시작하는 불릿 (`✅ 빠른 속도`) | 정보 위계를 이모지로 대체 | 일반 불릿 또는 문장으로 |
@ -214,10 +199,7 @@ B 라면, 반응형에서 매번 바뀌는 것을 지목한 것이다. 지목하
| 지문 | 왜 문제 | 대신 |
|---|---|---|
| **랩탑 앞에서 웃는 다국적 팀 스톡 사진** | 방문자는 스톡을 알아보고 **신뢰도가 실제로 하락**한다 | 실제 팀 사진, 실제 작업 공간, 또는 사진 없이 타입으로 |
| **떠 있는 3D 추상 블롭·기하 도형** | 콘텐츠 부재를 덮는 장식 | 제품 실제 화면, 데이터 시각화, 또는 여백 |
| **AI 생성 일러스트** (지나치게 매끄럽고 대칭적) | 결함이 없어도 톤에서 티가 남 | 일러스트 시스템을 직접 정의하거나 아예 쓰지 않는다 |
| **일반 SVG 도형을 조합한 히어로 그래픽** | 플레이스홀더 클립아트처럼 읽힘 | 제품 UI 조각을 실제로 렌더 |
| **스톡 사진·추상 3D·AI 일러스트·일반 SVG 그래픽** | 이미지가 제품 증거·브랜드·정보 이해 대신 빈 자리를 메우는지 점검한다 | 실제 사진·제품 UI·데이터·일러스트·여백 중 과업을 가장 잘 설명하는 것을 선택하고, 출처·라이선스·alt·크롭을 검증한다 |
| **아이콘 세트가 섞임** (라인·필·이모지 혼재) | 시스템이 없다는 증거 | 세트 하나 고정. 굵기·크기·광학 정렬 통일 |
| **`src`가 비었거나 깨진 이미지 태그** | 검증 없이 배포된 흔적 | 배포 전 이미지 로드 전수 확인 |
| **모든 이미지가 같은 비율의 둥근 사각형** | 그리드를 이미지에 강제 | 콘텐츠에 맞는 비율. 풀블리드 1장을 섞으면 리듬이 생긴다 |
@ -226,25 +208,19 @@ B 라면, 반응형에서 매번 바뀌는 것을 지목한 것이다. 지목하
## 8. 한글 조판 (한국어 프로젝트 필수)
서구 갤러리에는 없는 규칙이다. **영문 기준을 그대로 쓰면 전부 깨진다.**
한국어는 글꼴·줄바꿈·fallback·문장 리듬을 실제 콘텐츠와 지원 기기에서 별도로 검증한다. 영문 출발값을 그대로 복사하지 않는다.
| 규칙 | 값 | 이유 |
|---|---|---|
| **한글 폰트 미지정 금지** | `Pretendard` 또는 `본고딕` 명시 | 지정하지 않으면 시스템 기본(맑은 고딕 / Apple SD 산돌고딕)으로 떨어지고, 그 자체가 완성도 미달 신호다 |
| **line-height를 영문보다 높게** | 본문 **1.6–1.8** | 한글은 글자 밀도가 높아 1.5로는 답답하다 |
| **음수 자간 금지** | 본문 0, 대형 헤드라인만 -0.01 ~ -0.02em | 한글은 고정폭에 가까워 음수 자간이 즉시 뭉개진다 |
| **`word-break: keep-all`** | `overflow-wrap: break-word`와 함께 | 기본값은 단어 중간에서 줄바꿈되어 어색하다 |
| **한 줄 25–40자** | 영문 45–75자 기준을 쓰면 너무 길다 | 컨테이너 폭을 좁혀라 |
| **라틴 폰트를 폴백 앞에** | `font-family: 'Satoshi', 'Pretendard', sans-serif` | 한글 폰트를 앞에 두면 라틴 문자까지 그 폰트로 그려진다 |
| **번역투 금지** | `~를 통해`, `~에 대한`, `~에 있어서` | 영문 AI 카피를 기계 번역한 티가 난다. `~로`, `~의`, `~에서`로 |
| **한글 글꼴과 fallback 미확인** | 한국어 glyph coverage·줄바꿈·숫자·운영체제 fallback이 과업에 맞는지 점검한다 | 시스템 한글 폰트를 포함해 실제 지원 기기에서 긴 제목·본문·숫자를 렌더하고, 선택·라이선스·fallback metric을 기록한다. 시스템 폰트 사용만으로 완성도 미달로 취급하지 않는다. 출처: `references/typography.md`, 프로젝트 브리프 |
| **한글 line-height 후보** | 글꼴·크기·문단 길이·화면 폭에 따라 읽기 리듬이 달라진다 | 1.6–1.8을 포함한 후보를 실제 본문·확대 상태에서 비교한다. 특정 값이 보편 기준은 아니다 |
| **한글 음수 자간** | 글리프 충돌·단어 인식·확대 상태에서의 읽기성을 점검한다 | 본문·대형 제목·지원 viewport·200% 확대에서 실제 렌더를 비교한다. 음수 자간의 존재만으로 실패시키지 않고, 충돌이나 읽기 저하가 확인되면 조정한다. 출처: `references/typography.md`, 프로젝트 렌더 검증 |
| **줄바꿈·measure·fallback 순서** | 단어 단위 줄바꿈, 라틴/한글 숫자 렌더, 폭은 글꼴과 콘텐츠에 따라 달라진다 | `word-break`, `overflow-wrap`, measure, fallback 순서를 대표 문단·긴 단어·숫자·지원 기기에서 비교한다. 고정 글자 수나 순서를 규범으로 쓰지 않는다 |
| **번역투 가능성** | 문맥·대상 독자와 맞지 않는 직역은 의미를 흐릴 수 있다 | 실제 독자와 용어 체계에 맞춰 문장을 다듬고, 제시 표현을 기계적 치환 규칙으로 쓰지 않는다 |
**한글 폰트 선택**
- 기본값: **Pretendard** (SIL OFL, Thin~Black 9단계, 가변). CDN: `https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/variable/pretendardvariable.min.css`
- 명조·에디토리얼: **마루 부리** (세리프 부활 트렌드의 한글 대응)
- 수치 많은 UI: **Spoqa Han Sans Neo**
- 다국어 안정성: **본고딕 / Noto Sans KR**
- 캠페인 헤드라인: 배민 도현체·여기어때 잘난체 — **B2C 한정. B2B에 쓰면 즉시 아마추어**
- 나눔고딕은 너무 흔하다. 폴백으로만.
**한글 폰트 후보**
- Pretendard, 마루 부리, Spoqa Han Sans Neo, 본고딕/Noto Sans KR 등은 목적·glyph coverage·라이선스·fallback·전송량을 비교할 후보다.
- 캠페인 display 폰트도 업종 자체로 배제하지 않는다. 실제 문구·브랜드·작은 크기·지원 기기에서 읽기와 인상을 검증한다.
---
@ -281,11 +257,11 @@ It's not .* it's
**한글 프로젝트 추가 검사**
```
# 있어야 하는 것 — 없으면 실패
# 확인 후보 — 프로젝트 계약과 실제 렌더로 평가
word-break:\s*keep-all
Pretendard|Noto Sans KR|본고딕|마루부리
# 없어야 하는 것
# 세밀하게 확인할 후보 — 프로젝트 계약과 실제 렌더로 평가
letter-spacing:\s*-0\.0[3-9] (한글 본문)
~를 통해|~에 대한|에 있어서
```
@ -306,10 +282,10 @@ letter-spacing:\s*-0\.0[3-9] (한글 본문)
```js
await page.evaluate(() => el.scrollIntoView({ block: 'center' }));
await page.waitForTimeout(1600); // 반드시 애니메이션이 끝난 뒤에 잰다
await page.waitForTimeout(1600); // 프로젝트의 모션 종료 조건을 확인한 뒤 잰다
```
**② `sharp(...).extract(...).stats()` 는 잘라내기를 무시하고 원본 전체를 잰다.**
**② `sharp(...).extract(...).stats()` 사용 결과가 의도한 crop 통계를 반영하는지 확인한다.**
영역을 여섯 군데 잘라 밝기를 쟀는데 여섯 개가 **소수점까지 똑같이** 나왔다.
전부 원본 전체 평균이었기 때문이다. 값이 수상하게 균일하면 도구를 먼저 의심해라.
@ -324,12 +300,10 @@ const s = await sharp(buf).stats(); // 잘라낸 버퍼를 다시 물려야
겹쳐 있어서 "필터가 안 붙는다" 는 잘못된 결론이 나왔다. 멈춘 프레임을 그대로
찍으려면 그 옵션을 **빼야** 한다.
> 여기서 더 중요한 결론이 나왔다 — **움직임으로 증명하려 들지 마라.**
> 지나가는 프레임은 방문자도 놓치고 측정도 매번 다른 값을 준다.
> 정지된 before/after 두 장이 더 정확하게 말한다.
> 움직임이 핵심 주장을 전달한다면 정지 상태의 대체 증거와 함께 검증한다. 지나가는 프레임은 캡처 시점에 따라 달라질 수 있으므로, before/after 정지 캡처가 비교에 적합한 경우가 많다.
**④ 개별 요소의 높이로는 "여러 줄에 걸친 것" 을 못 잡는다.**
"컨트롤이 2줄로 접히는가"(하드 게이트 6)를 이렇게 쟀다.
"컨트롤 행이 지원 폭에서 읽거나 조작할 수 없게 되는가"를 이렇게 쟀다.
```js
// 틀렸다 — 이 검사는 "접힘 0" 이라고 답한다
@ -348,16 +322,15 @@ new Set([...document.querySelectorAll('.nav a')]
```
같은 함정이 갤러리·태그 목록·버튼 그룹 어디에나 있다.
**"몇 줄인가" 를 물을 때는 높이가 아니라 위치를 세라.**
**"몇 줄인가"를 물을 때는 컨테이너의 위치 분포와 요소 높이를 함께 본다.**
**⑤ 눈으로 의심한 것이 실측에서 뒤집힐 수 있다.**
WebGL 판이 본문 뒤를 지나가는 화면을 보고 "대비가 죽었다" 고 판단해 캔버스를
어둡게 만들려 했다. 스크린샷에서 글자 사이 빈 띠의 배경 휘도를 재보니
최악 지점이 **9.83:1** — AAA(7:1)를 넘었다. 고칠 필요가 없었다.
밝은 것과 대비가 낮은 것은 다르다. **화려한 배경 위의 대비는 인상이 아니라 숫자로 판정해라.**
밝은 것과 대비가 낮은 것은 다르다. 화려한 배경 위 텍스트는 실제 배경 합성 상태의 contrast ratio와 읽기 관찰을 함께 확인한다.
> 캔버스 위에서는 `readPixels` 가 안 통한다(three.md 참고). 스크린샷을 찍어
> **글자를 피한 빈 띠**의 평균 휘도를 재는 것이 유일하게 맞는 방법이다.
> 캔버스 접근 가능성은 렌더링 구성에 따라 다르다. 스크린샷 기반 측정은 한 방법이며, 텍스트와 배경의 실제 합성 상태를 대표하는 표본을 골라 계산한다.
---
@ -371,8 +344,7 @@ sticky 캔버스가 끝까지 돌려면 목록 뒤에 스크롤 구간이 필요
문장 자체는 틀리지 않았다. 다만 **읽는 사람이 그 자리에서 기대하는 말이 아니다.**
사용자가 처음 한 말이 "이건 왜 있는 거예요" 였다.
레이아웃이 필요해서 쓴 문장은 예외 없이 겉돈다. 여백이 필요하면
`padding` 을 주고, 그 자리에 놓을 말이 정말 있을 때만 문장을 쓴다.
레이아웃을 위해 추가한 문장이 콘텐츠 역할을 하지 않으면 겉돌 수 있다. 여백과 문장 중 어느 것이 목적을 더 잘 충족하는지 검토한다.
> 자가 진단: 이 문장을 지우면 **레이아웃이 깨지는가, 뜻이 빠지는가?**
> 레이아웃만 깨진다면 그 문장은 여백의 대역이고, 여백으로 바꿔야 한다.
@ -393,7 +365,7 @@ sticky 캔버스가 끝까지 돌려면 목록 뒤에 스크롤 구간이 필요
**곁가지 하나가 본론의 1.3 배, 첫인상이 가장 짧다.** 분량 배분이 중요도와 정반대다.
사용자 표현으로는 "정신이 없어 보인다".
부분 수정을 몇 번 하고 나면 반드시 전체를 다시 재라.
부분 수정이 누적되면 전체 비율·읽기 순서·핵심 과업을 다시 측정한다.
```js
// 섹션별 화면 수 — 배분이 우선순위와 맞는지 한 번에 보인다
@ -453,26 +425,22 @@ sticky 캔버스가 끝까지 돌려면 목록 뒤에 스크롤 구간이 필요
| 7 이미지 | | |
| 8 한글 조판 | | |
**통과 조건 — 전부 예여야 한다**
- [ ] 최우선 3종(보라 CTA / 전체 대문자 / 1·2·3 단계) 중 걸린 것이 없다
- [ ] grep 검사에서 나온 항목을 전부 해소했다
**검토 조건 — 근거와 결과를 남긴다**
- [ ] 반복 후보(보라 CTA / 전체 대문자 / 1·2·3 단계)를 발견했으면 프로젝트 계약·브리프·실제 렌더 근거로 유지·수정·제거를 결정했다
- [ ] grep 검사에서 나온 항목은 자동 실패로 취급하지 않고, 출처·관찰·결정·검증을 기록했다
- [ ] 카피가 경쟁사 치환 테스트를 통과한다
- [ ] 모션을 전부 끄고도 페이지가 작동한다
- [ ] 강조색을 지워도 페이지가 읽힌다
- [ ] 색을 회색조로 바꿔도 위계 순서가 비즈니스 우선순위와 일치한다
- [ ] 키보드만으로 전 인터랙션이 가능하고 포커스 링이 보인다
- [ ] (한국어) 한글 폰트가 명시되어 있고 `word-break: keep-all`이 있다
- [ ] 모든 섹션에서 제목→본문 간격이 **같은 값**이다 (0px 인 섹션이 하나도 없다)
- [ ] (한국어) 글꼴·fallback·줄바꿈 규칙이 실제 지원 기기와 긴 제목·본문·숫자에서 읽히는지 검증하고, 프로젝트 계약에 선택 근거를 남겼다
- [ ] 제목→본문 간격이 각 섹션의 정보 관계를 설명하고, 의도하지 않은 0px·겹침·불균형이 없는지 실제 렌더에서 확인했다
- [ ] 화면에 적은 수치는 **전부 그 화면을 캡처해 잰 값**이다 (추정치·기대치가 섞여 있지 않다)
- [ ] 이미지 높이를 실제로 쟀다 — 원본 픽셀 높이가 그대로 나오면 `aspect-ratio` 가 죽은 것이다
- [ ] 이미지의 실제 표시 비율·크롭·해상도가 의도한 콘텐츠 역할과 화면 폭에 맞는지 측정했다. 원본 픽셀과의 일치만으로 `aspect-ratio` 동작을 판정하지 않는다
- [ ] 내비·버튼 그룹의 **행 수를 `top` 값으로** 셌다 (개별 높이로는 못 잡는다)
**판정**
| 걸린 개수 | 판정 |
|---|---|
| 0–1 | 통과 |
| 2–3 | 해당 항목 수정 후 재검 |
| **4 이상** | **폐기. reference-method.md §3부터 다시** |
후보 수를 점수나 자동 통과 기준으로 쓰지 않는다. 각 후보마다 `프로젝트 계약`, `출처 ID`, `적용 조건`, `실제 렌더 관찰`, `유지·수정·제거 결정`, `검증 결과`를 남긴다. WCAG 성공 기준 또는 실제 기능 실패가 있으면 그 항목은 해결 전 통과로 선언하지 않는다. 그 밖의 스타일 후보는 과업·브랜드·위계·성능의 증거로 판정한다.
> 근거: research/references/04-ai-slop-signatures.md, 03-trends-2026.md (조사일 2026-08-20)
> 근거와 한계: `references/evidence-ledger.md`, `references/design-foundations.md`, 프로젝트 브리프와 렌더 검증

View file

@ -0,0 +1,149 @@
# art direction — 화면에서 판단할 수 있게 방향을 정하는 법
2단계에서 방향을 고르고, 3단계 토큰과 4단계 구현 전에 읽는다. 아트디렉션은 스타일 이름이나 색 견본을 고르는 일이 아니라, 실제 카피·자산·과업을 어떤 화면 질서로 보일지 결정하는 일이다.
이 문서는 원전을 그대로 옮긴 규칙도, 특정 효과의 실험적 성과 주장도 아니다. 아래 출처와 프로젝트 렌더를 바탕으로 합성한 작업 절차다.
| 이 문서를 여는 때 | 여기서 얻는 결정 | 답이 부족할 때 필요한 추가 근거 |
|---|---|---|
| 첫 화면의 정보 순서·이미지 역할·섹션 리듬이 아직 불명확할 때 | 방문자 질문, 주장, 증거, 다음 행동과 그것의 화면 비중 | 실제 사용자 과업, 콘텐츠 소유자 확인, 브랜드 자산·권리·사실성, 지원 기기·상태별 렌더 |
| 같은 스타일 안에서 구도·크롭·타입·밀도를 정해야 할 때 | 초점, 정렬선, 여백 역할, 원본·배포 크롭, proof sheet의 채택 근거 | 원본 자산, 실제 카피·수치·상태, 이미지 설명·접근성 요구, 배포 비율과 실제 viewport |
| 한 선택의 품질이나 과업 적합성을 판정할 때 | 동일 조건에서 비교할 관찰·원인 후보·한 가지 수정 | 프로젝트 계약, 사용성·접근성 규범, 기능·성능 측정, 필요하면 해당 분야의 1차 출처 또는 현장 검증 |
출처가 현재 질문을 직접 지지하지 않으면 이 문서의 예시를 규칙으로 확대하지 않는다. 필요한 과업·자산·규범의 1차 근거를 새로 확인하고, 그 범위와 한계를 프로젝트 기록에 남긴다.
---
## 1. 대안을 언제 비교할지 먼저 정한다
큰 불확실성이 있는 디자인 결정에서만 구조가 다른 대안을 작게 비교한다. 예를 들어 첫 화면이 공간의 분위기를 먼저 보여야 하는지, 예약 가능한 시간과 위치를 먼저 보여야 하는지처럼 정보 순서가 바뀌는 결정이다.
이때의 대안은 색·서체·사진만 바꾼 변형이 아니라, 질문에 답하는 순서와 화면 골격이 달라야 한다.
| 상황 | 할 일 | 하지 않을 일 |
|---|---|---|
| 브랜드·과업·정보 우선순위가 아직 불확실함 | 서로 다른 구조를 가진 소수 안을 같은 콘텐츠로 비교 | 시안 수를 의무화하거나 장식만 바꾼 안을 세기 |
| 브랜드 방향과 핵심 구조가 확정됨 | 기존 방향 안에서 카피·크롭·밀도 후보를 고름 | 확정된 정체성을 매 화면마다 다시 경쟁시킴 |
| 국소 컴포넌트나 상태를 다듬음 | 그 화면의 과업과 상태를 기준으로 후보를 비교 | 전체 랜딩의 AIDA나 히어로 구도를 복사 |
결정마다 `무엇이 불확실한가 / 구조 차이는 무엇인가 / 같은 조건은 무엇인가 / 채택 이유는 무엇인가`를 `design.md`에 남긴다. 대안 탐색은 좁은 문제를 확인하는 절차이며, 더 많은 안이 더 좋은 결과를 보장하지 않는다.
## 2. 콘텐츠 편집표로 화면의 일을 정한다
레이아웃 전에 각 화면 또는 섹션이 답해야 할 질문을 적는다. 마케팅 서사에 유용한 순서가 있어도 기능 화면·운영 화면에 AIDA를 강요하지 않는다.
| 방문자 질문 | 핵심 주장 | 실증 콘텐츠 | 행동 또는 다음 정보 | 화면 비중 |
|---|---|---|---|---|
| 여기는 무엇이며 나에게 왜 중요한가 | 한 문장 가치 | 실제 결과, 공간, 사례, 수치의 출처 | 더 보기, 예약, 시작 | 첫 화면에서 가장 크게 |
| 이 주장을 믿을 근거가 있는가 | 구체적 차이 | 과정, 비교, 고객 맥락, 검증 가능한 정보 | 세부 근거로 이동 | 주장 다음에 충분히 |
| 지금 무엇을 해야 하는가 | 다음 행동의 비용과 결과 | 가격, 일정, 준비물, 현재 상태 | 실행, 저장, 문의 | 행동 직전에 읽히게 |
| 일이 진행 중인 운영 화면인가 | 지금의 상태와 예외 | 목록, 필터, 경고, 최근 변경 | 처리, 검토, 복구 | 빈도·위험도에 비례 |
행마다 실제 문장, 실제 데이터, 실제 자산을 붙인다. 주장이 있는데 실증 콘텐츠가 없으면 장식으로 덮지 말고 주장 범위를 줄이거나 근거를 확보한다.
행동이 없으면 CTA를 억지로 추가하지 말고 다음에 필요한 정보를 우선한다.
## 3. 실제 재료로 구도 축을 고른다
빈 로렘입숨과 회색 상자로 구도를 확정하지 않는다. 실제 한글·영문 카피, 제목 길이, 숫자, 사진·도표·상태를 넣고 아래 축 중 필요한 것을 고른다.
| 구도 축 | 질문 | 렌더에서 확인할 것 |
|---|---|---|
| 초점 | 처음 시선이 닿아야 하는 것은 무엇인가 | 제목·행동·증거 중 하나가 경쟁 없이 먼저 읽히는가 |
| 정렬선 | 무엇이 같은 관계로 묶이는가 | 제목, 수치, 표, 행동의 시작선이 의미를 드러내는가 |
| 큰 면과 작은 정보 | 무엇이 면적을 가져야 하는가 | 큰 사진·제목이 보조 정보의 읽기를 막지 않는가 |
| 여백의 역할 | 비어 있는 면이 무엇을 돕는가 | 분리, 대기, 방향, 강조 중 이유 없이 빈 곳은 없는가 |
| 이미지와 텍스트 관계 | 둘은 같은 주장인가 다른 역할인가 | 텍스트가 사진 속 사실을 과장하거나 반복하지 않는가 |
색만 바꾼 안은 별도 구조안이 아니다. 사진 위 텍스트는 대비만 보지 말고, 피사체·캡션·행동이 같은 의미를 말하는지 확인한다.
수치·상태·가격·주소처럼 수정 가능하고 사실 검증이 필요한 정보는 이미지 안에 굽지 않는다.
## 4. 이미지는 원본과 배포 크롭을 함께 검토한다
이미지의 품질은 원본 파일만으로 판정하지 않는다. 원본, 데스크톱 배포 크롭, 모바일 배포 크롭을 나란히 놓고 슬롯별로 아래를 기록한다.
| 기록 항목 | 확인 질문 |
|---|---|
| 주피사체 | 이 화면에서 반드시 남아야 하는 사람·제품·공간·행동은 무엇인가 |
| 시선 방향 | 인물·물체·선이 카피나 다음 행동으로 시선을 보내는가 |
| 사라지는 맥락 | 크롭 뒤에 장소·관계·사용 장면의 뜻이 사라지지 않는가 |
| 데스크톱 초점 | 넓은 비율에서도 핵심이 안전 영역에 남는가 |
| 모바일 초점 | 좁은 비율에서 핵심이 폴드 아래·텍스트 뒤로 밀리지 않는가 |
| `object-position` | 같은 원본을 쓸 때 각 비율에서 의도한 초점 좌표가 명시됐는가 |
한 장의 원본을 모든 슬롯에 억지로 쓰지 않는다. 반대로 비교를 위해 여러 이미지를 인위적으로 생성할 필요도 없다. 필요한 슬롯에 실제로 다른 구도가 필요할 때만 별도 자산을 고른다.
이미지 조달·진실 라벨·대체 텍스트의 세부는 [images.md](images.md)와 `trustworthy-showcases.md`를 따른다.
## 5. 타입은 proof sheet에서 결정한다
폰트 이름, 문자 견본, 한 줄 영문만으로 위계를 확정하지 않는다. 새 서체·타입 계층을 고르거나 변경이 줄바꿈·밀도·상태 인지에 영향을 줄 때만, 같은 폭과 크기에서 실제 콘텐츠를 넣은 proof sheet를 만든다.
- 프로젝트에서 쓰는 언어의 본문과 긴 제목
- 표시하는 가격·날짜·비율·음수·증감 같은 숫자
- 실제로 쓰는 선택됨, 오류, 비활성, 처리 중 같은 상태 텍스트
- 변경 범위에 있는 버튼 라벨, 표 머리글, 카드 보조 정보
그 뒤 줄바꿈, 숫자 폭, 밀도, 상태의 구분, 확대 상태에서의 읽기를 비교한다. 서체 선택·로딩·폴백 메트릭·라이선스와 한글 조판의 정본은 [typography.md](typography.md)다.
proof sheet는 멋의 순위를 매기는 카드가 아니라, 이 프로젝트의 실제 글자가 어떤 구도를 요구하는지 확인하는 증거다.
## 6. 전체 스크롤의 리듬을 편집한다
반복은 나쁜 것이 아니다. 같은 데이터·행동·비교 규칙을 읽게 하는 반복은 보존한다. 문제는 역할이 다른 섹션이 같은 제목·카드·장식만 반복해서 스크롤이 정보를 더하지 않을 때다.
1. 각 섹션의 의미 단위와 다음 행동을 목록화한다.
2. 같은 의미 단위는 같은 리듬을 유지한다.
3. 정보 관계가 달라지면 필요한 순서·밀도·배치를 조정한다.
4. 장식만 변주해 다른 섹션처럼 보이게 하지 않는다.
5. 긴 페이지는 폴드마다 무엇이 새로 이해되거나 진행되는지 실제 스크롤에서 확인한다.
리듬을 바꾸는 이유는 새 배경색이나 카드 모양이 아니라 독자의 질문과 과업이 바뀌었기 때문이다.
## 7. 실제 화면에서 한 번에 하나씩 비평한다
소스 코드나 모형을 보고 완성도를 선언하지 않는다. 같은 viewport, 상태, 카피, 자산에서 전후 렌더를 비교한다.
1. **관찰**: 무엇이 먼저 읽히는지, 어디에서 시선·행동·관계가 끊기는지 적는다.
2. **원인 후보**: 구도, 카피 길이, 크롭, 위계, 상태 밀도 중 가능한 원인을 좁힌다.
3. **한 가지 수정**: 한 번에는 한 원인 가설만 바꾼다.
4. **동일 조건 비교**: 같은 폭·상태·콘텐츠에서 전후를 보고 유지·되돌림·다음 수정을 결정한다.
비평은 아래 세 관점에서 따로 적는다.
| 관점 | 확인 질문 | 주장하지 말 것 |
|---|---|---|
| 독창성 | 이 브랜드·과업·자산에서만 나올 이유가 보이는가 | 낯선 효과 하나가 고유성을 증명한다 |
| 완성도 | 정렬, 타입, 크롭, 여백, 상태가 같은 규칙으로 끝까지 유지되는가 | 자동 심미 점수가 품질을 증명한다 |
| 과업 | 사용자가 다음 정보와 행동을 예측·완료·복구할 수 있는가 | 시각 점수만으로 성공률이나 전환 인과를 증명한다 |
완료 판정은 세 갈래로 분리한다: 사용성·접근성 규범, 프로젝트의 기능·성능 계약, 시각 평가.
한 갈래의 통과가 다른 갈래의 통과를 대신하지 않는다.
## 8. 같은 규칙도 화면에 따라 다르게 쓴다
### 공간 포트폴리오
방문자 질문은 “이 장소의 분위기와 작업 범위가 내 프로젝트에 맞는가”다.
첫 화면은 실제 공간의 주피사체와 짧은 주장으로 관계를 만들고, 다음 섹션은 재료·과정·완성 공간의 증거로 이어진다.
카피가 길지 않다면 큰 이미지 면과 넓은 여백이 장소의 규모·빛·질감을 읽게 할 수 있다.
모바일 크롭에서 창, 사람의 행동, 재료 중 무엇이 사라지면 장소의 뜻이 바뀌는지 기록한다.
문의 행동은 검증 가능한 범위·일정 정보 다음에 둔다.
### 밀집 운영 화면
방문자 질문은 “지금 무엇이 막혔고 어떤 항목부터 처리해야 하는가”다.
첫 화면은 분위기 사진보다 상태, 위험, 우선순위, 가능한 행동을 먼저 둔다.
같은 종류의 행·필터·수치는 반복 구조를 유지해야 비교가 빠르며, 여백은 장식보다 군집과 오류 복구를 돕는다.
큰 영문 타이틀이나 풀블리드 이미지를 공간 포트폴리오처럼 복사하면 상태 정보와 행동이 밀릴 수 있다.
타입 proof sheet에는 긴 한국어 상태, 숫자 열, 비활성·오류 라벨을 넣어 밀도에서 실제로 비교한다.
두 화면 모두 실제 콘텐츠, 구도 축, 원본과 크롭, 동일 조건 비평을 쓴다.
다만 질문과 실증 콘텐츠가 다르므로 면적·정렬선·리듬·행동 순서를 같게 만들 이유는 없다.
## 근거와 적용 경계
| ID | 출처와 역할 | 이 문서에서의 사용 한계 |
|---|---|---|
| DC-DIAMOND | [Design Council, Double Diamond](https://www.designcouncil.org.uk/resources/the-double-diamond/) · practice | 불확실한 문제에서 소규모 대안 탐색과 검증의 절차 참고이며, 시안 수나 성과의 근거가 아니다 |
| VA-MOFFAT | [V&A, Curtis Moffat: Working Methods](https://www.vam.ac.uk/articles/curtis-moffat-working-methods) · case · 2025-09-24 | contact print와 crop을 한 작업 사례로 참고하며, 모든 프로젝트의 이미지 절차로 일반화하지 않는다 |
| CH-DESCRIPTION | [Cooper Hewitt image description guidelines](https://www.cooperhewitt.org/cooper-hewitt-guidelines-for-image-description/) · practice | 중요한 정보부터, 공간 관계를 따라 설명하는 방식을 참고한다. alt 단어 수를 고정하지 않는다 |
| LUPTON-POSTERS | [How Posters Work](https://www.cooperhewitt.org/publications/how-posters-work/) · bibliography | 책의 소개·서지 확인만 한다. 원문을 읽었거나 개념을 인용했다는 주장이 아니다 |
| GOV-IMAGES | [GOV.UK Design System: images](https://design-system.service.gov.uk/styles/images/) · practice | 정부 서비스 범위에서 과업 정보·맥락·alt를 다루는 지침이다. 감성 사진 금지를 상업 브랜드에 일반화하지 않는다 |
출처의 상세 기록 형식과 사용성·접근성·성능 검증은 [evidence-ledger.md](evidence-ledger.md), [design-foundations.md](design-foundations.md), 프로젝트의 `design.md`를 함께 따른다.

View file

@ -2,9 +2,9 @@
5단계(감사)를 **완료 선언의 게이트**로 만드는 문서다. 핵심 원칙은 하나다:
> **완료의 정의는 게이트 통과다. 사용자를 QA 로 쓰지 않는다.**
> **완료는 기능·프로젝트 계약·렌더 시각 평가가 함께 확인된 상태다. 사용자를 QA 로 쓰지 않는다.**
"고쳤다"는 말의 근거는 검증 리포트의 수치다. 게이트가 실패 항목을 내면 4단계로 돌아가 고치고 **다시 전체를 돌린다** — 고친 것만 재검하지 않는다. 이 루프(구현 → 게이트 → 실패 → 수정 → 재게이트)가 designpaca의 기본 동작이며, 게이트 없이 6단계로 넘어가는 것은 경로 무관 금지다.
"고쳤다"는 말의 근거는 검증 리포트의 수치와 실제 렌더 평가다. 게이트가 실패 항목을 내면 4단계로 돌아가 고치고, 변경한 영역과 연결된 회귀 위험을 다시 검사한다. 프로젝트가 정한 전체 검증 계약은 그대로 수행한다. 이 루프(구현 → 게이트 → 실패 → 수정 → 재게이트)가 designpaca의 기본 동작이며, 기능·접근성 자동 검사와 시각 평가(위계·브랜드·타입·구도·이미지·리듬)는 별도 결과로 보고한다.
## 왜 필요한가 — 실제 사고 목록
@ -16,6 +16,8 @@
| 브랜드가 모바일에서 1글자 폭으로 수축, 세로로 쌓임 | 수축 탐지(짧은 라벨 n줄 이상) + 브랜드 1줄 |
| 기기 글꼴 확대(안드로이드 큰 글꼴 1.3×)에서 제목 부서짐 | 스케일×폭 h1 행렬 |
| 본문 보조색이 배경 대비 4.4:1 (AA 미달) | 전 요소 대비 스캔(블렌드 계산) |
| CSS `color-mix()`의 computed `color(srgb …)`를 0~255 rgb로 오독해 정상 대비를 1:1로 거짓 실패 | CSS Color 4 `color(srgb 0~1)` 파싱 + 알파 합성 RED fixture |
| `.sr-only`·`aria-hidden` 장식·빈 overlay의 범위를 보이는 셀/버튼 글자로 합산해 0px 여백 거짓 실패 | text node 단위 실측 + 비가시 조상 제외. 화면에 보이는 글자만 인셋 판정 |
| 조사만 하고 안 고친 영역(관심 과목 등) | 전 뷰 순회 — 검사는 '내가 고친 것'이 아니라 전체 표면 |
| 대시보드 26명 ↔ 명단 12명 숫자 모순 | 콘텐츠 정합(요약 = 재계산 비교) |
| aria-label 을 div/ul 에 오용(보조기술 무시) | html-validate + axe |
@ -29,12 +31,14 @@
| 같은 명시도의 숨김 규칙이 기본 규칙보다 앞에 있어 짐 — 모바일에서 숨기려던 kbd 안내가 렌더됨(6% 시각 diff) | 시각 회귀 + 규칙: **숨김 오버라이드는 기본 규칙보다 파일 뒤쪽에** 두거나 컴포넌트 규칙 근처의 미디어쿼리로 |
| 탐색 차터가 결과를 단언하지 않아 "9288점" 입력이 조용히 통과(값을 덧붙인 타이핑) | 하니스 규칙 8(검사는 단언)·9(number 입력 조작) |
| 모바일 라운드 — 중앙 모달이 썸존 밖, 백 키가 앱을 통째로 종료, safe area 무시, 입력 확대 | L6 시나리오(백·시트·터치타깃 스윕) + `references/mobile-app-ux.md` 표준 — **OS 관습은 조사 없이 바꾸지 않는다** |
| 전역 `* { margin: 0 }`가 native dialog의 UA 중앙 여백을 지워 좌상단에 고정, 짧은 화면의 패널은 고정 크롬에 눌려 내부 scroll이 0px | dialog의 `fixed/inset/margin:auto`를 명시하고 390·1440·2560 중심 오차, viewport rect, `html` scroll lock, short-height scroll owner를 hard fail로 측정. 모달 행동은 [WAI-ARIA APG](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)를 따른다 |
| 2560px 분할 히어로에서 사진이 1400px 이상으로 팽창하고 카피 열은 200px 안팎으로 붕괴 | hero·copy·media·후속 정보 레일의 rect 계약. stage/media/copy/workbench 토큰을 분리하고 media 상한·copy 최소폭·정렬을 hard fail로 측정 |
| 탭 전환 스크롤 잔류 — 짧은 뷰에서 클램프되어 헤더 반쯤 잘림, 뷰마다 크롬 위치 제각각 | 뷰 전환 `scrollTo(0,0)` 단언(M5) + mobile-app-ux.md |
| 크럼을 헤더로 내보내 빈약한 헤더 — 계측 전부 통과했지만 사용자에게 '깨져 보인다' | 헤더 sticky·브랜드+액션 구조 단언 + **시각 무게 평가(눈)** — mobile-app-ux.md |
| 왼쪽 인셋 리듬 불일치(제목 16 vs 앱바·카드 24) — 오버플로 검사는 오른쪽만 보므로 통과 | M6 왼쪽 인셋 스위트(텍스트 시작점 ≥12px·같은 축 ±10px) + preflight §0-E |
| 모바일에서 절대 배치 지도 자식의 부모를 `position: static`으로 바꿈 — offsetParent가 BODY가 되어 격자·핀이 히어로 전체를 덮음. 기하 게이트 8/8 통과 | **상태별 실제 캡처를 열어 보는 시각 E2E** + absolute/fixed 자식의 containing block 단언(하니스 규칙 19) |
| 새 관리자·가맹 서브페이지가 생겼지만 `public/work/*/index.html`만 검사 — 새 HTML 오류 8건이 검증 밖 | 정적 입력을 `public/work/**/*.html`·`**/*.css`로 재귀화 + 검사 대상 파일 수 단언(하니스 규칙 20) |
| 마케팅 h1의 의도적 2줄 때문에 범용 1줄 규칙을 꺼 버릴 위험 | 기본 1줄 유지 + 페이지별 `h1MaxLines` 계약. 375·390 × 글꼴 확대 매트릭스도 같은 상한 적용 |
| 마케팅 h1의 의도적 2줄 때문에 범용 줄 수 규칙을 강제할 위험 | 페이지별 읽기 폭·문구·글꼴 확대의 계약을 기록하고 375·390 × 글꼴 확대에서 실제 줄 수와 가독성을 판정. 줄 수를 맞추려고 글자 크기를 축소하지 않음 |
## 7계층
@ -44,7 +48,7 @@
|---|---|---|
| L0 정적 | stylelint + html-validate | 문법·구문 위반을 브라우저 켜기 전에 |
| L1 단위 | 순수 계산 로직 추출(calc.js) + 경계값 테스트 | 프로젝트별 작성 — 0명/만석/초과/빈배열. **입력 검증·신청 가능 같은 '판정'(가능/불가+사유)도 여기 둔다** — UI 는 판정을 문장으로 번역만 |
| L2 불변식 | `design-gate.mjs` — 오버플로·h1·수축·대비·SEO/meta·죽은 선택자·토큰 위생·theme-color 정합 | 전 뷰 × 전 폭 |
| L2 불변식 | `design-gate.mjs` — 오버플로·제목 존재/가시성·수축·대비·SEO/meta·죽은 선택자·토큰 위생·theme-color 정합. 명시한 양의 `h1MaxLines`만 줄 수 계약으로 검사 | 전 뷰 × 전 폭 |
| L3 시각 회귀 | 기준 화면 pixelmatch diff (≥0.1% 실패) + 핵심 상태 캡처 | 갱신은 검증된 배포 후 `--update-baseline`. 캡처 존재와 사람의 시각 판정은 별개다 |
| L4 WebKit | Playwright webkit | 사파리 엔진 렌더 차이 |
| L5 탐색 | 키보드 완전 통과 + 사용자 여정 차터 | 프로젝트별 작성 — 마우스 금지 |
@ -70,7 +74,7 @@ L1·L5 는 프로젝트 안에 `tools/unit/*.test.mjs`, `tools/exploratory.mjs`
- **Windows**: 스크롤바가 오버레이가 아니다 — `overflow-x:auto` 짝에는 `overflow-y:hidden` + `scrollbar-width` 검토. 스크롤바 자체가 레이아웃을 민다.
- **iOS/Safari(WebKit)**: `100vh` 주소창 문제(`100dvh`), 텍스트 인플레이션(`-webkit-text-size-adjust:100%`), WebKit 만의 flex/행높이 차이 → L4 로 재검.
- **안드로이드**: 시스템 글꼴 확대(1.15~1.3× 흔함) — rem 전면 확대로 제목 칸 수축 → 스케일×폭 행렬으로 재현·검증.
- **안드로이드**: 시스템 글꼴 확대는 제목·컨트롤을 수축시킬 수 있다. gate의 `fontScales`와 computed root 스케일은 회귀 신호일 뿐, px 고정 서체의 실제 OS 확대를 보장하지 않는다. 실제 접근성 확대와 WCAG 200% zoom은 별도 브라우저·기기 검증으로 보고한다.
- **폰트 스왑**: `font-display: swap` 의 CLS — 폴백 메트릭 보정(typography.md §4) 없이 검사 통과를 선언하지 않는다.
## 하니스 규칙 (테스트를 만드는 테스트)
@ -103,12 +107,18 @@ L1·L5 는 프로젝트 안에 `tools/unit/*.test.mjs`, `tools/exploratory.mjs`
26. **진실 라벨은 개수가 아니라 의미 범위를 단언한다** — 같은 상태·출처·행동 가능성을 공유하는 자식은 제목과 경계가 분명한 가장 가까운 공통 부모가 한 번 소유하고 프로그램적으로 연결한다. 상태가 다른 자식만 예외 라벨을 둔다. 같은 공개 문구가 한 그룹의 모든 자식에 복제되면 범위 설계를 다시 본다.
27. **보이는 입력과 판정 의존성을 같은 SSOT로 잠근다** — 폼의 사용자 조건 이름 집합과 순수 판정 함수가 참조하는 사용자 조건 집합을 비교한다. 숨은 기본값을 사용자 선호로 세지 않고, 보이는 입력을 무시하지 않으며, 분석 전 완료 결과가 없고 복합 비용 일부로 적합을 확정하지 않는지 단언한다.
28. **공개와 관리 콘텐츠 소유권을 테스트한다** — 공개 DOM에는 내부 ID·원문 출처 원장·규제 채널 상수·감사 로그를 복제하지 않는다. 대신 사용자 행동을 막거나 바꾸는 제한과 해석 가능한 이유는 남긴다. 관리자는 그 요약을 재현하는 출처·검증 이벤트·판정 기록을 가진다.
29. **색·가시성 측정기는 새 CSS 문법과 보이는 텍스트를 구분한다** — computed color가 CSS Color 4 `color(srgb r g b / a)`이면 0~1 채널을 0~255으로 변환하고, 반투명 배경은 자식에서 부모 방향으로 올바르게 합성한다. `Range.selectNodeContents()`로 sr-only·`aria-hidden` 장식·빈 overlay까지 합산하지 말고 실제 보이는 text node의 rect만 잰다. 두 경우 모두 UI를 고치기 전에 RED fixture → 파서 수정 → 같은 fixture GREEN 순서로 검사기 자체를 검증한다.
30. **native dialog의 위치를 UA 스타일에 맡기지 마라** — 전역 reset은 dialog의 기본 margin을 지울 수 있다. open 상태에서 `(left + width/2, top + height/2)`와 viewport 중심의 오차, 화면 안 rect, `html` overflow, Escape 뒤 복귀를 390·1440·2560에서 측정한다. 내용이 길면 dialog 또는 내부 본문 하나만 scroll owner여야 한다.
31. **초광폭 히어로는 비율 계약을 잰다** — 2560 같은 실제 wide viewport에서 hero 폭/정렬, copy 최소 폭, media 최대 폭, 다음 정보 레일 폭을 함께 단언한다. media만 넓고 카피가 가늘어지는 상태를 `overflow=0`으로 통과시키지 않는다. 절대 수치는 페이지별 토큰이 소유하며, 풀블리드 편집 사진 예외는 이유를 기록한다.
32. **캐시 무효화도 사용자 화면에서 단언한다** — static CSS/JS를 덮어쓴 뒤 HTML만 새 URL이면 브라우저는 이전 stylesheet를 계속 쓴다. stylesheet와 `@import` 의 버전을 같이 바꾸고, 공개 URL에서 실제 href·새 토큰/핵심 computed value를 검사한다. HTTP 200과 새 HTML만으로 배포 성공을 선언하지 않는다.
33. **썸네일은 안정한 프레임만 저장한다** — 캡처 전 local HTTP 경로에서 `document.fonts.ready`·모든 이미지 `decode()`·bounded settle을 기다리고, `prefers-reduced-motion: reduce`를 적용한다. 그 뒤 video/audio·CSS/WAAPI·rAF canvas를 멈추고, local 4xx·pageerror·깨진 이미지가 하나라도 있으면 실패시킨다. 원리는 `animation:none`으로 초기 상태를 재현하는 것이 아니라 **완성된 한 프레임을 고정**하는 것이다. 카드·OG·핵심 상태가 같은 계약을 공유하는지 파일 치수·0바이트·시각 검수까지 단언한다.
34. **초광폭 text-media split은 가족별로 잰다** — hero가 통과했다고 과정·추천·가맹 분할이 통과한 것이 아니다. 서로 다른 DOM/여백 규칙을 가진 각 split root에서 1920·2560의 rail, copy/media rect, heading의 실제 내용 폭과 줄 수를 hard fail로 기록한다. wide override는 양쪽 logical padding을 명시하고, 상위 container의 `max-width`가 specificity 때문에 named stage를 다시 줄이지 않는지 단언한다. grid 바깥 rect가 정상이어도 text content가 한 글자 열로 수축하면 실패다.
## 리포트 양식
```
게이트: 전 계층 통과 — 배포 가능 · 총 Ns
| 계층 | 결과 | 항목 | 소요 |
게이트: 기능·접근성 자동 검사 / 시각 평가를 별도 보고 · 총 Ns
| 범주 | 결과 | 항목 | 소요 | 근거 |
```
실패 상세는 원문 테일 포함. 리포트 파일(gate-report.md / verify-report.md)은 커밋 대상이다.
실패 상세는 원문 테일 포함. 시각 평가는 같은 viewport·상태·자산을 전후로 보존해 위계·브랜드·타입·구도·이미지·리듬을 기록한다. 사용자 성과의 인과나 취향 우승을 검증 없이 주장하지 않는다. 리포트 파일(gate-report.md / verify-report.md)은 커밋 대상이다.

View file

@ -0,0 +1,67 @@
# design foundations — 근거를 결정으로 바꾸는 법
교과서 요약집이 아니다. 각 원칙은 실제 과업에서 무엇을 확인할지 정하는 도구다. 출처의 상세 범위는 [evidence ledger](evidence-ledger.md)를 따른다.
## 1. 먼저 성공 과업을 한 문장으로 적는다
`누가 / 어떤 맥락에서 / 무엇을 / 어떤 위험 없이 끝내야 하는가`를 쓴다. 시각 방향은 그 뒤에 고른다. 첫 화면의 주장은 이름, 가치, 다음 행동을 모두 억지로 넣는 공식이 아니라 이 과업의 다음 결정을 충분히 설명하는지로 판정한다.
| 원칙 | 지지 주장 | 적용 조건 | 잘못된 일반화 | 구체 검증 | 예시 결정 |
|---|---|---|---|---|---|
| 관련성 | 불필요한 정보는 관련 정보의 가시성과 경쟁한다 [NNG-HEUR] | 첫 선택과 오류 복구가 중요한 화면 | 요소 수가 적을수록 좋다 | 대표 과업에서 필요한 정보가 같은 화면에 있는지 본다 | 가입 전 가격 세부는 요약+상세 링크로 분리 |
| 위계 | 크기·명도·위치·여백은 함께 작동해 시선을 만든다 [NNG-VISUAL] | 스캔이 필요한 랜딩·목록 | h1:h2 비율 하나로 판정 가능 | 회색조 캡처를 짧게 보고 제목·주요 행동·본문의 읽는 순서를 기록한다 | CTA만 색 대비, 섹션 제목은 공간 대비 |
| 근접성·유사성·공통 영역 | 가까움, 공통 외곽, 유사한 표식은 관계를 읽히게 할 수 있다 [GESTALT] | 반복 모듈과 복잡한 정보 | 모든 영역이 12열 중앙 정렬이거나 카드여야 한다 | 카드·표·CTA의 묶음·분리·선택 상태를 회색조와 실제 과업에서 본다 | 사양과 가격은 한 group, 관련 도움말은 다음 group |
| 정렬과 그리드 | 일관된 column·gutter·breakpoint는 제품 UI의 정렬 기준을 제공한다 [IBM-GRID] | 여러 화면의 반복 표면 | Carbon 격자나 특정 열 수가 보편 정답이다 | 콘텐츠 폭, 정렬선, 작은 폭의 재배치를 검증한다 | 읽는 surface와 비교 surface에 다른 폭 토큰을 둔다 |
| 직접 조작 | 멀수록 작을수록 조준이 느리고 오류가 늘 수 있다 [FITTS] | 빈번한 포인터 행동 | 모든 버튼을 크게 만들면 해결된다 | 실제 hitbox, 거리, 인접 오탭을 관찰한다 | 아이콘만 누르게 하지 않고 라벨까지 클릭 가능하게 |
| 단서와 선택 설계 | 행동 단서는 지각 가능해야 하고, 선택 비용은 선택지 수만 아니라 구분·라벨·목표에 좌우된다 [NORMAN-SIGN, HICK, KRUG] | 내비·필터·설정 | 7±2로 메뉴 수를 제한한다 | 사용자가 목표 항목을 찾는 시간·오류를 본다 | 9개 항목을 사용자 과업별 3개 묶음으로 재분류 |
| 기억보다 인지 | 화면의 상태·다음 행동·되돌림을 드러내면 기억 부담을 낮춘다 [NNG-HEUR, MILLER] | 다단계·파괴적 행동 | 모든 것을 항상 보여야 한다 | 뒤로가기·오류·재진입에서 상태가 복구되는지 시험한다 | 업로드 후 파일명·교체·삭제를 같은 표면에 노출 |
| 인지 부하 | 과업에 필요 없는 기억·변환·탐색 단계를 줄이면 문제 해결 부담을 낮출 수 있다 [COGNITIVE-LOAD] | 낯선 절차·설정·비교 | 모든 정보를 숨기거나 단계를 줄이면 더 쉽다 | 첫 사용자 과업의 오류·되돌림·완료 시간을 비교한다 | 설정 값을 군집화하고 선택 결과를 바로 옆에 보인다 |
## 2. 접근성은 시각 스타일과 독립된 통과선이다
WCAG는 A·AA·AAA의 테스트 가능한 성공 기준이다. 제품의 목표 수준과 적용 법규·계약을 먼저 정하고, 장식적 선택이 그 기준을 넘어서는지 확인한다. AA는 일반적인 목표가 될 수 있지만, 이것만으로 모두에게 적합하다는 보증은 아니다 [WCAG-22].
- 텍스트 대비, 200% 확대, 320 CSS px reflow, 사용자 text-spacing, 포인터 target, 자동 움직임을 핵심 화면·상태별로 시험한다 [WCAG-READ, WCAG-TARGET, WCAG-MOTION].
- 모션 감소 선호에서는 움직임을 줄이되, 로딩·선택·오류 같은 상태 신호를 다른 방식으로 남긴다 [W3C-REDUCED].
- 스크린샷은 이 기준을 증명하지 못한다. 키보드·줌·사용자 스타일·보조기술 테스트를 별도 기록한다.
## 3. 성능 수치의 이름을 바르게 쓴다
Core Web Vitals 적합성은 실제 사용자의 field data에서 모바일·데스크톱별 75th percentile로 판단한다. 로컬 Lighthouse/DevTools/테스트 장비 수치는 개발 회귀를 잡는 lab proxy이며 field 판정이 아니다 [CWV, CWV-METHOD].
| 주장 | 필요한 증거 |
|---|---|
| “개발 빌드가 이전보다 가벼워졌다” | 같은 환경·시나리오의 bundle/전송량 또는 lab 결과 |
| “CWV good” | RUM 또는 CrUX의 LCP·INP·CLS p75, 기간·트래픽 분리 |
| “사용자가 더 빨리 끝낸다” | 실제 과업 시간·성공률·오류/중단의 전후 비교 |
| “이 디자인이 더 아름답다” | 동일 브리프에서의 렌더 비교와 근거 있는 리뷰; 자동 점수로 단정하지 않음 |
## 4. 비교는 같은 조건에서 한다
브랜드 고유성, 시각 위계, 구도·리듬, 이미지 품질은 실제 렌더에서 비교한다. 평가자는 같은 브리프·폭·상태·콘텐츠에서 다음을 적는다: 무엇이 첫 번째로 읽히는가, 어떤 근거가 이 브랜드에만 맞는가, 반복이 정보 구조를 드러내는가, 이미지의 크롭·해상도·대체 텍스트가 목적을 지지하는가. 심미성 자동점수, 상 수상, 단일 클릭 수로 전환 인과를 주장하지 않는다 [AWWWARDS, WEBBY].
## 5. 필요할 때: 평가의 네 증거선을 분리한다
예쁘다는 평가와 실제 과업 결과가 엇갈리거나, 변경이 개선 효과를 냈다고 주장하려 할 때 이 절을 연다. 평가 종류가 답하는 질문은 다르다. 첫인상·취향, 실제 과업 행동, 접근성 적합성, 전문가의 시각 검토를 한 PASS로 합치지 않는다. 보기 좋은 표면이 사용성 평가를 좋게 느끼게 할 수 있으므로, 호감과 과업 결과도 따로 적는다 [NNG-AESTHETIC].
| 증거선 | 답하는 질문 | 최소 기록 | 다른 증거로 대체할 수 없는 것 |
|---|---|---|---|
| 첫인상·취향 | 무엇이 먼저 읽히고 어떤 인상을 주는가 | 같은 렌더 조건, 관찰, 해석 | 실제 과업 성공·접근성 적합성 |
| 실제 과업 행동 | 사람이 목표를 달성하며 어디에서 멈추거나 오류를 내는가 | 현실적인 중립 과업, 행동·발화·막힌 지점 | 표준 적합성·전문가 미감 판정 |
| 접근성 적합성 | 정한 WCAG 수준과 제품 계약을 충족하는가 | 기준, 상태, 키보드·확대·보조기술 결과 | 사용자 경험 전체 |
| 전문가 시각 검토 | 위계·타입·구도·이미지·리듬이 브리프와 맞는가 | 같은 조건의 전후 렌더, 관찰, 변경 근거 | 실제 사용자 연구 |
불확실하거나 영향이 큰 과업이면 실제 또는 예상 사용자가 현실적인 목표를 중립적 과제로 수행하게 하고, 답을 암시하지 않은 채 행동을 관찰한다 [GOV-TEST]. 실제 사용자 검증이 없으면 그 한계를 결과에 적고, 과업 효과를 확정적으로 주장하지 않는다. 사용자 평가는 표준 적합성 평가를 보완하며 대체하지 않는다 [W3C-USER-EVAL]. 일반적인 구현에 사용자 모집을 필수 게이트로 만들지 않는다. 에이전트 walkthrough는 정해진 시나리오의 회귀 점검일 뿐 실제 사용자 연구가 아니다.
수정은 `관찰 → 원인 가설 → 한 가지 변경 → 같은 조건의 재비교`로 남긴다. 필요하면 여러 구조적 대안을 탐색하고 작은 범위에서 시험해 버리거나 다듬는다는 Double Diamond의 프레임을 참고한다 [DC-DIAMOND]. 이 절차는 출처가 보장한 단일 방법이 아니라, 위 근거를 스킬 작업에 맞게 합성한 절차다. 고정된 관찰 시간·참여자 수·성공률 하나를 보편 통과선으로 쓰지 않는다.
## 6. 결정 기록 템플릿
```markdown
- 출처 ID: WCAG-TARGET
- 지지 주장: 포인터 대상의 AA 최소 크기는 24×24 CSS px이다.
- 적용 조건: 모바일에서 빈번한 아이콘 행동.
- 잘못된 일반화: 모든 컨트롤에 44px가 법적 필수라는 말.
- 구체 검증: 390px에서 실제 클릭 영역과 인접 대상 간격을 측정한다.
- 예시 결정: 삭제 아이콘의 보이는 glyph는 16px, 클릭 영역은 28px로 둔다.
```

View file

@ -0,0 +1,58 @@
# evidence ledger — 판단의 출처와 한계
조사일: 2026-09-12. 이 목록은 읽을거리 순위가 아니라, 디자인 결정에 쓸 수 있는 근거의 범위다. `normative`는 적합성 조건, `research`는 특정 인간-컴퓨터 상호작용의 경향, `practice`는 설계 절차, `case`는 한 사례 또는 심사 체계, `forecast`는 전망, `bibliography`는 서지 확인만 뜻한다. 사례·전망·서지는 효과나 인과를 증명하지 않는다.
| ID | 출처·날짜 | 종류 | 실제로 지지하는 주장 | 적용 조건 / 잘못된 일반화 | 구체 검증 |
|---|---|---|---|---|---|
| WCAG-22 | [WCAG 2.2](https://www.w3.org/TR/WCAG22/) · 2023-10-05 | normative | A·AA·AAA는 서로 다른 적합성 수준이며 AAA만으로 모두에게 접근 가능해지지는 않는다 | 법·계약 목표는 별도로 확인한다. AA를 자동 법적 보장이라 부르지 않는다 | 요구 수준과 전 과정의 SC를 매핑한다 |
| WCAG-READ | [contrast](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html) · [resize](https://www.w3.org/WAI/WCAG22/Understanding/resize-text.html) · [reflow](https://www.w3.org/WAI/WCAG22/Understanding/reflow.html) · [text spacing](https://www.w3.org/WAI/WCAG22/Understanding/text-spacing.html) · 2023 | normative | 일반 텍스트 AA 4.5:1/큰 텍스트 AA 3:1, 200% 확대, 320 CSS px 재배치, 사용자 지정 간격에서의 내용·기능 보존을 각각 요구한다 | 로고·비활성·2차원 배치 같은 예외가 있으며, 이는 단일 ‘좋은 조판’ 수치를 만들지 않는다 | 실제 상태에서 대비, 200% 확대, 320px/400%, W3C text-spacing 주입을 함께 검사한다 |
| WCAG-TARGET | [SC 2.5.8](https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html) · 2023 | normative | 포인터 입력 대상은 최소 24×24 CSS px(예외 포함)인 AA 기준이 있다 | 44px를 모든 대상의 WCAG AA 요건으로 말하지 않는다 | 실제 clickable box와 인접 대상 간격을 측정한다 |
| WCAG-MOTION | [SC 2.2.2](https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide.html) · 2023 | normative | 자동으로 시작해 5초 넘게 병행되는 움직임은 멈춤·숨김·일시정지 제어가 필요할 수 있다 | 모든 모션 금지가 아니다 | 자동 콘텐츠마다 제어와 키보드 접근을 시험한다 |
| W3C-REDUCED | [Media Queries 5](https://www.w3.org/TR/mediaqueries-5/#prefers-reduced-motion) · 확인 2026-09-12 | practice | CSS Media Queries 명세는 `prefers-reduced-motion` 질의를 정의한다 | WCAG 성공 기준이나 모든 모션 정책의 적합성 선언이 아니다 | 시스템 설정에서 핵심 상태 피드백이 남는지 확인한다 |
| W3C-FONT | [CSS Fonts 4](https://www.w3.org/TR/css-fonts-4/#font-display-desc) · 확인 2026-09-12 | practice | CSS Fonts 명세는 `font-display`의 로딩 기간 표시 전략을 정의한다 | WCAG 적합성 요건이 아니며, `swap`이 항상 최선이거나 CLS를 반드시 만든다고 단정하지 않는다 | 느린 네트워크에서 FOUT/FOIT·레이아웃을 캡처한다 |
| W3C-SIZE | [CSS Fonts 5](https://www.w3.org/TR/css-fonts-5/#size-adjust-desc) · 확인 2026-09-12 | practice | CSS Fonts Level 5 명세는 `size-adjust` descriptor를 정의한다 | 문서 상태·브라우저 지원을 별도 확인하며, 임의 백분율을 보편 처방으로 복사하지 않는다 | 대상 문자열·실제 폴백으로 줄바꿈과 높이를 비교한다 |
| NNG-HEUR | [10 usability heuristics](https://www.nngroup.com/articles/ten-usability-heuristics/) · 마지막 검토 2024-01-30 | practice | 최소성은 관련 없는 정보가 관련 정보의 가시성을 경쟁시킨다는 휴리스틱이다 | ‘미니멀=요소 삭제’나 전환 효과의 증명이 아니다 | 핵심 과업별 정보·피드백·복구 가능성을 리뷰한다 |
| NNG-VISUAL | [Principles of visual design](https://www.nngroup.com/articles/principles-visual-design/) · 확인 2026-09-12 | practice | scale, visual hierarchy, balance, contrast, Gestalt principles를 시각 디자인의 검토 차원으로 설명한다 | 특정 크기 비율·색 수·자동 심미성 점수를 처방하지 않는다 | 회색조와 실제 콘텐츠에서 우선순위·묶음·대비를 비교한다 |
| NORMAN-SIGN | [Signifiers, not affordances](https://jnd.org/signifiers-not-affordances/) · 2008 | practice | 사용자가 어디서 어떻게 행동할지 알게 하는 지각 가능한 단서는 signifier로 설명할 수 있다 | 시각적으로 버튼처럼 보이면 이해·접근성이 자동 보장된다는 뜻이 아니다 | 첫 사용자가 라벨·상태·피드백으로 행동을 예측하는지 관찰한다 |
| KRUG | [Don’t Make Me Think Revisited sample](https://sensible.com/downloads/DMMT-Revisited-sample-chapter.pdf) · 확인 2026-09-12 | bibliography | 공개 샘플은 Krug의 저작과 ‘명확한 선택지’라는 교육적 관점을 확인하는 범위다 | 메뉴 수 고정 규칙이나 실험적 성과 증거가 아니다 | 애매한 라벨을 대표 사용자의 과업 탐색으로 시험한다 |
| FITTS | [Fitts’s law](https://doi.org/10.1037/h0055392) · 1954 | research | 목표까지 거리와 이동축의 폭은 조준 시간에 관계한다 | 메뉴 개수 공식이나 모바일 타깃 크기 법규가 아니다 | 자주 쓰는 행동의 실제 거리·hitbox·오입력을 관찰한다 |
| HICK | [Hick](https://doi.org/10.1080/17470215208416600) · 1952 | research | 선택 신호의 정보량과 반응 시간 관계를 실험했다 | ‘7개 이상 메뉴 금지’로 변환하지 않는다 | 사용자 목표별 선택지의 분류·라벨·성공 시간을 테스트한다 |
| MILLER | [Miller](https://doi.org/10.1037/h0043158) · 1956 | research | 단기 기억의 절대적 한계가 아니라 chunking과 과제 맥락을 논의했다 | 7±2를 내비게이션 항목 수 규칙으로 쓰지 않는다 | 회상 대신 과업 완료·오류·도움 요청을 관찰한다 |
| GESTALT | [Wagemans et al.](https://doi.org/10.1037/a0029333) · 2012 | research | 근접성·유사성·공통 영역 같은 grouping 원리와 figure-ground를 현대적으로 검토한다 | 정렬·카드·색 하나만으로 이해·전환이 보장된다는 뜻이 아니다 | 관련 항목의 묶음과 분리, 선택 상태를 회색조·키보드·실제 과업에서 확인한다 |
| COGNITIVE-LOAD | [Sweller](https://onlinelibrary.wiley.com/doi/10.1207/s15516709cog1202_4) · 1988 | research | 문제 해결에서 작업 기억과 문제 구조의 상호작용을 다룬다 | 정보량을 무조건 줄이거나 단일 요소 수 규칙으로 환원하지 않는다 | 처음 하는 과업에서 단계·용어·기억해야 할 상태를 줄이고 오류·완료 시간을 관찰한다 |
| AWWWARDS | [Evaluation system](https://www.awwwards.com/about-evaluation/) · 확인 2026-09-12 | case | 해당 심사는 Design 40 / Usability 30 / Creativity 20 / Content 10으로 채점한다 | 배점 비율이 두 요소의 효과 크기나 사업 성과를 뜻하지 않는다 | 제출 목적일 때 네 차원을 별도 체크한다 |
| WEBBY | [Judging criteria](https://www.webbyawards.com/judging-criteria/) · 확인 2026-09-12 | case | Webby는 콘텐츠·구조/내비게이션·시각 디자인·기능성·상호작용·전반 경험을 심사한다 | 수상은 특정 패턴의 보편적 효율 증거가 아니다 | 사례를 해체하되 과업·접근성·성능을 별도 검증한다 |
| DROPBOX | [Dropbox brand](https://brand.dropbox.com/) · 확인 2026-09-12 | case | 브랜드 사이트가 공개한 자산·원칙의 사례다 | 브랜드의 자체 설명을 사용자 효과로 해석하지 않는다 | 사용할 요소를 렌더·접근성·브리프 적합성으로 다시 판정한다 |
| AREA17 | [AREA 17 × Pentagram](https://area17.com/ideas/area-17-pentagram) · 확인 2026-09-12 | case | 제작사가 프로젝트 접근을 설명하는 사례다 | 제작사 주장은 독립 성과 검증이 아니다 | 구조와 시스템 선택만 참고하고 결과는 직접 비교한다 |
| UNSEEN | [Unseen 2025 wrapped](https://2025.unseen.co/) · 확인 2026-09-12 | case | 시간·문화 맥락이 강한 실험적 웹 표면의 사례다 | 사이트 자체만으로 사용성·전환 효과를 주장하지 않는다 | 모션 off·키보드·모바일에서 핵심 메시지를 검증한다 |
| CSSDA-UNSEEN | [CSS Design Awards listing](https://www.cssdesignawards.com/sites/unseen-studio-2025-wrapped/48957/) · 2026-03-09 | case | CSSDA가 해당 작품을 WOTD로 기록한 사례다 | 단일 수상을 스타일 효과의 일반 증거로 쓰지 않는다 | 작품의 제약과 대상이 현재 브리프와 맞는지 비교한다 |
| CWV | [Web Vitals](https://web.dev/articles/vitals) · 2024-10-31 | practice | CWV의 good 기준은 LCP ≤2.5s, INP ≤200ms, CLS ≤0.1이며 field 75th percentile로 판정한다 | Lighthouse 한 번이나 개발 로컬 측정을 field 적합성이라 부르지 않는다 | RUM/CrUX와 모바일·데스크톱 p75를 기록하고, 로컬은 회귀 proxy로 표기한다 |
| CWV-METHOD | [Threshold methodology](https://web.dev/articles/defining-core-web-vitals-thresholds) · 2025-05-07 | practice | CWV 임계값은 단일 인간 지각값이 아니라 연구·실사용 데이터·달성 가능성을 함께 고려한다 | 한 임계값을 모든 과업의 만족도 원인으로 단정하지 않는다 | 성능 수치와 과업 관찰을 분리 보고한다 |
| IBM-GRID | [Carbon 2x Grid](https://carbondesignsystem.com/elements/2x-grid/usage/) · 확인 2026-09-12 | practice | 제품 UI에서 그리드·gutter·breakpoint를 일관되게 적용하는 방법의 예다 | 모든 브랜드가 Carbon 격자를 써야 한다는 규범이 아니다 | 콘텐츠 폭, 정렬선, 작은 폭의 재배치를 검증한다 |
| IBM-DATA | [IBM data visualization basics](https://www.ibm.com/design/language/data-visualization/design/basics/) · 확인 2026-09-12 | practice | 데이터 표현은 목적·비교·맥락에 맞춰 선택해야 한다 | 장식적 차트나 색만으로 의미가 생긴다고 보지 않는다 | 질문-표현-축/라벨-대체 접근을 연결해 검토한다 |
| IFD-TREND | [iF Design Trend Report 2025](https://ifdesign.com/en/if-magazine/newsroom/if-design-trend-report-2025) · 2025 | forecast | 기관의 거시 디자인 전망이다 | 웹 UI의 검증된 성과나 필수 스타일 목록이 아니다 | 브리프·사용자 조사와 맞을 때만 가설로 쓴다 |
| DC-DIAMOND | [Design Council Double Diamond](https://www.designcouncil.org.uk/resources/the-double-diamond/) · 확인 2026-09-12 | practice | 발견·정의·개발·전달을 오가며 다양한 답을 탐색하고 작은 시험으로 버리거나 개선하는 프레임을 소개한다 | 고정 단계 수나 모든 프로젝트의 의무 절차로 쓰지 않는다 | 불확실성이 큰 결정의 대안·시험·폐기 근거를 기록한다 |
| VA-MOFFAT | [Curtis Moffat: working methods](https://www.vam.ac.uk/articles/curtis-moffat-working-methods) · 2025-09-24 | case | contact sheet, 크롭, 비중심 배치가 작가 작업에서 어떻게 쓰였는지 보여 주는 사례다 | 웹 성과나 보편 구도 규칙의 증거가 아니다 | 이미지 선택·크롭의 목적과 전후 렌더를 비교한다 |
| CH-DESCRIPTION | [Cooper Hewitt image-description guidelines](https://www.cooperhewitt.org/cooper-hewitt-guidelines-for-image-description/) · 확인 2026-09-12 | practice | 중요한 내용을 먼저 두고 공간 순서와 맥락에 맞춰 이미지를 설명하는 방법을 제시한다 | 박물관의 단어 수를 보편 alt 길이 규칙으로 바꾸지 않는다 | 정보성 이미지에서 목적·핵심 내용·대체 설명을 함께 검토한다 |
| LUPTON-POSTERS | [How Posters Work](https://www.cooperhewitt.org/publications/how-posters-work/) · 확인 2026-09-12 | bibliography | Lupton의 책과 포스터를 분석 대상으로 삼는 출판 맥락을 확인하는 출발점이다 | 이 항목은 책 본문을 정독했거나 특정 구성 규칙을 인용했다는 뜻이 아니다 | 판본과 페이지를 확보한 뒤 적용할 개념과 한계를 기록한다 |
| GOV-IMAGES | [GOV.UK Design System: images](https://design-system.service.gov.uk/styles/images/) · 확인 2026-09-12 | practice | 정부 서비스에서 목적에 맞는 사진·도식·대체 설명을 선택하는 지침이다 | 상업 브랜드에 무드 이미지를 금지하는 일반 규칙으로 확대하지 않는다 | 이미지가 과업 정보·맥락·접근성에 기여하는지 확인한다 |
| GOV-TEST | [GOV.UK moderated usability testing](https://www.gov.uk/service-manual/user-research/using-moderated-usability-testing) · 확인 2026-09-12 | practice | 현실적인 중립 과업으로 실제 또는 예상 사용자의 행동과 어려움을 관찰하는 방법을 설명한다 | 특정 참여자 수·시간·성공률을 보편 통과선으로 만들지 않는다 | 과업 문구, 사용 환경, 행동·오류·중단 관찰을 분리 기록한다 |
| NNG-AESTHETIC | [Aesthetic-usability effect](https://www.nngroup.com/articles/aesthetic-usability-effect/) · 2024-02-03, 검토 2026-09-01 | practice | 시각적 호감이 사용성에 대한 지각을 높일 수 있어 실제 과업 결과와 구분해 볼 필요를 해설한다 | 원 논문 전문을 읽었거나 호감이 실제 사용성을 보장한다고 주장하지 않는다 | 취향·첫인상과 과업 행동·오류를 별도 결과로 남긴다 |
| W3C-USER-EVAL | [Involving users in evaluating web accessibility](https://www.w3.org/WAI/test-evaluate/involving-users/) · 2024-04-30 | practice | 사용자 평가는 표준 기반 적합성 평가를 보완하며 둘을 함께 써야 한다고 설명한다 | 적은 표본이나 한 번의 평가를 모든 사용자에게 일반화하지 않는다 | WCAG 평가와 사용자 관찰의 질문·결과를 분리해 기록한다 |
| SWISS-NB | [The International Style 1950–1970](https://www.nb.admin.ch/en/the-international-style-1950-1970) · 확인 2026-09-12 | bibliography | 스위스/국제 타이포그래피의 역사·소장 맥락을 소개한다 | 역사 양식을 기능·전환의 보편 처방으로 바꾸지 않는다 | 그리드·타입 선택의 문화적 근거만 명시한다 |
| BRINGHURST | [The Elements of Typographic Style](https://hartleyandmarks.com/Hartley-%26-Marks-Publishers) · 확인 2026-09-12 | bibliography | Bringhurst 4.0의 서지·출판 정보를 확인하는 출발점이다 | 이 ledger는 책 본문을 읽거나 특정 규칙을 인용했다는 뜻이 아니다 | 판본을 확보한 뒤 페이지 단위로 별도 기록한다 |
| LUPTON | [Thinking with Type, 3rd ed.](https://www.chroniclebooks.com/products/thinking-with-type-1) · 2024 | bibliography | Lupton 저작의 3판 출판 정보를 확인하는 출발점이다 | 이 ledger는 본문 요약이나 보편 수치 근거가 아니다 | 판본을 확보한 뒤 개념·예시 페이지를 별도 기록한다 |
| MULLER-BROCKMANN | [Grid systems in graphic design](https://niggli.ch/en/products/rastersysteme-fur-die-visuelle-gestaltung) · 확인 2026-09-12 | bibliography | Müller-Brockmann 저작의 출판사 서지 정보를 확인하는 출발점이다 | 그리드를 모든 과업의 정답으로 만들지 않는다 | 판본을 확보한 뒤 grid 선택의 목적·예외·검증을 분리 기록한다 |
## 사용할 때
결정마다 `출처 ID / 지지 주장 / 적용 조건 / 잘못된 일반화 / 구체 검증 / 예시 결정`을 남긴다. 예: `WCAG-TARGET / 24×24 AA 최소 / 포인터 대상 / 44px 의무라고 확대하지 않음 / 실제 버튼 box 측정 / 작은 아이콘을 24px 이상 hit area로 확장`.
## 교과서 입문·정독 범위
다음은 **서지 확인된 읽기 경로**다. 이 목록은 본문을 읽었다거나 특정 페이지의 규칙을 채택했다는 주장이 아니다. 입문에서는 편집·그리드·글자 형태의 어휘를 얻고, 실제 프로젝트에 인용할 때는 확보한 판본과 페이지를 다시 기록한다.
1. Bringhurst, *The Elements of Typographic Style* — 조판 어휘와 본문 리듬의 정독 출발점 [BRINGHURST].
2. Lupton, *Thinking with Type*, 3rd ed. — 타입을 구성·위계·이미지와 연결하는 입문 [LUPTON].
3. Müller-Brockmann, *Grid Systems in Graphic Design* — grid의 역사적·실무적 어휘 [MULLER-BROCKMANN].
4. Meggs 계열의 그래픽 디자인 역사 저작은 별도 판본 서지와 페이지 근거를 확보한 뒤 역사 맥락용으로 추가한다. 이 ledger에서는 미확인 판본의 내용을 인용하지 않는다.

View file

@ -6,7 +6,7 @@
## 1. 브리프 → 갤러리 라우팅
`R1`은 구조, `R2`는 톤, `R3`는 디테일을 가져올 소스다. R2는 **반드시 R1과 다른 업종**에서 고른다.
`R1`은 구조, `R2`는 톤, `R3`는 디테일을 검토할 슬롯이다. R2는 다른 업종 또는 매체를 우선 검토하되, 같은 업종이 브리프·시장·제작 조건에 맞으면 그 이유를 기록한다.
> **두 행에 동시에 걸리면 청중을 기준으로 고른다.** "개발자용 API 모니터링 SaaS"는
> `SaaS · B2B` 와 `개발자 도구` 양쪽에 해당한다. 보는 사람이 엔지니어면 개발자 도구 행이다.
@ -49,10 +49,7 @@
## 2. 경고 — 갤러리마다 편향이 있다
**Awwwards 구조를 SaaS에 이식하지 마라.**
2026 Q1 SOTD 47건의 업종 분포는 럭셔리 패션 28% · 자동차 22% · 건축 18% · 문화기관 14%, **테크/SaaS는 5% 미만**이다.
수상작의 전면 3D·풀스크린 인트로·스크롤 서사는 "제품을 팔지 않는 사이트"의 문법이다.
Awwwards는 **타이포 완성도와 야심의 상한선**으로만 쓰고, 섹션 구조는 Land-book·Refero에서 가져온다.
**Awwwards 사례를 제품 구조의 보편 답으로 쓰지 마라.** 어워드 사례는 심사 맥락·예산·제작 환경이 다르다. 전면 3D·풀스크린 인트로·스크롤 서사는 목적·성능 예산·사용 과업에 맞을 때만 선택한다. 각 사례에서 가져올 원칙과 적용 조건을 기록하고, 정보 구조는 목표 사용자·과업·프로젝트 계약으로 다시 검증한다.
그 밖의 편향:
@ -96,7 +93,7 @@ Awwwards는 **타이포 완성도와 야심의 상한선**으로만 쓰고, 섹
라우팅 표에서 못 찾았을 때만 본다.
**종합 어워드**
- Awwwards https://www.awwwards.com — SOTD 아카이브. 상한선용
- Awwwards https://www.awwwards.com — SOTD 아카이브. 심사 맥락과 적용 조건을 함께 검토
- The FWA https://thefwa.com — 인터랙티브·기술 실험
- Godly https://godly.website — 주당 3~5개만 등록. 신호 대 잡음비 최고

View file

@ -8,22 +8,28 @@
---
## 1. 조달 경로 — 위에서부터 확인한다
## 1. 조달 경로 — 기본 탐색 순서로 확인한다
| 순위 | 경로 | 언제 |
|---|---|---|
| 1 | **사용자가 준 사진** | 있으면 무조건 이것. 실제 작업물을 이길 생성물은 없다 |
| 2 | **생성**(codex `image_gen`) | 콘셉트 확인용, 톤이 정확히 지정된 추상·분위기 컷 |
| 1 | **사용자가 준 사진** | 자산 용도·품질·권리·진실성에 맞을 때. 실제 작업물은 그 근거가 필요한 화면에서 강하다 |
| 2 | **생성**(codex `image_gen`) | 필요한 슬롯에 콘셉트 확인용 또는 톤이 정확히 지정된 추상·분위기 컷이 맞을 때 |
| 3 | **스톡** | 라이선스가 명확한 곳만. 출처를 `design.md` 에 적는다 |
| 4 | 없이 간다 | 타이포·색·여백만으로 만든다. 약한 사진보다 낫다 |
**0단계에서 물어라**: "쓸 수 있는 사진이 있나요?"
없다고 하면 2번으로 가되, **생성물이라는 사실을 `design.md` 에 적는다.**
나중에 실제 사진으로 바꿀 때 레이아웃을 다시 짜지 않도록 비율을 미리 고정해 둔다.
이 순위는 기본 탐색 순서일 뿐이다. 자산 용도·품질·권리·진실성 판단보다 앞서지 않는다.
사용자가 이미 자산을 제공했거나 사용 가능한 사진이 없다고 명시했거나, `design.md`·저장소에서 확인할 수 있으면 다시 묻지 않는다. 기존 정보로 확인할 수 없고 자산 유무가 결정에 필요할 때만 **0단계에서 묻는다**: "쓸 수 있는 사진이 있나요?"
없다는 답만으로 생성 경로를 자동 선택하지 않는다. 슬롯별 자산 용도·품질·권리·진실성을 비교해 경로를 고르고, 생성물을 쓰면 **생성물이라는 사실을 `design.md` 에 적는다.**
나중에 실제 사진으로 바꿀 가능성이 있으면 레이아웃을 다시 짜지 않도록 비율을 미리 고정해 둔다. 구도·크롭·타이포 검토는 [art-direction.md](art-direction.md)를 따른다.
### 제품을 고르는 화면의 추가 원칙
사용자가 제품 자체를 비교·선택하는 카드라면 CSS 도형이나 레이어 쌓기로 제품을 대신 그리지 않는다. 실제·라이선스 사진이 없을 때만 비율을 정한 생성 사진을 쓰고, 사진과 같은 시각 그룹에 `생성 이미지`처럼 가까운 출처 라벨을 둔다. DOM 도식은 재료 비교, 과정 설명, 수치처럼 **그림보다 읽히는 정보**에만 쓴다.
---
## 2. codex 가 있으면 생성한다
## 2. 생성이 적합할 때 사용할 경로
설치 여부를 먼저 확인한다. 없으면 조용히 3번으로 내려간다.

View file

@ -25,7 +25,7 @@
**`main` 폭에 고정값을 쓰지 마라.** `--measure` 는 3단계에서 언어와 폰트에 맞춰 정한 값이다.
여기에 60rem 같은 숫자를 넣으면 본문이 measure 를 넘어가 이 문서의 통과 조건을 스스로 어긴다.
한글은 보통 라틴보다 좁다(35ch ≈ 561px vs 640px).
`ch`는 선택한 글꼴의 메트릭에 의존한다. 한글과 라틴의 실제 줄 길이는 글꼴·언어·내용으로 달라지므로, 예시 폭을 보편 수치로 쓰지 말고 대표 문단을 렌더해 확인한다.
이 한 번의 정의로 **본문 폭 · 넓은 블록 · 전체 폭**이 전부 정렬된다. 섹션마다 `max-width` 와 `padding` 을 다시 쓰면 반드시 어긋난다.
@ -39,7 +39,7 @@
### 벤토 그리드 주의
벤토(크기가 다른 카드들의 격자)는 **더 이상 차별화가 아니라 새 기본값**이다. 효과는 있지만(스크롤 깊이 증가) 표준화가 끝나서 "고민 안 했음"의 신호로 읽힌다. 쓰려면 최소한 다음 중 하나는 해라:
벤토(크기가 다른 카드들의 격자)는 정보를 묶거나 우선순위를 드러낼 때 쓸 수 있다. 카드 크기 변화만으로 고유성·스크롤 깊이·이탈을 예측할 수는 없다. 아래 장치는 선택지이며, 구조가 필요로 할 때만 쓴다:
- 카드 경계를 없애고 여백만으로 구획
- 그리드에서 한 칸을 의도적으로 비우기
- 카드 하나만 규칙을 깨고 밖으로 나가기
@ -50,10 +50,10 @@
사람은 페이지를 읽지 않고 **훑는다.** 훑는 경로를 설계하는 것이 레이아웃의 본체다.
1. **첫 화면에서 세 가지만** — 무엇인지 / 왜 좋은지 / 다음 행동. 넷 이상이면 아무것도 안 읽힌다
2. **위계는 크기가 아니라 대비로 만든다** — 크기·굵기·색·여백 중 **둘 이상을 동시에** 바꿔야 위계가 보인다. 크기만 조금 키우는 것은 위계가 아니다
1. 첫 화면이 대표 과업에 필요한 정체성·가치·다음 행동을 충분히 설명하는지 확인한다. 세 요소는 흔한 점검 틀이지 모든 화면의 최대 항목 수가 아니다
2. 위계는 크기·굵기·색·여백·위치의 조합으로 만든다. 어떤 두 변수를 반드시 바꿔야 한다는 공식보다, 회색조와 실제 콘텐츠에서 우선순위가 읽히는지를 본다
3. **시선을 한 번은 멈춰라** — 전부 같은 리듬으로 흐르면 아무것도 강조되지 않는다. 섹션 하나는 리듬을 깨야 한다
4. **스크롤은 보상이어야 한다** — 다음 화면에 무언가 새로운 것이 있어야 한다. 같은 카드 그리드가 세 번 반복되면 거기서 이탈한다
4. 스크롤 뒤에도 새 정보나 과업 진전이 있는지 확인한다. 반복 카드가 이탈을 만든다는 보편 증거는 없으므로, 실제 콘텐츠와 사용자 행동으로 판단한다
### 의미가 다르면 섹션 토폴로지도 달라야 한다
@ -75,10 +75,10 @@
한 액션 묶음의 **같은 행에 놓인 직접 자식**인 채운 버튼과 텍스트 링크는 `align-items:center`만으로 시각 중심이 맞지 않을 수 있다. 중첩된 아이콘·배지까지 한꺼번에 비교하거나, 모바일에서 세로로 쌓인 액션에 이 규칙을 적용하면 오탐이다.
- 두 액션 모두 `inline-flex; align-items:center; min-height:44px`의 실제 hitbox를 가진다
- 두 액션의 실제 hitbox와 정렬을 함께 측정한다. 44px은 편안한 시작값일 수 있지만, WCAG 2.2 AA의 포인터 target 최소 기준은 예외를 포함해 24×24 CSS px다 (`evidence-ledger.md`의 `WCAG-TARGET`)
- `getBoundingClientRect()`의 중심 y 차이가 브라우저 CSS 픽셀 기준 2px 이하여야 한다
- 텍스트 baseline이 아니라 클릭 가능한 박스 전체를 비교한다
- 보조 링크를 44px로 키우면서 빈 padding이 과해지면 액션을 세로로 쌓는다
- 보조 링크의 hitbox를 키운 결과 밀도가 나빠지면, 행동 중요도·오입력·레이아웃을 보고 세로 배치나 다른 구조를 검토한다
### 패널을 숨기면 그리드 트랙도 회수한다
@ -104,7 +104,7 @@
### 본문이 주인공이다
헤드라인은 눈에 띄기 쉽다. **본문이 읽히는지**가 실력이다.
- 한 줄 길이(`--measure`)를 반드시 적용해라. 전체 폭 본문은 읽을 수 없다
- 한 줄 길이(`--measure`)를 본문 역할에 적용하고, 실제 언어·글꼴·확대 상태에서 읽기 리듬을 검토한다. 전체 폭이 항상 실패인 것은 아니지만 긴 산문에 무심코 적용하지 않는다
- 문단 간격은 줄간격의 1.5배 이상. 들여쓰기와 문단 간격을 동시에 쓰지 마라
- 링크는 색만으로 구분하지 마라(밑줄 또는 다른 신호 병행)
@ -112,7 +112,7 @@
`references/antipatterns.md` 의 한글 조판 섹션을 읽어라. 요약:
- `word-break: keep-all` — 없으면 단어가 아무 데서나 잘린다
- line-height 1.6~1.8 (라틴보다 넉넉하게)
- 음수 자간 금지
- 자간은 글꼴·크기·스크립트별로 실제 렌더를 보고 조정한다. 한글 음수 자간은 기본 처방으로 쓰지 않으며, 적용했다면 작은 크기·확대·줄바꿈에서 검증한다
- 한 줄 25~40자
- 라틴 폰트를 폴백 스택 **앞**에 둔다 (숫자·영문이 한글 폰트로 렌더되면 조악해진다)
- **한글 폰트를 지정하지 않는 것 자체가 완성도 미달 신호다**
@ -131,31 +131,28 @@
.cards { grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); }
```
`auto-fit` + `minmax` 로 해결되는 것을 미디어쿼리로 만들지 마라. 미디어쿼리는 **레이아웃 구조 자체가 바뀔 때만** 쓴다.
`auto-fit` + `minmax`는 유동 반복 grid에 유용하다. 미디어쿼리와 container query는 구조·밀도·행동이 바뀌는 실제 실패 지점에서 선택한다.
### 모바일에서 먼저 확인해라
데스크톱에서 아름다운 것이 모바일에서 무너지는 것이 기본이고, 그 반대는 드물다. 특히:
- 큰 타이포는 모바일에서 반드시 줄여라 (`clamp` 의 최소값)
- 큰 타이포는 모바일에서 줄 수·가려짐·다음 행동의 가시성을 보고 크기·폭·문구·구도를 함께 조정한다. 단순 축소가 유일한 해법은 아니다
- 가로 스크롤이 생기는 요소를 찾아라 (`overflow-x: hidden` 으로 덮지 말고 원인을 고쳐라)
- 터치 타깃 44×44px 이상
- 터치 타깃은 과업 빈도·오입력 위험과 WCAG 2.2 AA의 24×24 CSS px 최소 기준(예외 포함)을 함께 검토한다
- 호버로만 접근되는 기능을 만들지 마라
---
## 4-b. 모바일 내비 — 햄버거를 기본값으로 삼지 마라
내비 항목 수는 0단계에서 이미 정해져 있다(= 섹션 수). 구현하다 발견하면 늦다.
내비 구조는 섹션 수가 아니라 대표 과업, 라벨 길이, 우선순위, locale, 화면 폭을 함께 보고 0단계에서 정한다. Hick의 연구를 항목 수 공식으로 쓰지 않는다.
| 항목 수 | 처리 | 비용 |
| 관찰한 조건 | 검토할 구조 | 검증 |
|---|---|---|
| **2~4개** | 한 줄 유지. 자간을 줄이고 `space-between` 으로 편다 | 0 |
| **5~6개** | 가로 스크롤 줄, 또는 하단 고정 바 | 낮음 |
| **7개 이상** | 여는 메뉴 | `<details>` 로 JS 0바이트 |
| 모든 우선 행동이 한 줄에서 읽히고 hitbox가 확보됨 | 직접 노출된 내비 | 작은 폭·키보드에서 라벨 접힘과 순서를 본다 |
| 라벨이 길거나 행동 우선순위가 갈림 | 일부 노출 + 추가 메뉴 또는 하단 행동 | 자주 쓰는 행동의 탐색 시간·오입력을 본다 |
| 문맥별 명령이 많음 | 컨텍스트 메뉴·검색·계층적 IA | 메뉴 열기/닫기·포커스·Esc·현재 위치를 시험한다 |
**넷인데 햄버거를 쓰면 탭 한 번을 공짜로 뺏는 것이다.** 반대로 여섯을 한 줄에
욱여넣으면 접힌다 — 실측에서 링크가 두 줄(top 46/77)에 걸렸고, 워드마크까지 3줄이 됐다.
### 2~4개 — 쌓는 것이 접히는 것보다 낫다
### 항목 수 대신 실제 라벨·과업으로 결정한다
워드마크와 내비를 한 줄에 두면 둘 다 접힌다. 좁은 화면에서는 **위아래로 쌓아라.**
@ -169,10 +166,9 @@
라벨 자간(`0.14em`)이 좁은 화면에서 폭을 크게 먹는다. **자간부터 줄여라** —
폰트 크기를 줄이는 것보다 읽기에 덜 해롭다.
### 7개 이상 — `<details>` 면 JS 가 필요 없다
### 단순 공개/접기에는 `<details>`를 검토한다
키보드 조작·`Esc`·포커스 이동이 브라우저 기본으로 온다.
직접 만든 토글은 그걸 전부 다시 구현해야 하고, 대개 빠뜨린다.
`<details>`는 간단한 공개/접기에는 유용하지만, 복잡한 메뉴의 포커스 이동·Esc·모달 동작을 자동으로 완성하지 않는다. 실제 키보드와 보조기술에서 목표 행동을 시험하고, 필요한 패턴을 선택한다.
```html
<details class="nav-mobile">
@ -215,6 +211,47 @@ new Set([...document.querySelectorAll('.nav a')]
---
## 4-d. 초광폭은 여백이 아니라 별도 레이아웃 상태다
1440px에서 멀쩡한 페이지가 1920·2560px에서 깨지는 방식은 반대다. 모바일처럼 넘치지는 않지만, 본문은 끝없이 늘어나고 고정 폭 헤드라인은 빈 여백에 밀려나며 표는 너무 좁아진다. **컨테이너 하나를 무조건 풀거나 조이지 마라.** 읽는 표면과 비교·명세 표면의 요구가 다르다.
```css
:root { --measure: 42rem; --container-wide: 74rem; }
.prose { max-width: var(--measure); }
.specification { width: min(100% - 2 * var(--pad-inline), var(--container-wide)); }
@media (min-width: 120rem) {
.specification { --container-wide: 86rem; } /* 데이터 표면만 확장 */
}
```
- 본문·설명·폼은 `--measure`를 지켜 행 길이와 label-입력 관계를 읽을 수 있게 둔다
- 표·도면·비교 작업대는 더 넓어도 되지만, 어떤 surface가 넓어지는지 이름으로 구분한다. 전역 `max-width` 해제는 해결이 아니다
- 1920과 2560에서 h1 폭·히어로 칼럼·table/flex 셀의 실제 rect를 재고, 빈 공간이 필요한지와 내용 폭이 부족한지를 별도로 판정한다
Apple도 다양한 화면 크기에서 적응형 레이아웃과 읽기 좋은 텍스트 폭의 제한을 권한다. 여기의 정확한 수치는 제품 토큰으로 정하고, 넓은 폭을 시험하지 않은 상태를 통과로 부르지 않는다. [Apple HIG Layout](https://developer.apple.com/design/human-interface-guidelines/layout)
### 4-e. 분할 히어로의 사진에는 상한이 있고, 카피에는 최소 폭이 있다
초광폭에서 `1fr 1fr`을 그대로 두면 사진은 창과 함께 계속 커지는데 카피는 상대적으로 가늘어져, 화면의 절반을 차지한 이미지와 읽을 수 없는 작은 설명이 공존한다. 풀블리드 편집 사진이 **의도적으로** 화면을 점유하는 경우가 아니라면, 분할 히어로를 wide surface처럼 별도 계약으로 둔다.
```css
@media (min-width: 120rem) {
.hero { width: min(100%, var(--stage-wide)); margin-inline: auto; }
.hero-copy { width: min(100%, var(--copy-wide)); justify-self: end; }
.hero-media { width: min(100%, var(--media-wide)); justify-self: end; }
}
```
- `--stage-wide`, `--copy-wide`, `--media-wide`, 후속 `--workbench-wide`를 따로 이름 짓는다. 한 `--wrap`을 무한히 키우지 않는다
- 1920·2560에서 hero·copy·media·다음 정보 레일의 실제 rect를 기록한다. media의 상한, copy의 최소 폭, hero의 중앙/의도된 정렬을 **그 페이지의 계약**으로 단언한다
- 이미지가 넓어질 자격은 이미지 자체의 편집적 역할에 있다. 정보·주문·비교가 뒤따르는 제품 화면에서는 후속 레일도 같은 밀도로 확장해, 첫 장면만 거대하고 본문은 점처럼 보이는 단절을 만들지 않는다
- 이 계약은 hero만의 면허가 아니다. 과정·추천·가맹 같은 별도 text-media split은 각각 원래의 padding/`max-width` 규칙을 가진다. wide에서 새 stage를 넣을 때는 inherited `padding-inline`의 **양쪽 값**과 상위 container selector의 specificity를 함께 확인한다. 한쪽에 `calc((100vw - --wrap) / 2)`가 남으면 grid rect는 멀쩡해도 실제 텍스트 폭이 한 글자까지 붕괴할 수 있다. 각 split마다 heading의 실제 폭·줄 수와 copy/media rect를 별도로 측정한다.
이 규칙은 읽기 폭을 지키는 기존 wide-surface 원칙의 구체형이다. 사람의 시선은 이미지 크기보다 이미지·카피·행동의 비례에서 우선순위를 읽는다.
---
## 5. 이 단계의 통과 조건
- [ ] CSS/HTML만으로 페이지가 완성됐다. JS를 꺼도 읽힌다
@ -224,7 +261,7 @@ new Set([...document.querySelectorAll('.nav a')]
- [ ] 본문에 `--measure` 가 적용됐다
- [ ] 모바일 폭 **320px**(iPhone SE 세로)에서 가로 스크롤이 없다
- [ ] 키보드 Tab 만으로 모든 인터랙티브 요소에 도달한다. 포커스 링이 보인다
- [ ] 같은 행 액션 묶음의 직접 자식 hitbox가 44px 이상이고 중심 y 차이가 CSS 픽셀 2px 이하다
- [ ] 같은 행 액션 묶음의 직접 자식 hitbox가 과업·WCAG target 기준에 맞고, 중심 y 차이를 실제 CSS 픽셀로 기록했다
- [ ] 데스크톱 분할 패널을 숨긴 상태에서 빈 그리드 트랙이 남지 않고 주 표면이 전체 폭을 회수한다. 모바일 시트는 닫힘·배경·포커스 복원을 따로 검증한다
- [ ] 한글이 있다면 조판 규칙이 적용됐다

View file

@ -37,7 +37,7 @@
|---|---|
| 애니메이션 때문에 콘텐츠가 **읽히기까지 지연**됨 | 첫 화면은 모션 없이 즉시 표시. 리빌은 스크롤 이후 |
| 스크롤 리빌이 위아래로 오갈 때 **매번 재생** | `both` / `once` / `unobserve()` |
| 애니메이션 중 **레이아웃 시프트** | 게이트 #8. `transform`/`opacity`만 |
| 애니메이션 중 **레이아웃 시프트** | 레이아웃·입력·CLS를 실제로 측정하고 프로젝트 예산과 비교 |
| **동시에 3개 이상** 독립 애니메이션 | 시선이 분산된다. 순차화하거나 통합 |
| 400ms 넘게 **사용자를 막는** 전환, 인터럽트 불가 | 줄이고, 애니메이션 중에도 입력을 받아라 |
| 무한 반복되는 **큰 면적** 움직임 | 전정기관 자극. 정지 수단을 주거나 제거 |
@ -79,9 +79,9 @@
---
## 2. 하드 게이트 #8을 지키면서 하고 싶은 걸 다 하는 법
## 2. 저비용 기본 선택으로 만들기
**`transform`/`opacity` 외의 속성을 애니메이션하면 실패다. 오버라이드 없다.** 그런데 거의 모든 요구는 우회할 수 있다.
**`transform`/`opacity`는 저비용 기본 선택이다.** 다른 속성의 애니메이션은 자동 실패가 아니다. 다만 비용·CLS·입력 간섭·감소 모션·지원 범위를 측정해 프로젝트 예산과 비교하고, 목적이 더 단순한 정적 상태로 충족되는지도 먼저 검토한다.
| 하고 싶은 것 | 하지 마라 | 대신 |
|---|---|---|
@ -203,7 +203,7 @@
### 코드 C — 스크롤 진입 리빌 (IntersectionObserver)
> 언제: Firefox 지원이 **요구사항**일 때, 또는 진입 시점에 **DOM을 바꿔야** 할 때(카운트업 시작, 지연 로드, 3D 씬 기동). CSS는 스타일만 바꾼다.
> `window.addEventListener('scroll')`은 **게이트 #10**이다. 절대 쓰지 마라.
> `window.addEventListener('scroll')`은 자동 금지가 아니다. 필요한 경우 passive 처리·throttle 또는 rAF 일정화·입력 지연·프레임 시간·감소 모션 경로를 측정하고, `IntersectionObserver` 또는 scroll-driven animation이 더 맞는지 비교한다.
```css
/* .js가 붙기 전에는 그냥 보인다 — JS가 실패해도 콘텐츠가 사라지지 않는다 */
@ -732,9 +732,9 @@ const progress = clamp01((start - rect.top) / travel);
**하나라도 실패하면 4-4 복귀다.**
**게이트 직결**
- [ ] `transition`·`@keyframes`를 전부 grep했다. 애니메이션되는 속성이 `opacity`·`transform`·`translate`·`rotate`·`scale`뿐이다 (**#8**)
- [ ] `background-color`·`box-shadow`·`filter`·`height`·`width`·`top`·`left`·`color`를 전환하는 곳이 없다. 있으면 §2 우회표로
- [ ] `addEventListener('scroll'`이 소스에 없다 (**#10**)
- [ ] `transition`·`@keyframes`를 전부 grep하고, 각 속성의 목적·비용 측정·입력 간섭·감소 모션 경로를 기록했다. `opacity`·`transform`·`translate`·`rotate`·`scale` 이외 속성은 프로젝트 예산과 실제 렌더 결과로 정당화했다
- [ ] `background-color`·`box-shadow`·`filter`·`height`·`width`·`top`·`left`·`color`를 전환한다면 §2의 저비용 대안과 비교하고, 유지 이유와 측정 결과를 기록했다
- [ ] `addEventListener('scroll'`을 썼다면 passive 처리와 일정화, 프레임 시간·입력 지연 측정, 감소 모션 경로를 기록했다
- [ ] duration·이징이 전부 `var(--dur-*)`·`var(--ease-*)`다. 인라인 ms 값이 없다
- [ ] 이펙트를 전부 끈 상태에서 페이지가 완성돼 있다
- [ ] **등장 모션이 감사 도구의 대기시간보다 짧다** — 시각 회귀가 뷰 전환 뒤 N ms 에 스크린샷을 찍는다면 등장 애니메이션은 그보다 짧게(관례: ≤70ms). 아니면 회귀가 매번 다른 프레임을 찍어 흔들린다. 자세한 규칙은 `audit-gate.md` 하니스 10

View file

@ -31,8 +31,8 @@ canvas { display: none !important; }
```
이 상태에서 **페이지가 여전히 읽히고 구조가 살아 있어야 한다.** 무너지면 이펙트가 구조를 대신하고 있는 것이다. 4-1로 돌아가라.
### B. 슬롭 지문 grep
`references/antipatterns.md` 의 grep 목록을 소스에 돌린다. 검출률 상위 항목(보라 CTA, 전체 대문자 헤드라인, 번호 매긴 1·2·3 단계)부터.
### B. 스타일 후보 grep
`references/antipatterns.md`의 grep 목록을 소스에 돌려 보라 CTA, 전체 대문자 헤드라인, 번호 매긴 단계 같은 반복 후보를 찾는다. 검출은 실패가 아니다. 프로젝트 계약·브리프·실제 렌더를 대조해 결정을 기록한다.
### C. 카피 경쟁사 치환
제품명을 경쟁사 이름으로 바꿔 읽는다. **문장이 그대로 성립하면 그 카피는 아무것도 말하지 않았다.** 히어로 문구부터 검사한다.
@ -110,34 +110,43 @@ canvas { display: none !important; }
### 폰트 (`typography.md` §8 이 전체 목록)
- [ ] **폴백 메트릭을 보정했거나 `font-display: optional` 을 골랐다.** 둘 다 안 했으면 폰트가 교체되는 순간 레이아웃이 튄다
- [ ] preload 한 파일이 첫 화면에 실제로 쓰인다. `crossorigin` 이 있다
- [ ] 수치를 나열하는 곳에 `tabular-nums` 가 있다
- [ ] 한글 폰트를 명시했고 서브셋을 쓴다
- [ ] 웹폰트 임베딩 라이선스를 확인해 `design.md` 에 적었다
- [ ] **폰트를 못 받은 상태로 열어봤다.** 그 상태로도 읽힌다
- [ ] 웹폰트를 쓰면 실제 교체 시점의 레이아웃을 측정하고, 폴백 메트릭·`font-display` 전략을 선택했다. 특정 전략만으로 CLS 유무를 단정하지 않는다
- [ ] 폰트를 preload했다면 첫 화면에서 실제로 쓰이고 요청에 맞는 `crossorigin`이 있다
- [ ] 수치 비교가 필요한 곳의 숫자 정렬을 확인했다. `tabular-nums`는 해당 서체의 지원과 과업에 맞춰 선택한다
- [ ] 한글 글꼴과 시스템 폴백을 실제로 확인했다. 웹폰트를 전송하는 경우 서브셋·전송량·글리프 범위를 검토했다
- [ ] 웹폰트를 임베딩하면 라이선스를 확인해 `design.md`에 적었다
- [ ] 웹폰트를 쓰면 **폰트를 못 받은 상태로 열어봤다.** 그 상태로도 읽힌다
### 자주 걸리는 것
- 폰트 서브셋 안 함 (한글 전체 폰트는 수 MB)
- 전송하는 폰트의 크기·글리프 범위 미측정. 한글 웹폰트는 필요한 범위와 라이선스에 맞춰 서브셋을 검토한다
- 첫 화면 밖 이미지에 `loading="lazy"` 누락
- 3D/WebGL을 초기 번들에 포함 (뷰포트 진입 시 동적 import 해야 한다)
- 애니메이션이 `top`/`left`/`width` 를 건드림 → `transform` 으로 바꿔라
- 애니메이션이 `top`/`left`/`width` 를 건드림 → `transform`·`opacity`로 가능한지 먼저 검토하고, 유지한다면 비용·CLS·입력 간섭·감소 모션을 측정해 예산과 대조한다
- `will-change` 남발 → 레이어가 늘어 오히려 느려진다
---
## 3. 반응형
- [ ] **320px** 폭에서 가로 스크롤 없음 (하드 게이트 #5 는 320~1920px 전 구간을 요구한다. 375px 만 보면 그 아래가 뚫린다)
- [ ] 프로젝트가 채택한 회귀 매트릭스에서 가로 스크롤 없음. 이 저장소의 기본 매트릭스는 **320·390·768·1440·1920·2560px**이며, 지원 기기·브리프가 다르면 그 계약을 바꾸고 이유를 기록한다
- [ ] **크롬(탑바·앱바·툴바)에 컨트롤을 더했다면 그 줄의 최소폭 합을 가장 좁은 폭에서 다시 잰다** — 버튼 하나가 320px를 뚫는 사고가 반복된다(실측: 인쇄 버튼 1개로 +18px). 줄이 넘으면 감싸기(2행 랩)가 기본 수습이다
- [ ] 1920px 이상에서 콘텐츠가 늘어지지 않음(최대 폭 제한)
- [ ] 프로젝트 회귀 매트릭스의 각 폭에서 실제 브라우저 렌더를 잰다. 기본 매트릭스(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 이상**(WCAG 2.5.8 AA). 여기에 44 를 요구하면 정상적인 내비 링크가 오탐된다
- [ ] **컨트롤의 내용 여백**: 버튼 라벨·입력값·select 표시·표 셀의 실제 렌더 글자가 테두리에 닿지 않는다. 이 저장소의 실렌더 기본값은 좌우 **8 CSS px** 이상이다. 이는 WCAG 적합성 수치가 아니라 검증 가능한 시각 품질 기본값이며, 아이콘 전용·체크박스·의도적인 데이터 정렬은 별도 근거를 적는다
- [ ] **폼 기하**: 연결된 label/name/autocomplete를 확인한 뒤 실제 렌더 rect로 입력끼리 겹치지 않는지 잰다. 좁은 폭에서는 필드를 세로로 쌓고, label·입력·오류 문장이 서로의 클릭/읽기 영역을 침범하지 않게 한다
- [ ] **표와 표처럼 보이는 flex/grid**: 데이터 관계에는 `th`/`td`와 `scope`를 쓰고, 모든 가시 셀에 좌우 읽기 여백을 둔다. 좁은 폭에서 스크롤·카드화로 형식이 바뀌어도 헤더-값 관계가 남아야 한다
- [ ] 호버로만 접근되는 기능 없음
- [ ] 긴 단어/URL이 넘치지 않음 (`overflow-wrap: anywhere`)
- [ ] 가로 모드에서 첫 화면이 잠기지 않음
> 근거: [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.
---
## 4. 콘텐츠
@ -159,6 +168,7 @@ canvas { display: none !important; }
- [ ] **판정은 순수 함수다** — 가능/불가+사유의 판정 로직은 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)
- [ ] **로딩 상태는 실제 비동기에만** — 동기 인라인 데이터에 스켈레톤을 붙이는 건 저장을 흉내내는 것이다. 없는 지연을 만들지 마라
---
@ -178,17 +188,30 @@ canvas { display: none !important; }
- [ ] 생성 이미지는 가격·주소·점수의 증거로 쓰이지 않으며, 모바일 390×844에서 핵심 피사체가 첫 화면의 의도와 맞는다
- [ ] URL별 390·1440 기본 화면과 핵심 입력/오류/모달 상태 캡처를 실제로 열어 보고 시각 PASS/RED를 따로 기록했다
## 4-3. 썸네일·OG 캡처 — 움직이는 화면을 대표하지 않는다
썸네일은 '페이지가 열렸다'는 증거가 아니라 사람들이 처음 보는 **정지된 브랜드 표면**이다. 로딩 중인 폰트, 이미지 디코드 전 프레임, 입장 모션 중간, 다음 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바이트·해시 중복을 검사한 뒤 적어도 움직임이 있던 대표 컷을 눈으로 연다. 생성 이미지가 있으면 기존 진실 라벨 계약도 그대로 보존한다
> 근거: [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.
---
## 5. 디자인 자체
기계로 못 잡는 부분이다. **정직하게 답해라.**
첫인상·실제 과업 행동·접근성 적합성·전문가 시각 검토가 필요한 경우, [design foundations의 평가 절차](design-foundations.md)를 읽고 각 결과를 분리해 기록한다.
1. **이 사이트를 다른 회사의 것으로 바꿔도 그대로 쓸 수 있는가?** → 그렇다면 브리프에 맞춘 것이 없다
2. **2단계에서 정한 한 문장 컨셉이 화면에서 보이는가?** → 안 보이면 컨셉이 문서에만 있는 것이다
3. **감수하기로 한 리스크가 실제로 들어갔는가?** → 구현 중에 안전한 쪽으로 후퇴하지 않았는지
4. **1단계 레퍼런스 R2(다른 업종의 톤)의 흔적이 있는가?** → 없으면 결국 R1을 베낀 것이다
5. **강조색이 하나인가?**
4. **사용한 레퍼런스의 원칙이 브리프와 렌더 결과에 맞게 번역됐는가?** → 슬롯 수나 업종 차이 자체로 판단하지 않는다
5. **강조색·radius·정렬·반복이 프로젝트 토큰과 과업에 맞는가?**
6. **여백이 의도적인가, 남은 것인가?**
---
@ -204,8 +227,9 @@ canvas { display: none !important; }
- [ ] 콘텐츠 체크리스트 통과
- [ ] 상태 완결성(§4-1) — 입력 검증·되돌림·Esc 를 실제 조작으로 확인
- [ ] 해당하면 진실 완결성(§4-2) — 예시·AI·규제·생성 이미지의 오해 경로를 실제 상태로 확인
- [ ] 5장 여섯 질문에 정직하게 답했고, 실패 항목을 고쳤다
- [ ] `antipatterns.md` 자가 채점표 통과
- [ ] 정적 CSS/JS의 stable URL을 바꿨다면 HTML 링크와 `@import` 의 버전을 함께 올렸고, 공개 CDN에서 실제 내려온 stylesheet URL과 새 토큰/규칙을 확인했다. HTML만 cache-bust하고 CSS가 이전 버전이면 배포 성공이 아니다
- [ ] 기능·접근성 자동 검사의 결과와 시각 평가(위계·브랜드·타입·구도·이미지·리듬)의 결과를 별도로 기록했다. 한쪽 PASS를 다른 쪽 PASS로 바꾸어 말하지 않았다
- [ ] 5장 여섯 질문과 `antipatterns.md` 검토를 프로젝트 계약·실제 렌더 근거로 기록했다
**하나라도 실패하면 출시가 아니라 4단계 복귀다.**

View file

@ -2,7 +2,7 @@
2단계에서 읽는다. **하나를 고르고, 왜 그것인지 한 줄로 적는다.**
프리셋은 템플릿이 아니라 **결정의 출발점**이다. 그대로 쓰면 그 프리셋의 평균이 나온다.
프리셋은 템플릿이 아니라 **결정의 출발점**이다. 값·폰트·효과 목록은 가설이며, 브리프·콘텐츠·실제 렌더 검증으로 조정한다. 단 각 프리셋이 정한 정체성 계약(예: editorial의 세리프 주역, dense 화면의 비교 가능성)은 바꾸려면 다른 프리셋 또는 새 방향을 정의한다.
1단계 레퍼런스 R2(다른 업종의 톤)를 여기에 섞어야 이 브리프만의 것이 된다.
| 프리셋 | 한 줄 | 언제 |
@ -14,4 +14,4 @@
| [quiet-commerce](quiet-commerce.md) | 읽히는 커머스 | 전환이 목적인 B2C. 상품 수가 적고 사양이 설득의 주체 |
**어느 것도 맞지 않으면 새로 정의해라.** 브리프가 프리셋보다 우선한다.
새로 정의할 때도 이 문서들의 항목 구조(타이포/색/간격/재질/모션/실패)는 그대로 채워야 한다.
새로 정의할 때도 이 문서들의 항목 구조(타이포/색/간격/재질/모션/실패/검증)는 채운다. 실제 적용 조건과 검증은 `../style-playbook.md`를 따른다.

View file

@ -2,7 +2,7 @@
> 한 문장: **기억되는 것이 목적일 때.** 규칙을 알고 나서 깬다.
벤토 그리드가 표준화되자 그 반작용으로 부상한 방향이다. 2026년 현재 **실제로 작동하는 차별화 수단**이다.
규칙적 격자에 비대칭·겹침·예상 밖의 리듬을 의도적으로 더하는 방향이다. 이것이 2026년에 보편적으로 더 효과적이라는 실증 주장은 하지 않는다.
## 언제 고르나

View file

@ -16,7 +16,7 @@
## 결정
### 타이포그래피
- 산세 + **모노스페이스 병용**. 수치·ID·코드는 반드시 모노
- 산세를 본문 출발점으로 두고, 정렬이 중요한 수치·ID·코드에는 tabular figure 또는 모노를 검토한다. 모든 수치·ID에 모노가 필수인 것은 아니다.
- 비율 1.200~1.250. 정보 밀도가 높으므로 위계 차이를 작게
- 숫자는 **tabular-nums** 를 켜라 (`font-variant-numeric: tabular-nums`). 수치가 흔들리면 계기판이 아니다
@ -26,8 +26,7 @@
| 수치·코드 | JetBrains Mono, IBM Plex Mono | + Pretendard |
### 색
- **순수 검정을 쓰지 마라.** `#0d0d0f`~`#14130f` 대역. 순흑은 대비가 과해 눈이 아프다
- 텍스트도 순백 금지. `#e8e4dc`~`#e6e6e9`
- 순수 검정/흰색과 근접색을 모두 후보로 두고, 실제 기기·주변광·장시간 읽기에서 대비와 눈부심을 확인한다. `#0d0d0f`~`#14130f`, `#e8e4dc`~`#e6e6e9`는 출발값 예시다.
- 표면 단계를 3개로: `--surface`(가장 어두움) / `--surface-raised` / `--line`
- 강조색은 **밝고 채도 높게, 아주 좁게.** 어두운 배경에서는 작은 면적으로도 충분히 강하다
- 상태색(성공/경고/오류)을 강조색과 구분해서 정의해라. 여기서는 기능이 우선이다
@ -49,9 +48,9 @@
## 흔한 실패
1. **순수 검정 + 순수 흰색** → 눈이 아프고 아마추어
1. **배경·텍스트 대비를 기기에서 확인하지 않음** → 순수색과 근접색 모두 조건에 따라 눈부심·번짐·브랜드 인상을 만들 수 있다
2. **네온 보라/파랑 강조** → 슬롭 지문. 어두운 배경일수록 이 함정에 빠지기 쉽다
3. **다크만 만들고 라이트를 안 만듦** → 토큰으로 짰으면 라이트도 나온다. 다크 전용은 사용자 선택권을 뺏는 것
3. **테마 결정을 설명하지 않음** → 다크 전용일 수 있지만 대상 환경·브랜드·접근성·운영 이유를 기록하고, 지원하는 테마는 역할 토큰과 실제 상태를 완성한다
4. **수치에 가변폭 폰트** → 숫자가 흔들려 읽기 어렵다
5. **대비 미달** → 어두운 배경에서 `--ink-muted` 가 4.5:1 아래로 떨어지기 쉽다. 반드시 측정해라

View file

@ -11,9 +11,9 @@
## 결정
### 타이포그래피
- **세리프를 주역으로.** 디스플레이와 본문 중 최소 하나는 세리프
- 세리프 또는 고대비 산세를 주역 후보로 둔다. 대상 언어의 glyph·작은 크기·긴 읽기에서 더 잘 작동하는 쪽을 선택한다.
- 비율 **1.333~1.618** — 헤드라인과 본문의 차이를 크게 벌린다
- 본문 `--measure` 를 반드시 좁게. 라틴 60~68자, 한글 25~35자
- 본문 `--measure`는 상대적으로 좁은 값에서 시작하고, 라틴 60~68자·한글 25~35자는 후보 범위로만 쓴다. 글꼴·언어·확대 상태의 실제 문단으로 확정한다.
- 웨이트는 2개면 충분(Regular + 하나). 굵기로 소리치지 않는다
| 슬롯 | 무료 | 유료 | 한글 |
@ -22,9 +22,9 @@
| 본문 | Spectral, Lora | Söhne, Tiempos | 마루 부리 / 리디바탕 |
### 색
- 배경은 **순백이 아니다.** 종이 쪽으로 살짝 따뜻하게 (`#fbfaf8`, `#f7f5f0`)
- 잉크도 순흑이 아니다 (`#1a1917`, `#211f1c`)
- 강조색은 **하나, 작게.** 링크·인용부호·페이지 번호 정도. 면적을 넓히면 잡지가 아니라 전단지가 된다
- 종이 같은 따뜻한 배경과 순백 모두를 콘텐츠·이미지·브랜드 대비로 비교한다. `#fbfaf8`, `#f7f5f0`은 출발값 후보일 뿐이다.
- 잉크는 순흑과 근접색을 실제 대비·주변 이미지·긴 읽기에서 비교한다.
- 강조 역할이 필요하면 **한 색을 작게** 쓴다. 강조색이 없어도 되며, 링크·인용·페이지 번호의 구분은 대비·밑줄·위치로도 만들 수 있다.
### 간격
- **여백이 콘텐츠다.** 섹션 간격을 본문 간격의 5~6배까지 벌려도 된다
@ -32,7 +32,7 @@
- 이미지는 본문 폭을 넘어가게(`wide`/`full`) 두어 리듬을 만든다
### 재질 (`svg-filters.md`)
- **필름 그레인**이 이 프리셋의 기본 재질. `feTurbulence fractalNoise` 를 아주 약하게(opacity 0.03~0.06)
- 필름 그레인은 인쇄·사진 재질이 브리프에 맞을 때만 후보로 둔다. 효과 없이도 editorial 리듬이 성립해야 한다.
- 인쇄물의 질감: 이미지에 미세한 듀오톤 또는 그라디언트 맵
- 유리·네온·글로우는 이 프리셋에 어울리지 않는다

View file

@ -18,7 +18,7 @@
### 타이포그래피
- 디스플레이 **세리프 1** + 본문 **산세리프 1**. 세리프가 톤을, 산세리프가 가독성을 맡는다
- 비율 **1.333**, 최대÷본문 **4배 이상**. 비율만으로 안 나오면 디스플레이를 스케일 밖에 따로 정의한다(`tokens.md` §1)
- 비율 1.333과 강한 display 대비에서 시작하고, 문구 길이·제품 증거·모바일 줄 수로 확정한다. 최대÷본문 4배 같은 고정 비율은 요구사항이 아니다.
- 단계 **4~5개**. 커머스 레퍼런스는 흔히 10단계인데 그대로 베끼면 "통제 실패"에 걸린다
- 웨이트 2개
@ -33,12 +33,12 @@
### 색
- 배경은 순백이 아니라 **오프화이트**. 단 **크림·베이지는 피해라** — "크림 = 고급"은 보라를 대체한 신종 슬롭이다. 크림을 쓰려면 브리프와 연결할 근거를 대라
- 잉크도 순흑이 아니다
- **강조색 하나, 면적 5% 미만.** 좋은 커머스 레퍼런스는 강조색을 CTA 한 곳과 링크에만 쓴다
- 강조 역할이 필요하면 한 색으로 시작하고, 실제 상태색·링크·CTA를 포함한 화면에서 경쟁 여부를 검증한다. 5% 면적 같은 값은 보편 품질 기준이 아니다.
- hue 2개 (중립 + 강조)
### 간격
- 8px 그리드, 여백 값 5종
- **섹션 간 : 요소 간 = 1:5.** 커머스는 섹션이 많아 간격이 좁으면 즉시 뭉개진다
- 섹션과 요소 간격의 차이가 정보 묶음을 읽히게 하는지 본다. 1:5는 예시 가설이며, 상품 수·콘텐츠 길이·화면 폭에서 조정한다.
- 비대칭을 쓴다 — 본문을 그리드 왼쪽에 두고 오른쪽 여백에 사양·수치를 흘린다
- `--measure` 는 좁게. 한글 25~35자

View file

@ -11,7 +11,7 @@
## 결정
### 타이포그래피
- **그로테스크 산세 하나로 끝낸다.** 두 번째 폰트를 넣고 싶으면 모노스페이스(라벨·코드용)까지만
- 그로테스크 산세 하나에서 시작하고, 필요한 경우 라벨·코드 역할에 모노를 추가한다. 언어 범위나 실제 위계가 추가 패밀리를 요구하면 전송량과 렌더 근거를 남긴다.
- 비율 **1.200~1.250** — 위계 차이를 작게. 대신 웨이트와 색으로 구분
- 정렬은 왼쪽. 가운데 정렬은 히어로 한 곳까지만
- **Inter 를 기본값으로 쓰지 마라.** Switzer, Satoshi, DM Sans 가 거의 언제나 낫다
@ -22,7 +22,7 @@
| 라벨·코드 | JetBrains Mono, IBM Plex Mono | — | Pretendard + JetBrains Mono |
### 색
- 무채색 90% + 강조색 하나
- 무채색 비중이 큰 팔레트에서 시작하고, 강조 역할이 필요하면 한 색으로 시작해 실제 상태색·링크·CTA의 경쟁을 검증한다
- 배경/카드/경계선의 명도 차이를 **아주 작게**. 그림자 대신 1px 경계선
- 강조색은 채도를 낮춰라. 형광에 가까운 색은 이 프리셋에서 즉시 싸구려가 된다
- **보라~파랑 그라디언트 절대 금지** (검출률 1위 슬롭 지문)
@ -30,12 +30,12 @@
### 간격
- 4/8 배수 그리드를 **엄격하게** 지킨다. 여기서만큼은 예외를 만들지 마라
- 정렬선이 전부 맞아야 한다. 이 프리셋의 품질은 정렬에서 나온다
- 섹션 간격은 본문의 4배
- 섹션과 본문의 간격 대비는 시작값으로 크게 두되, 콘텐츠 그룹과 좁은 폭에서 실제 리듬을 보고 확정한다.
### 재질 (`svg-filters.md`)
- **재질을 거의 쓰지 않는다.** 이 프리셋의 표면은 평평한 종이다
- 굳이 쓴다면 아주 약한 그레인 하나까지
- 글래스모피즘 금지 — 실기기에서 FPS 15~30% 하락하고, 이 프리셋의 정직함과 어긋난다
- 유리 효과는 이 프리셋의 평면적 언어와 충돌하기 쉽다. 채택하려면 계층상 이유와 저사양 기기의 성능·대비·reduced-transparency 대안을 검증한다. 고정 FPS 하락 수치를 일반화하지 않는다.
### 모션 (`motion.md`)
- 상태 변화 위주(`--dur-instant`, `--dur-quick`). 등장 애니메이션은 최소화

View file

@ -6,27 +6,27 @@
| 가져간다 (원리) | 안 가져간다 (표현) |
|---|---|
| 그리드 비율 (5:7 비대칭) | 섹션 배치 그대로 |
| 타입 스케일 비율 (1.333) | 폰트 조합 그대로 |
| 색의 역할 배분 (60/30/10) | 색상값 |
| 여백 리듬 (섹션 120 / 요소 24) | 섹션 순서 |
| 그리드의 비대칭 관계 (관찰 예: 5:7) | 섹션 배치 그대로 |
| 타입 단계의 관계 (관찰 예: 1.333) | 폰트 조합 그대로 |
| 색의 역할 배분 (관찰 예: 60/30/10) | 색상값 |
| 여백의 상대 리듬 (관찰 예: 섹션 120 / 요소 24) | 섹션 순서 |
| 모션의 의도 (스크롤=시선 유도) | 애니메이션 시퀀스 |
표의 숫자는 특정 사례를 설명하는 관찰 예시일 뿐, 다른 프로젝트에 그대로 적용할 법칙이 아니다. 관계가 현재 콘텐츠·언어·과업·렌더에서도 작동하는지 확인한 뒤 값을 정한다.
---
## 1. 3-소스 합성 규칙
## 1. 레퍼런스 슬롯으로 해체하기
레퍼런스 1개는 모방, 2개는 절충, **3개부터 합성**이다. 단 **층위를 나눠서** 고른다.
R1·R2·R3은 구조·톤·디테일을 섞어 복제하지 않기 위한 **검토 슬롯**이다. 세 슬롯을 채웠다는 사실은 합성의 품질을 보장하지 않는다. 프로젝트에 필요한 슬롯만 채우고, 비운 슬롯은 이유와 대체 근거를 기록한다.
| 슬롯 | 가져올 것 | 어디서 |
|---|---|---|
| **R1 · 구조** | 그리드, 섹션 순서, 정보 위계, 여백 리듬 | 같은 업종·같은 목적 |
| **R2 · 톤** | 색 역할, 타입 페어링, 형태 언어, 질감 | **반드시 다른 업종** (또는 웹이 아닌 것) |
| **R2 · 톤** | 색 역할, 타입 페어링, 형태 언어, 질감 | 다른 업종 또는 웹 이외 매체를 우선 검토 |
| **R3 · 디테일** | 모션 1개, 호버 1개, 전환 1개 | Codrops / Eyecandy / Design Spells |
**R2가 R1과 같은 업종이면 합성이 아니다.**
SaaS 구조 + SaaS 톤 = 또 하나의 SaaS 슬롭.
SaaS 구조 + 독립 출판사 톤 = 차별화.
R2가 R1과 같은 업종이어도 브리프가 요구하는 브랜드·시장·제작 조건에 맞으면 가능하다. 같은 업종의 평균을 무비판적으로 복제하지 않도록, 가져오는 원칙·관찰·결정·검증을 분리해 기록한다.
### 목표 시장은 네 번째 레퍼런스가 아니라 경계 조건이다
@ -37,7 +37,7 @@ SaaS 구조 + 독립 출판사 톤 = 차별화.
```
- R1은 같은 업종·목적에 더해 **같은 목표 시장을 우선**한다. 구조에 단위·용어·연락 방식이 묻어 있기 때문이다.
- R2의 `다른 업종` 규칙은 유지한다. 목표 시장 감각이 중요하면 **같은 문화권의 다른 업종**을 먼저 찾는다.
- R2는 다른 업종 또는 같은 문화권의 다른 매체를 먼저 검토하되, 같은 업종이 더 적합하면 그 조건과 이유를 기록한다.
- 다른 문화권 R3는 기법만 가져온다. 카피·색·상징·사진 연출까지 번지면 경계 조건 위반이다.
- 같은 시장 자료가 부족하면 빈칸을 숨기지 말고, 어떤 관습을 공식 문서·현지 서비스로 별도 검증했는지 적는다.
@ -47,41 +47,38 @@ SaaS 구조 + 독립 출판사 톤 = 차별화.
1. 가독성·접근성이 언제나 이긴다. R2의 톤이 대비를 깨면 톤을 조정한다.
2. R1 구조가 R3 모션을 이긴다. 모션 때문에 구조를 바꾸지 않는다.
3. 브리프가 모든 레퍼런스를 이긴다. 충돌하면 레퍼런스를 버린다.
4. **한 축은 한 소스에서만.** 그리드는 R1에서만, 팔레트는 R2에서만. 섞으면 이유 없는 절충이 된다.
4. 한 축에 여러 출처가 영향을 주면 출처별 관찰과 최종 결정을 분리한다. 출처가 섞였다는 사실이 실패는 아니며, 근거 없이 절충한 경우만 다시 검토한다.
---
## 2. 6축 해체 프레임워크
각 레퍼런스를 6축으로 뜯는다. **형용사는 기록이 아니다. 숫자나 규칙으로 적는다.**
각 레퍼런스를 6축으로 뜯는다. **형용사만으로 끝내지 않는다.** 실측값, 관찰 가능한 규칙, 적용 조건을 함께 적고 실제 렌더에서 결정이 맞는지 확인한다. 아래 숫자 범위는 보편 판정이 아니라 관찰을 시작할 때 쓸 수 있는 예시다.
**R3 예외**: R3는 사이트가 아니라 기법이다. 축 5(모션 언어) 하나만 채우고 나머지는 비운다.
대신 R3에는 **구현 제약**(브라우저 지원, 성능 비용, 보간 조건)을 반드시 적는다.
### 축 1 — 레이아웃 그리드
컬럼 수·거터 / 콘텐츠 최대폭과 뷰포트 비 / 대칭·비대칭(비율) / 그리드를 깨는 요소 / 본문 한 줄 글자 수(영문 45–75, 국문 25–40)
컬럼 수·거터 / 콘텐츠 최대폭과 뷰포트 비 / 대칭·비대칭(비율) / 그리드를 깨는 요소 / 실제 본문 줄 길이와 읽기 맥락
→ *"무엇을 정렬시키고 무엇을 일부러 어긋나게 했나?"*
### 축 2 — 타입 스케일
실제 사용된 크기 목록 / 그 사이 비율(1.25 또는 1.333이 표준) / 단계 수(3–5가 건강, 8+ 는 통제 실패) / 패밀리 수(2개 표준) / 굵기 대비 / line-height / letter-spacing
→ *"최대 글자와 본문의 비는 몇 배인가?"* (4배 미만이면 위계 약함)
실제 사용된 크기 목록 / 그 사이 비율 / 단계 수 / 패밀리 수 / 굵기 대비 / line-height / letter-spacing
→ *"최대 글자와 본문의 비는 몇 배이고, 이 화면의 과업·언어·읽기 폭에서 위계가 보이는가?"*
### 축 3 — 컬러 역할
색상값이 아니라 **역할**로 적는다. surface 단계 수 / text 3단계 유무 / accent 면적 %(10% 이하가 정상) / border 존재 여부 / state 색 분리 여부
→ *"강조색을 지우면 여전히 읽히는가?"* (읽혀야 정상)
색상값이 아니라 **역할**로 적는다. surface 단계 수 / text 위계 / accent 면적과 역할 / border 존재 여부 / state 색 분리 여부
→ *"강조색을 지워도 정보 위계와 조작 상태를 이해할 수 있는가?"*
### 축 4 — 여백 리듬
기본 단위(4 또는 8px) / 섹션 간 수직 여백(80–160px) / 요소 간 : 섹션 간 비율(1:4~1:6) / 여백 값 종류 수(4–6종이 건강) / 좌우 패딩
기본 단위 / 섹션 간 수직 여백 / 요소 간 : 섹션 간 비율 / 여백 값 종류 / 좌우 패딩
→ *"가장 큰 빈 공간은 어디이고 왜 거기인가?"*
### 축 5 — 모션 언어
트리거(로드/스크롤/호버/커서) / duration / easing / 정보 전달인가 장식인가 / reduced-motion 대응
→ *"모션을 전부 끄면 작동하는가?"* (작동해야 정상)
**어떤 CSS 속성을 애니메이션하는지 반드시 적어라.** 하드 게이트 #8은 `transform`·`opacity` 만 허용한다.
`stroke-dashoffset`·`height`·`clip-path`·`filter` 로 만든 기법을 R3로 골라놓고 4단계에서 발견하면
그때는 이미 늦다. **1단계에서 걸러라.** 그 기법이 좋다면 `motion.md` 의 우회법 표에서
같은 인상을 `transform`/`opacity` 로 내는 방법을 찾은 뒤에 채택해라.
**어떤 CSS 속성 또는 스크롤 로직을 쓰는지 반드시 적어라.** `transform`·`opacity`는 저비용 기본이지만 다른 선택은 금지가 아니다. `stroke-dashoffset`·`height`·`clip-path`·`filter` 같은 기법은 1단계부터 비용 측정, 입력 간섭, 감소 모션, 지원 범위와 예산을 함께 기록해 `motion.md`의 계약으로 검증한다.
### 축 6 — 시선 흐름
첫 진입점 / 2·3번째 / 비즈니스 우선순위와 일치 여부 / Z·F·수직 낙하 중 무엇인가 / CTA가 경로 위에 있나
@ -90,7 +87,7 @@ SaaS 구조 + 독립 출판사 톤 = 차별화.
> 색을 전부 제거했을 때 가장 강한 덩어리는 ___, 두 번째는 ___, 세 번째는 ___.
> 이 순서는 비즈니스 우선순위 [1위 ___ / 2위 ___ / 3위 ___]와 일치한다 / 하지 않는다.
일치하지 않으면 크기·굵기·대비를 조정한다. **색으로 해결하지 마라.**
일치하지 않으면 크기·굵기·대비·공간·순서를 검토한다. 색만으로 해결하지 않고, 필요한 경우 색 역할도 프로젝트 토큰 안에서 조정한다.
---
@ -98,19 +95,19 @@ SaaS 구조 + 독립 출판사 톤 = 차별화.
- [ ] **1. 브리프를 한 문장으로 압축한다.** `[대상]이 [행동]하게. 느낌은 [형용사 2개].` 이 문장 없이 수집을 시작하지 않는다.
- [ ] **1-b. 목표 시장 경계 조건을 쓴다.** 국가/생활권·기기·단위/통화·행동 관습·금지할 외래 문법. R1이 같은 시장인지 표시한다.
- [ ] **2. galleries.md 라우팅 표에서 R1/R2/R3 갤러리를 정한다.** R2가 R1과 다른 업종인지 확인한다.
- [ ] **3. 각 갤러리에서 원본 사이트 URL을 1개씩 확보한다.** 하위 페이지가 필요하면 **인덱스에서 `href`를 뽑아라. slug를 추측하지 마라** — 추측한 URL은 404가 난다.
- [ ] **2. galleries.md 라우팅 표에서 필요한 레퍼런스 슬롯을 정한다.** R2의 업종·매체 선택 조건을 기록한다.
- [ ] **3. 사용한 각 슬롯에서 원본 사이트 URL을 확보한다.** 하위 페이지가 필요하면 **인덱스에서 `href`를 뽑아라. slug를 추측하지 마라** — 추측한 URL은 404가 난다.
- [ ] **3-b. 그 URL이 정본인지 검증한다.** 사용자가 특정 제품·브랜드를 지목했고 후보 도메인이 여럿이면 필수다. **HTTP 200은 살아있다는 뜻이 아니다.**
- `curl -sS -o /dev/null -w "%{url_effective} %{http_code} %{size_download}\n" -L <url>` 로 **최종 URL·본문 크기**를 본다. 본문이 **1KB 미만이면 파킹 도메인을 의심하라** — 전형적 파킹 페이지는 `/lander` 로 보내는 JS 한 줄뿐이다.
- 후보들의 본문이 **바이트 단위로 동일**하면 전부 같은 파킹 서비스다.
- 후보들의 본문이 **바이트 단위로 동일**하면 공통 응답일 수 있다. 파킹이라고 단정하지 말고 제목·본문 정체성·소유자·정본 링크를 확인한다.
- 정본 판정 근거는 **콘텐츠 안에서** 찾아라: 저장소 링크, 설치 명령에 적힌 패키지·스킬 이름, 소유자 계정, 문서 제목이 로컬 파일과 일치하는지.
- [ ] **4. 원본 사이트 3개를 §4 템플릿으로 WebFetch 한다.** 갤러리 페이지가 아니라 원본이다. → 축 6·정보 위계·카피 톤을 얻는다.
- [ ] **4. 사용한 원본 사이트를 §4 템플릿으로 WebFetch 한다.** 갤러리 페이지가 아니라 원본이다. → 축 6·정보 위계·카피 톤을 얻는다.
- [ ] **5. §5로 computed style을 추출한다.** → 축 1·2·3·4를 얻는다. **이 단계를 건너뛰면 6축의 4개가 빈칸으로 남는다.**
- [ ] **6. 텍스트 소스로 폰트 이름·무료 대체를 확정한다.** (Typewolf / Happy Hues / Eyecandy — galleries.md §3)
- [ ] **7. 6축 해체 시트를 작성한다.** 숫자로. 6축에 안 들어가는 관측은 `6축 밖의 관측`에 적는다.
- [ ] **8. 합성한다.** 축마다 어느 소스에서 가져왔는지 명시.
- [ ] **9. 의도적 변형을 각 레퍼런스마다 1개씩 넣고 이유를 적는다.**
- [ ] **10. antipatterns.md를 훑고 걸린 항목이 없는지 확인한다.**
- [ ] **7. 6축 해체 시트를 작성한다.** 실측값 또는 관찰 가능한 규칙과 적용 조건을 쓴다. 6축에 안 들어가는 관측은 `6축 밖의 관측`에 적는다.
- [ ] **8. 결정한다.** 출처 ID → 원칙/조건 → 관찰 → 결정 → 검증 계획을 프로젝트 `design.md` 또는 evidence 기록에 남긴다.
- [ ] **9. 필요한 변형과 이유를 적는다.** 변형 개수는 슬롯 수와 무관하다.
- [ ] **10. antipatterns.md를 훑고 발견한 후보를 프로젝트 계약과 실제 렌더로 평가한다.**
---
@ -123,7 +120,7 @@ SaaS 구조 + 독립 출판사 톤 = 차별화.
> 2. h1의 정확한 문장, 그리고 h2 전체 목록.
> 3. CTA 문구 전부와 각각이 페이지 어디에 있는지.
> 4. 내비게이션 항목 목록.
> 5. 이 페이지가 방문자에게 시키려는 단 하나의 행동.
> 5. 이 페이지가 방문자에게 시키려는 우선 행동과, 있으면 보조 행동. 각각이 어디서 어떤 위계로 제시되는지.
> 6. 카피의 톤 — 평균 문장 길이, 1인칭/2인칭 사용, 전문용어 밀도.
> 7. 숫자나 지표가 사용된 위치와 그 값.
> 추측하지 말고 페이지에 실제로 있는 것만 답해라. 없으면 "없음"이라고 써라.
@ -199,12 +196,12 @@ WebFetch는 마크다운으로 변환하면서 CSS를 버린다. **그리드·
> **먼저 `url` 을 봐라. 열려고 한 사이트가 아니면 나머지 값은 전부 버려라.**
> 페이지가 실행 중 이탈하거나 리다이렉트되면 스크립트는 **다른 사이트의 값을 조용히 반환한다.**
> 실측에서 2회 연속 발생했다. 이건 실패가 아니라 **오염**이다 — 숫자가 6축 시트에 들어가고,
> 3단계 토큰의 근거가 되고, `design.md` 에 남고, 어디서도 검출되지 않는다.
> 3단계 토큰의 근거가 되고, `design.md` 또는 프로젝트 evidence 기록에 출처 ID → 원칙/조건 → 관찰 → 결정 → 검증으로 남는다. `evidence-ledger.md`는 기록 형식을 안내하는 정본이다.
- `fontSizes` 상위 항목 = 타입 스케일. 최다 크기가 본문이다. 최대÷본문이 **4배 미만이면 위계가 약한 레퍼런스**다.
- `textColors` 상위 3~4개 = 텍스트 위계 단계. 3단계 이상이면 잘 설계된 것.
- `backgrounds` 최다값 = 지배 배경. 등장 횟수가 적은 채도 높은 색이 **강조색**이고, 그 횟수가 곧 면적 감각이다.
- `radii` 종류 수 = 형태 어휘. 1~3개면 통제된 것, 그 이상이면 우연히 결정된 것.
- `fontSizes` 상위 항목 = 타입 스케일 후보다. 실제 본문 요소와 화면 위계를 같이 확인한다.
- `textColors` 상위 항목 = 텍스트 위계 후보다. 색 수가 아니라 역할·대비·실제 렌더를 본다.
- `backgrounds` 최다값 = 지배 배경 후보다. 채도·등장 횟수만으로 강조 역할이나 면적을 단정하지 않는다.
- `radii` 종류 수 = 형태 어휘를 살필 출발점이다. 수가 아니라 토큰 역할과 화면 밀도로 평가한다.
- `widths` 최다값 = 컨테이너 폭. `body.widthPx` = 텍스트 컬럼 폭.
브라우저 도구를 못 쓰는 환경이면 **6축 중 4개가 빈칸이라는 사실을 산출물에 명시**하고 넘어간다. 채운 척하지 마라.
@ -236,22 +233,34 @@ WebFetch는 마크다운으로 변환하면서 CSS를 버린다. **그리드·
- 행동 관습:
- 금지할 외래 문법:
## R1 · 구조 — <이름> <원본 URL>
## R1 · 구조 — <출처 ID> <이름> <원본 URL>
- 가져올 축: 그리드, 여백 리듬
- 그리드: 최대폭 1200 / 비대칭 7:5 / 거터 32
- 여백: 8px 단위 / 섹션 간 120 / 요소 간 24 (1:5)
- 원칙/조건:
- 관찰:
- 결정:
- 검증 계획:
- 그리드: 관찰한 최대폭 / 비대칭 또는 대칭 관계 / 거터
- 여백: 관찰한 단위 / 섹션 간 / 요소 간 / 관계
- 6축 밖의 관측: (있으면) 카피 규범, 증거 제시 방식, 반복 노출 규칙 등
- 변형: 최대폭을 1120으로 축소 — 본문 한 줄이 국문 40자를 넘었기 때문
## R2 · 톤 — <이름> <원본 URL> [업종: R1과 다름 ✓]
## R2 · 톤 — <출처 ID> <이름> <원본 URL> [업종/매체 선택 조건:]
- 가져올 축: 컬러 역할, 타입 스케일
- 컬러: 배경 1단계 / 텍스트 3단계 / 강조 1색 면적 6%
- 타입: 세리프 디스플레이 + 그로테스크 본문, 비율 1.333, 최대/본문 4.2배
- 원칙/조건:
- 관찰:
- 결정:
- 검증 계획:
- 컬러: 관찰한 역할 단계와 면적 관계
- 타입: 관찰한 서체 역할·단계 관계·최대/본문 비율
- 6축 밖의 관측: (있으면) 신뢰를 만드는 방식, 제품을 보여주는 방식
- 변형: 비율을 1.25로 조정 — 정보량이 많아 중간 단계가 필요
## R3 · 디테일 — <이름> <출처 URL>
## R3 · 디테일 — <출처 ID> <이름> <출처 URL>
- 가져올 축: 모션 언어 (R3는 이 축만 채운다)
- 원칙/조건:
- 관찰:
- 결정:
- 검증 계획:
- 모션: 스크롤 진입 시 8px 상승 + 페이드, 320ms, ease-out
- 구현 제약: 브라우저 지원 / JS 비용 / 보간 조건
- 변형: 280ms로 단축 — 페이지가 길어 반복 노출이 잦음
@ -261,17 +270,17 @@ WebFetch는 마크다운으로 변환하면서 CSS를 버린다. **그리드·
비즈니스 우선순위 [1 ___ / 2 ___ / 3 ___]와 일치한다.
## 안 할 것
- (antipatterns.md에서 이 브리프에 특히 위험한 항목 3개 이상 명시)
- (antipatterns.md에서 이 브리프에 특히 위험한 후보와 그 이유)
```
**검증 조건** — 하나라도 빠지면 되돌아간다.
- R1/R2/R3 각각 **원본 사이트 URL**이 있다 (갤러리 URL 아님)
**검증 조건** — 사용한 슬롯마다 다음을 채운다.
- 사용한 슬롯은 **원본 사이트 URL**이 있다 (갤러리 URL 아님)
- 목표 시장 경계 조건이 있고 R1의 시장 일치 여부가 적혀 있다
- R2의 업종이 R1과 다르다
- 각 슬롯에 **가져올 축**이 명시되어 있다
- 각 슬롯에 **변형 1개와 그 이유**가 있다
- 6축 값에 숫자가 들어 있다 — **§5를 실행했거나, 못 했다면 어느 축이 빈칸인지 명시했다**
- R3에 **구현 제약**(지원 범위·JS 비용)이 적혀 있다
- R2의 업종 또는 매체 선택 조건이 적혀 있다
- 각 슬롯에 **가져올 축**, 출처 ID → 원칙/조건 → 관찰 → 결정 → 검증 계획이 명시되어 있다
- 필요한 변형과 그 이유가 적혀 있다
- 6축은 실측값 또는 관찰 가능한 규칙으로 채웠고, §5를 못 실행했다면 어느 축이 빈칸인지 명시했다
- R3를 썼다면 **구현 제약**(지원 범위·JS 비용)이 적혀 있다
- 회색조 위계가 비즈니스 우선순위와 일치한다
> 근거: research/references/02-methodology.md (조사일 2026-08-20)

View file

@ -0,0 +1,56 @@
# style playbook — 과업에 맞는 시각 언어
스타일은 성격 테스트가 아니라 과업·콘텐츠·운영 조건의 출발점이다. 아래 항목을 고른 뒤에도 [design foundations](design-foundations.md)의 접근성·성능·과업 검증을 통과해야 한다. 수치나 금지 목록을 복사하지 말고, 실제 렌더에서 결정의 이유를 확인한다.
| 계열 | 목표·정보 구조 | 타입·색·이미지 | 모션 | 적합 조건 | 실패 | 검증 |
|---|---|---|---|---|---|---|
| Swiss / editorial | 복잡한 정보 또는 긴 읽기를 정돈한다. Swiss는 명확한 grid·비교, editorial은 본문·캡션·wide media의 읽기 리듬을 쓴다 | Swiss는 산세·제한된 역할색·도표/제품 증거. Editorial은 읽기 가능한 세리프 또는 산세·선택적 종이감·고품질 사진 | 상태 피드백 우선, 읽기 흐름을 빼앗지 않음 | 문서·B2B·문화·포트폴리오처럼 구조 또는 콘텐츠가 설득할 때 | 빈 여백으로 정보·근거를 숨김; editorial에서 라틴 전용 폰트가 한글을 망침 | 320px·200% 확대에서 measure·caption·표가 읽히고, 회색조에서 위계가 보이는지 |
| Expressive | 기억할 장면 하나를 만들되 내용의 읽기 순서를 유지한다. 비대칭·겹침·강한 타입은 한 개의 중심 개념에 복무한다 | 개성 강한 display와 안정 본문을 구분; 높은 대비 색·질감은 근거 있는 한정 영역에 | reduced motion에서 같은 의미를 정지 상태로 전달 | 캠페인·문화·에이전시처럼 독자적 인상이 과업일 때 | 장치를 여러 개 겹쳐 메시지·포커스·대비를 잃음 | 효과 off, keyboard, 390px에서 메시지·행동이 먼저 남는지 |
| Luxury | 희소성·재료·제작 맥락을 느리게 보여 준다. 선택지는 적고 증거는 깊다 | 절제된 팔레트, 정교한 type, 원본 수준 사진·재질; 장식보다 크롭과 상세에 투자 | 느린 전환도 사용자가 기다리는 흐름에서만 | 고관여 제품·공간·문화재처럼 신뢰와 감상이 함께 필요할 때 | 베이지·세리프·그레인을 고급의 자동 신호처럼 반복; 가짜 희소성/후기 | 이미지 출처·해상도·alt, 가격/조건의 명확성, 모션 off에서의 설득 |
| Playful | 탐색과 학습의 긴장을 낮춘다. 핵심 행동은 여전히 예측 가능해야 한다 | 밝은 색은 의미 역할을 분리하고, 일러스트/캐릭터는 과업 단서를 보강 | 작은 보상·피드백은 가능하나 중지 가능성과 오류 회복을 제공 | 교육·커뮤니티·가벼운 소비 경험에서 놀이가 목표를 돕는 경우 | 재미가 상태·가격·위험 신호를 가림 | 처음 방문자가 다음 행동·오류 복구를 설명할 수 있는지, reduced motion |
| Immersive | 공간·시간·감각적 몰입을 우선하되 대체 경로를 제공한다. 하나의 장면에서 깊이를 만든다 | 큰 원본 미디어, 제한된 overlay text, 읽을 수 있는 대비와 대체 콘텐츠 | WebGL/스크롤 연출은 progressive enhancement; pause·fallback 필수 | 전시·게임·영화·브랜드 스토리처럼 경험 자체가 가치일 때 | 로딩·멀미·배터리 비용을 감추고, 마우스/고성능 기기만 지원 | 저사양/느린 망/키보드/reduced motion에서 동일 핵심 정보와 행동 가능 여부 |
| Dense | 빈번한 비교·감시·편집을 빠르게 한다. 요약→필터→상세의 정보 계층을 명시한다 | 가독성 높은 본문·tabular 수치·기능색·표/차트의 명확한 라벨 | 값 변화는 위치 이동보다 상태 표식; 빠른 피드백 | 운영 도구·분석·거래처럼 반복 과업과 숙련 사용자가 있을 때 | 미니멀이라는 이유로 필터·단위·오류 맥락을 숨김; 색만으로 상태 전달 | 대표 과업 시간, 200% 확대, keyboard, 좁은 폭의 우선순위/대체 경로 |
## 계열별 빠른 선택 예와 오용
### Swiss / editorial
- **구도**: Swiss는 같은 시작선의 명세·비교 레일을, editorial은 본문 폭·wide 이미지·여백 주석의 리듬을 선택한다.
- **밀도·타입**: Swiss는 짧은 산세 본문과 촘촘한 비교를, editorial은 세리프 또는 산세 중 언어별 읽기 테스트를 통과한 본문과 캡션 계층을 쓴다.
- **오용**: Swiss의 빈 공간으로 필수 사양을 숨기거나, editorial의 종이색·그레인을 콘텐츠 근거 없이 고급 신호로 반복하지 않는다.
### Expressive
- **구도**: 한 장면만 비대칭·중첩·대형 타입으로 만들고, 이후 정보는 안정된 grid로 회수한다.
- **밀도·타입**: display의 개성과 본문의 읽기 역할을 분리하며, 강조색·질감·변형은 하나의 중심 아이디어에만 쓴다.
- **오용**: 회전·세로쓰기·대문자·왜곡을 함께 쌓아 포커스 순서와 메시지를 잃지 않는다.
### Luxury
- **구도**: 여백 속 하나의 제품/재료 detail과 근거·조건이 이어지는 느린 순서를 쓴다.
- **밀도·타입**: 큰 원본 사진은 크롭 품질을 우선하고, 가격·제작 방식·규격은 작은 글자로 숨기지 않는다.
- **오용**: 베이지, 세리프, grain을 자동 고급 장치로 쓰거나 검증되지 않은 희소성·후기를 만들지 않는다.
### Playful
- **구도**: 다음 행동과 진행 상태를 고정하고, 그 주변에서 색·캐릭터·보상 피드백을 쓴다.
- **밀도·타입**: 행동에는 명확한 라벨과 충분한 hitbox를 두며, 일러스트는 과업 단서를 설명할 때만 쓴다.
- **오용**: 게임 같은 보상이 오류·가격·동의·파괴적 행동의 위험 신호를 가리지 않게 한다.
### Immersive
- **구도**: 한 장면의 미디어와 짧은 overlay를 중심으로 두고, 동일 내용을 읽을 수 있는 문서 흐름을 제공한다.
- **밀도·타입**: 크고 원본 품질의 미디어를 쓸 때 contrast·caption·대체 텍스트를 표면의 일부로 설계한다.
- **오용**: WebGL·스크롤 연출을 핵심 정보의 유일한 전달 경로나 고성능 기기 전용 상호작용으로 만들지 않는다.
### Dense
- **구도**: 요약→필터→상세와 비교 가능한 표·차트를 같은 정보 모델로 연결한다.
- **밀도·타입**: tabular figure, 단위·기간 라벨, 기능색, 안정된 행 높이로 반복 과업의 속도를 돕는다.
- **오용**: ‘깔끔함’을 이유로 필터·오류·데이터 출처·단위를 감추거나 색만으로 상태를 전달하지 않는다.
## 사례와 트렌드의 사용법
수상 사례는 완성된 화면을 베끼는 재료가 아니라 제약·구도·사용자 경로를 분해할 표본이다. Awwwards의 40/30/20/10은 그 심사 시스템의 배점일 뿐 효과 크기가 아니다 [AWWWARDS]. Webby와 CSSDA 사례도 심사/제작사 기록으로만 사용한다 [WEBBY, CSSDA-UNSEEN, AREA17, DROPBOX]. iF trend report는 가설 목록이며 웹 제품의 성과 증거가 아니다 [IFD-TREND].
사례를 채택하기 전에는 다음 다섯 가지를 한 줄씩 기록한다: `원래 대상과 과업`, `가져올 구조 또는 재질`, `현재 브리프에 맞는 이유`, `접근성·성능 비용`, `동일 조건 렌더 검증`. 이 기록이 없으면 사례는 장식 참조로만 남긴다.

View file

@ -383,7 +383,7 @@ SVG 필터는 장식이 아니라 재질을 만드는 도구다. "예뻐 보이
### 필터를 애니메이션하지 마라
필터 값이 바뀌면 매 프레임 전체가 재계산된다. **SKILL.md 하드 게이트 8(`transform`/`opacity` 외 애니메이션 금지)에도 걸린다.** 필터는 정적으로 쓴다.
필터 값이 바뀌면 매 프레임 전체가 재계산될 수 있다. `transform`·`opacity`가 저비용 기본 선택이므로 필터는 정적 사용을 먼저 검토한다. 필터 애니메이션을 채택하면 프레임 시간·입력 간섭·감소 모션·프로젝트 예산을 실제 렌더에서 측정하고 기록한다.
| 하고 싶은 것 | 대신 할 것 |
|---|---|
@ -438,7 +438,7 @@ if (reduceEffects()) document.documentElement.classList.add('reduce-effects');
- [ ] 필터 영역이 최소 크기이고, 잘리는 곳이 없다. `numOctaves` ≤ 4
- [ ] 그레인 opacity ≤ 0.15 (anti-grid 제외)
- [ ] `feImage`를 썼다면 data URI다
- [ ] **애니메이션되는 필터가 없다**(하드 게이트 8). `will-change: filter`도 없다
- [ ] 애니메이션되는 필터가 있다면 프레임 시간·입력 간섭·감소 모션·프로젝트 예산을 실제 렌더에서 측정했다. `will-change: filter`의 불필요한 사용은 없다
- [ ] `backdrop-filter`가 뷰포트 전체를 덮지 않는다
- [ ] 필터를 전부 끈 상태에서 페이지가 완성돼 있다
- [ ] 필터 건 요소 안에 본문 텍스트나 `position: fixed` 자손이 없다
@ -485,7 +485,7 @@ if (reduceEffects()) document.documentElement.classList.add('reduce-effects');
`conic-gradient(from var(--angle), …)` 를 `@property` 로 애니메이션하는 방법이 흔히 보이는데,
**그건 매 프레임 배경을 다시 칠한다.** 컴포지터에서 못 돌고 메인 스레드가 페인트한다.
`transform`·`opacity` 만 애니메이션하라는 규칙에 걸린다.
저비용 기본 선택에서 벗어나는 사례다. 목적과 성능 측정, 감소 모션 경로를 프로젝트 계약에 기록한다.
**정사각형 원뿔 레이어를 마스크 안에서 회전시켜라.** 움직이는 것은 `rotate` 뿐이다.

View file

@ -17,7 +17,7 @@
| Perfect Fourth | 1.333 | 또렷한 위계 | 랜딩 페이지, 제품 소개 |
| Golden | 1.618 | 극적 | 포트폴리오, 에디토리얼. 중간 단계가 비어 본문이 외로워진다 |
**한 페이지에 비율은 하나다.** 헤드라인만 다른 비율을 쓰고 싶으면 그건 비율이 아니라 **디스플레이 사이즈를 따로 정의**하는 것이다.
본문 scale은 하나의 비율에서 시작하면 관리하기 쉽다. display가 다른 비율이나 별도 크기를 요구할 수 있으므로, 토큰 이름·역할·대표 폭에서의 위계 검증을 함께 기록한다.
### 실제 값으로 적는다
@ -34,12 +34,12 @@
}
```
### 반응형은 clamp 로, 미디어쿼리로 하지 마라
### 유동 크기와 구조 전환을 구분한다
```css
--step-4: clamp(2.25rem, 1.5rem + 3.75vw, 3.157rem);
```
`clamp(최소, 기준+vw, 최대)`. 최소값은 **모바일에서 읽히는 크기**, 최대값은 데스크톱 기준. 중간이 매끄럽게 이어져 중간 뷰포트에서 깨지지 않는다.
`clamp(최소, 기준+vw, 최대)`는 연속적으로 변해야 하는 값에 유용하다. 최소·최대는 대표 콘텐츠로 확인한다. grid 열, 내비, 작업대처럼 구조가 바뀌는 지점에는 미디어쿼리나 container query를 쓰고, 임의 기기 폭이 아니라 실제 실패 지점에서 정한다.
### 함께 정해야 하는 것
@ -48,9 +48,9 @@
| `--leading-tight` | 1.1~1.2 — 디스플레이/헤드라인 |
| `--leading-normal` | 1.5~1.6 — 본문 (라틴) / **1.6~1.8 (한글)** |
| `--measure` | 한 줄 길이. 라틴 60~75자, **한글 25~40자** |
| 자간 | 큰 글자에만 음수(`-0.02em` 정도). **한글에는 음수 자간 금지** |
| 자간 | 큰 라틴 디스플레이에서만 작은 음수 자간을 후보로 두고 실제 렌더로 확인. 한글은 글꼴·크기·문맥별로 시험하며, 음수값을 기본 처방이나 절대 금지로 두지 않는다 |
**폰트는 최대 2종**(+ 코드용 모노 1). 세 번째 텍스트 폰트를 넣고 싶다면 위계를 웨이트로 못 만들고 있다는 신호다.
폰트 수는 전송량·언어 범위·위계·라이선스를 함께 보고 예산으로 정한다. 두 텍스트 패밀리와 필요 시 모노는 흔한 시작점이지만, 세 번째 패밀리가 무조건 실패라는 뜻은 아니다.
> **어떤 폰트를 어떻게 고르고 싣는지는 `typography.md` 가 정본이다.** 여기서는 스케일 값만 정한다.
> 로딩 전략, 폴백 메트릭 보정, OpenType 기능, 한글 서브셋, 라이선스 확인이 그 문서에 있다.
@ -70,16 +70,16 @@
--ink: /* 본문 텍스트 */
--ink-muted: /* 보조 텍스트 — 대비 4.5:1 유지 */
--line: /* 경계선 */
--accent: /* 강조 — 페이지당 하나 */
--accent: /* 강조 역할이 필요할 때의 기본 토큰 */
--accent-ink: /* 강조 위에 올라가는 글자색 */
}
```
### 규칙
1. **강조색은 하나다.** 두 개가 필요하다고 느끼면 위계 설계가 실패한 것이다. 예외: 상태색(성공/경고/오류)은 강조색이 아니라 기능색이다
1. 강조 역할은 한 색에서 시작해 경쟁 여부를 검증한다. 여러 강조 역할이 필요할 수 있으며, 그때는 행동·상태·정보 목적을 역할 토큰으로 구분한다. 수만으로 위계 실패를 판정하지 않는다
2. **채도가 높은 색은 면적을 좁게.** 넓은 면적에 쓰면 눈이 피로하고 싸구려로 보인다
3. **중성색도 색이다.** 순수 회색(`#808080`) 대신 강조색 쪽으로 약간 기운 중성색을 쓰면 화면 전체가 하나로 묶인다
3. **중성색도 색이다.** 순수 회색과 색조를 띤 중성색을 모두 후보로 두고, 표면·브랜드·상태색과의 관계를 비교한다
4. **대비를 측정해라.** 본문 4.5:1, 큰 글자 3:1. 눈으로 판단하지 마라
### 다크 모드
@ -88,9 +88,9 @@
다크를 기본으로 하려면 **근거를 한 줄로 대라.** 정당화되는 이유의 목록은 `presets/dark-instrument.md` 에 있다(야간 운영 환경, 밝은 데이터 시각화의 대비, 제품 자체가 어두움). 댈 수 없으면 라이트로 간다.
어느 쪽을 기본으로 하든 **두 테마를 동등하게 정의한다.** 다크만 만들고 라이트를 빼는 것은 사용자 선택권을 뺏는 것이다.
라이트/다크 모두를 지원하기로 한 제품은 역할 토큰을 각각 정의하고 실제 상태를 검증한다. 제품이 한 테마만 제공할 수는 있지만, 사용자 환경·브리프·운영 맥락과 접근성 영향을 명시한다.
지원할 때는 색을 뒤집는 게 아니라 **역할별로 다시 정의**한다. 다크에서 순수 검정(`#000`)은 대비가 너무 세서 눈이 아프고, 순수 흰색 텍스트도 마찬가지다.
지원할 때는 색을 뒤집는 게 아니라 **역할별로 다시 정의**한다. 순수 검정·흰색과 근접한 색은 제품·디스플레이·주변광에 따라 읽기 경험이 다르므로, 대비·눈부심·브랜드 표면을 실제 기기에서 확인한다.
```css
:root { --surface: #fbfaf8; --ink: #1a1917; }
@ -123,10 +123,10 @@
### 여백이 위계를 만든다
- 관련 있는 것끼리는 **가깝게**, 다른 그룹과는 **확실히 멀게**. 애매한 중간 간격이 가장 나쁘다
- 섹션 간격은 **본문 간격의 4배 이상**. 좁으면 페이지가 뭉개진다
- 섹션·그룹·요소 간 간격 차이가 관계를 읽히게 하는지 실제 화면에서 본다. 4배 같은 고정 비율은 시작 가설일 뿐, 콘텐츠와 폭에 따라 조절한다.
- 요소를 정렬할 때 **간격이 아니라 정렬선**을 먼저 맞춰라
### h1 이 h2 보다 한 단계만 크면 위계가 아니다
### 제목 역할이 실제로 구분되는지 확인한다
히어로 제목을 줄이다가 실측에서 이렇게 됐다.
@ -142,10 +142,9 @@
배경 그래픽과 싸워서 줄였는데, 줄이다가 h2 와 붙는 데까지 왔다.
**한 극단에서 도망치다 반대쪽 극단에 도착한 것이다.**
기준: **넓은 화면에서 히어로 h1 은 섹션 h2 의 2 배 안팎.** 그 아래면 위계가 없고,
그 위는 배경·여백과 싸우기 시작한다.
넓은 화면에서 히어로와 섹션 제목이 같은 위계로 읽히지 않는지 비교한다. 2배 안팎은 한 프로젝트의 관찰값일 뿐 기준선이 아니다. 문구 길이, 구도, 여백, 브랜드 타입에 따라 같은 크기 차도 다르게 보인다.
### clamp 의 하한은 h2 를 기준으로 정한다
### clamp 값을 역할 관계와 함께 확인한다
여기서 한 번 더 틀렸다. 데스크톱만 보고 고쳤더니 이렇게 됐다.
@ -162,7 +161,7 @@
**h1 만 `vw` 에 비례해 줄고 h2 는 고정이라, 좁아질수록 둘이 만난다.**
데스크톱에서 고친 위계가 모바일에서 그대로 사라진다.
하한을 h2 의 1.4 배 이상으로 잡으면 전 구간이 유지된다.
모바일에서 h1과 h2가 거의 같은 크기로 수렴하지 않는지 확인한다. 아래 값은 해당 조합의 예시이며 모든 제목에 적용하는 비율 규칙이 아니다.
```css
/* 하한 3rem = 48px = h2(33.2px)의 1.45 배 */
@ -174,7 +173,7 @@
| 비율 | 1.45 | 1.45 | 1.63 | 1.87 | 2.12 |
좁은 화면에서 2 배를 고집할 필요는 없다 — 폭이 좁으면 큰 글자가 줄만 늘린다.
**1.4 배가 하한, 2 배 안팎이 상한**이고 그 사이를 `vw` 가 잇는다.
최소·중간·최대 폭에서 제목 역할이 구분되는지 확인하고, 필요한 경우 h1과 h2를 함께 유동화하거나 문구·레이아웃을 조정한다.
> 확인법: 한 화면 폭에서만 재지 마라. `clamp` 를 쓴 값은 **최소 폭·중간 폭·최대 폭
> 세 곳에서 계산해 보고**, 같이 변하지 않는 값(고정 스케일의 h2 등)과의 비율을 봐라.
@ -291,12 +290,12 @@ h3 + *,
### 버튼처럼 생긴 것은 전부 같은 치수를 쓴다
```css
--control-h: 2.75rem; /* 44px — 터치 타깃 */
--control-h: 2.75rem; /* 프로젝트의 편안한 기본 높이 예시 */
--control-h-sm: 2.25rem; /* 36px — 헤더 등 조밀한 자리 */
--control-pad-x: var(--space-3);
```
컴포넌트마다 높이와 패딩을 다시 정하면 그때부터 SSOT 가 아니다.
같은 역할의 컴포넌트는 치수 토큰을 공유한다. 다른 밀도나 위험도는 별도 역할 토큰으로 설명한다. 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` 로 **픽셀 단위까지 같았다.**
@ -314,7 +313,7 @@ h3 + *,
### 큰 간격은 화면에 반응해야 한다
작은 값(4~24px)은 고정, 큰 값만 `clamp()`.
작은 값은 고정값부터, 큰 공간은 `clamp()`부터 검토할 수 있다. 어느 쪽이든 콘텐츠·확대·컨테이너 조건에서 관계가 유지되는지 확인한다.
```css
--space-6: clamp(2.5rem, 1.6rem + 2.6vw, 4rem);
@ -339,16 +338,16 @@ h3 + *,
| WebGL/3D 추가분 | +200KB | +600KB | 측정 |
| 총 전송량 (첫 화면) | 1MB | 2MB | 측정 |
| 첫 인터랙션 (모바일 4G) | 3초 | 5초 | 측정 |
| LCP | 2.5초 | 3.5초 | 측정 |
| CLS | 0.1 | 0.1 | 측정 |
| 폰트 **패밀리** | 2개 이하 | 3개 | 개수 확인 |
| LCP | field p75 2.5초 이하를 목표로 검토 | 브리프에 명시 | RUM/CrUX 또는 local proxy 표기 |
| CLS | field p75 0.1 이하를 목표로 검토 | 브리프에 명시 | RUM/CrUX 또는 local proxy 표기 |
| 폰트 **패밀리** | 프로젝트별 전송량·언어 범위 예산 | 브리프에 명시 | 실제 요청·전송량 확인 |
| 폰트 전송량(첫 화면) | 100KB | 200KB | 측정 |
| 애니메이션 속성 | transform/opacity 만 | 동일 (타협 없음) | grep |
| 애니메이션 속성 | compositing 친화 속성을 우선 검토 | 브리프에 명시 | 성능·reduced-motion·상태 피드백 확인 |
예산을 올렸다면 **올렸다는 사실과 이유를 명시**해라. 조용히 넘기는 것이 가장 나쁘다.
> **폰트는 파일 개수가 아니라 패밀리 수와 전송량으로 센다.** 한글 웹폰트를 유니코드 범위별로
> 수십 개 파일로 쪼개는 것은 **올바른 최적화**다(Pretendard `dynamic-subset` 은 14개 이상).
> 수십 개 파일로 쪼개는 방식은 실제 사용 문자 범위·캐시·전송 waterfall에 따라 유효할 수 있다. 파일 개수 자체로 최적화를 판정하지 않는다.
> 브라우저는 페이지에 실제로 쓰인 글자 범위만 받는다. 파일 개수를 줄이라고 요구하면
> 한글 프로젝트를 단일 대용량 파일이라는 잘못된 방향으로 몬다.
@ -372,8 +371,7 @@ new PerformanceObserver((l) => {
</script>
```
**프로덕션 빌드에 돌려라.** 개발 서버는 번들이 다르고 HMR 스크립트가 섞여 값이 의미 없다.
Lighthouse 를 쓸 수 있으면 그쪽이 더 정확하다 — 단 모바일 프로파일로.
프로덕션 빌드에서 재는 편이 개발 서버보다 배포 조건에 가깝다. 다만 PerformanceObserver·Lighthouse 한 번은 **lab proxy**다. CWV의 적합성 주장은 실제 사용자의 field data에서 모바일·데스크톱별 75th percentile로 판단한다. local 값은 회귀 탐지에 쓰고 field 수치와 혼동하지 않는다 (`evidence-ledger.md`의 `CWV`, `CWV-METHOD`).
### 예산을 지키는 기본 수단

View file

@ -22,9 +22,7 @@
### 1-1. 먼저 개수를 정한다
**두 벌이면 충분하다.** 디스플레이 하나, 본문 하나. 코드나 수치가 있으면 모노 하나를 더한다.
세 번째 텍스트 폰트를 넣고 싶다면 **위계를 웨이트로 못 만들고 있다는 신호다.** 가변 폰트 한 벌의 300~700 구간이 서로 다른 폰트 두 벌보다 넓은 표현을 준다.
디스플레이 하나와 본문 하나, 필요할 때 모노 하나는 관리하기 쉬운 시작점이다. 추가 패밀리는 언어 범위·역할·전송량·라이선스와 실제 위계 효과를 근거로 제한한다. 가변 폰트가 정적 파일보다 항상 작거나 모든 필요한 축을 제공하는 것은 아니므로 실제 subset과 로딩 결과를 비교한다.
### 1-2. 성격을 브리프에서 끌어낸다
@ -35,13 +33,13 @@
| 정확함, 계기, 데이터 | 좁은 폭, 낮은 대비, 열린 카운터, 모노와 잘 붙는 것 |
| 편집, 읽는 시간, 권위 | 세리프 또는 고대비 산세, 넉넉한 x-height |
| 도구, 중립, 신뢰 | 그로테스크. 성격은 웨이트와 여백이 만든다 |
| 인상, 기억 | 디스플레이 전용 서체. **본문에는 절대 쓰지 않는다** |
| 인상, 기억 | 디스플레이 전용 서체를 우선 검토한다. 본문 사용은 긴 읽기·언어 범위·확대 상태에서 검증한 경우에만 채택한다 |
### 1-3. 후보를 실제 값으로 검증한다
이름과 인상으로 고르지 마라. 다음 넷을 본다.
- **x-height 비율** — 소문자 높이 ÷ 대문자 높이. 0.52 미만이면 작은 크기에서 흐려진다
- **x-height 비율** — 소문자 높이와 본문 크기의 관계. 단일 임계값으로 가독성을 판정하지 말고 실제 크기·언어·렌더링에서 확인한다
- **대비** — 획 굵기 차이. 큰 크기에서만 살아나는 폰트가 있다
- **웨이트 범위** — 필요한 웨이트가 실제로 있는가. 없는 웨이트를 브라우저가 합성하면 뭉개진다
- **숫자 형태** — 라이닝인가 올드스타일인가, tabular 가 있는가
@ -66,7 +64,7 @@ document.fonts.check('16px "Pretendard Variable"');
## 2. 가변 폰트
웨이트를 **두 개 이상** 쓰면 거의 항상 가변이 유리하다. 정적 폰트 두 벌보다 가변 한 벌이 작고, 중간 웨이트를 공짜로 얻는다.
여러 웨이트가 필요하면 가변 폰트를 후보로 둔다. 필요한 glyph·축·subset·캐시 조건에 따라 정적 파일 묶음이 더 작거나 호환성이 나을 수 있으므로 전송량과 실제 렌더를 비교한다.
### 축
@ -144,13 +142,13 @@ h1 { font-optical-sizing: auto; }
| 셀프 호스팅 | 요청 도메인 하나, 캐시 통제, 서드파티 장애 무관 | 업데이트를 직접 한다 |
| CDN | 설치가 한 줄 | 도메인이 늘고(각각 DNS+TLS), 그 서비스가 죽으면 글자가 죽는다 |
CDN 을 쓴다면 `preconnect` 를 반드시 건다. 셋 이상의 폰트 도메인은 그 자체가 결함이다.
CDN을 쓸 때 `preconnect`는 실제 첫 화면 요청·연결 비용을 측정한 뒤 선택한다. 도메인 수만으로 결함을 판정하지 말고, 권한·캐시·장애 경로·전송 waterfall을 함께 검토한다.
---
## 4. 폴백 메트릭 — 가장 자주 빠뜨리는 곳
폰트가 늦게 오면 브라우저는 폴백으로 먼저 그리고 나중에 교체한다. 두 폰트의 **행 높이와 글자 폭이 다르면 그 순간 레이아웃이 튄다.** 이것이 CLS 의 흔한 원인이고, `font-display: swap` 을 쓰는 한 반드시 일어난다.
폰트가 늦게 오면 표시 전략에 따라 폴백과 웹폰트가 교체될 수 있다. 두 폰트의 행 높이·폭이 다르면 레이아웃 이동의 원인이 될 수 있지만, `swap`이 반드시 CLS를 만든다고 단정하지 않는다. 실제 네트워크 조건에서 측정한다.
해결은 **폴백 폰트를 실제 폰트의 치수에 맞추는 것**이다.
@ -196,7 +194,7 @@ const measure = (family) => {
`ascent-override` / `descent-override` 는 실제 폰트의 `hhea` 또는 `OS/2` 값을 `unitsPerEm` 으로 나눈 백분율이다. 도구가 없으면 **행 높이가 눈에 띄게 튀지 않을 때까지 조정**하고 그 값을 `design.md` 에 적어라.
> `font-display: optional` 을 쓰면 이 문제가 사라진다. 대신 첫 방문자가 브랜드 서체를 못 볼 수 있다. **둘 중 하나는 골라야 한다.** 아무것도 안 하는 것이 최악이다.
> `font-display: optional`은 교체 가능성을 줄이는 한 선택지다. 폴백 메트릭 보정, 표시 전략, preload 여부는 실제 첫 방문·재방문·느린 망에서 조합해 검증한다.
---
@ -240,7 +238,7 @@ body { font-kerning: normal; }
### 6-1. 서브셋은 선택이 아니다
한글 완성형은 11,172자다. 전체 폰트는 수 MB 다. 반드시 쪼갠다.
한글 완성형은 11,172자다. 전체 폰트가 클 수 있으므로 subset 전략을 검토한다. 모든 프로젝트가 글자 빈도 기반의 동적 subset을 반드시 써야 하는 것은 아니며, 콘텐츠 범위·cache·운영 복잡도와 실제 전송량으로 결정한다.
**쪼개는 단위가 라틴과 다르다.** 라틴은 `unicode-range` 로 블록을 나누면 되지만, 한글은 자주 쓰는 글자가 블록에 흩어져 있다. 그래서 **글자 빈도로 나눈 수십 개 조각**을 만들고 각각에 `unicode-range` 를 건다. 브라우저가 페이지에 실제로 쓰인 조각만 받는다.
@ -313,13 +311,13 @@ font-family: "Switzer", "Pretendard Variable", system-ui, sans-serif;
## 8. 배포 전 체크리스트
- [ ] 폰트 패밀리 수가 예산 안이다(`tokens.md` §5). 텍스트 두 벌 + 모노 하나가 상한
- [ ] 폰트 패밀리 수와 실제 첫 화면 전송량이 프로젝트 예산 안이다(`tokens.md` §5)
- [ ] **"왜 이 폰트인가"에 한 문장으로 답할 수 있다**
- [ ] 웨이트를 두 개 이상 쓰는데 정적 폰트를 여러 벌 받고 있지 않다
- [ ] 필요한 웨이트의 가변/정적 후보를 실제 subset 전송량·지원 범위로 비교했다
- [ ] 없는 웨이트를 브라우저가 합성하고 있지 않다(`font-synthesis-weight: none` 으로 확인)
- [ ] `font-display` 를 의식적으로 골랐다
- [ ] preload 한 파일이 첫 화면에 실제로 쓰인다. `crossorigin` 이 있다
- [ ] **폴백 메트릭을 보정했거나, `optional` 을 골랐다.** 둘 중 하나는 했다
- [ ] 실제 첫 방문·재방문·느린 망에서 폴백 메트릭과 `font-display` 선택이 의도한 줄바꿈·레이아웃을 유지한다
- [ ] 수치를 나열하는 곳에 `tabular-nums` 가 있다
- [ ] 한글이 있으면 서브셋을 쓴다. 파일 개수가 아니라 전송량으로 쟀다
- [ ] 라틴 폰트가 폴백 스택 앞에 있다

View file

@ -0,0 +1,72 @@
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import url from "node:url";
const repo = path.resolve(path.dirname(url.fileURLToPath(import.meta.url)), "../../..");
const gate = path.join(repo, "apps/site/tools/design-gate.mjs");
const work = fs.mkdtempSync(path.join(os.tmpdir(), "design-gate-contract-"));
const configFor = (page, extra = {}) => ({
pages: [{ name: "fixture", path: page, h1: "h1", ...extra }],
widths: [375, 390],
fontScales: [1, 1.3],
checks: {
meta: false,
contrast: false,
stack: false,
rhythm: false,
scaleMatrix: true,
tapTargets: false,
leftInset: false,
deadCss: false,
cssHygiene: false,
},
});
const run = (name, html, extra = {}) => {
const page = path.join(work, `${name}.html`);
const config = path.join(work, `${name}.json`);
fs.writeFileSync(page, html.replace("<!doctype html>", "<!doctype html><style>html { overflow-x: clip; } body { margin: 0; }</style>"));
fs.writeFileSync(config, JSON.stringify(configFor(page, extra)));
return spawnSync(process.execPath, [gate, `--config=${config}`], { cwd: work, encoding: "utf8" });
};
const outputOf = (result) => result.stdout + result.stderr;
try {
const flexible = run("flexible", `<!doctype html><style>h1 { width: 9ch; font-size: 2rem; }</style><h1>A flexible display heading</h1>`);
assert.equal(flexible.status, 0, outputOf(flexible));
assert.match(outputOf(flexible), /h1 보임 — [2-9]줄\(줄수 계약 없음\)/);
const capped = run("capped", `<!doctype html><style>h1 { width: 100px; font-size: 2rem; }</style><h1>Heading that must wrap</h1>`, { h1MaxLines: 1 });
assert.equal(capped.status, 1, outputOf(capped));
assert.match(outputOf(capped), /h1 — \d+줄\(최대 1줄\)/);
const expanded = run("expanded", `<!doctype html><style>h1 { width: 260px; font-size: 1.5rem; }</style><h1>Scale tolerant design</h1>`, { h1MaxLines: 1 });
assert.equal(expanded.status, 0, outputOf(expanded));
assert.match(outputOf(expanded), /375@1x:1줄/);
assert.match(outputOf(expanded), /375@1\.3x:[2-9]줄/);
const clipped = run("clipped", `<!doctype html><style>.clip { width: 150px; overflow: hidden; } h1 { white-space: nowrap; font-size: 1.5rem; }</style><div class="clip"><h1>Heading that is clipped when enlarged</h1></div>`);
assert.equal(clipped.status, 1, outputOf(clipped));
assert.match(outputOf(clipped), /제목 텍스트 클리핑/);
const verticallyClipped = run("vertically-clipped", `<!doctype html><style>.clip { width: 300px; height: 32px; overflow-y: hidden; } h1 { margin: 0; font-size: 1.5rem; }</style><div class="clip"><h1>Title that becomes vertically clipped</h1></div>`);
assert.equal(verticallyClipped.status, 1, outputOf(verticallyClipped));
assert.match(outputOf(verticallyClipped), /제목 텍스트 클리핑 .+\(y\)/);
const missing = run("missing", `<!doctype html><h1>Existing heading</h1>`, { h1: ".missing" });
assert.equal(missing.status, 1, outputOf(missing));
assert.match(outputOf(missing), /h1 선택자 없음 \(\.missing\)/);
const empty = run("empty", `<!doctype html><h1></h1>`);
assert.equal(empty.status, 1, outputOf(empty));
assert.match(outputOf(empty), /h1 — 텍스트 없음/);
console.log("design-gate-contract: 7 passed");
} finally {
fs.rmSync(work, { recursive: true, force: true });
}

View file

@ -26,7 +26,6 @@ const DEFAULT_CFG = {
viewAttribute: "data-view",
views: ["main"],
h1: "h1",
h1MaxLines: 1,
brand: ".brand",
},
],
@ -233,6 +232,53 @@ const UTILS = `
const text = [...el.childNodes].find((node) => node.nodeType === 3 && node.textContent.trim());
return window.__lines(text || el);
};
window.__heading = (sel) => {
let el;
try { el = document.querySelector(sel); }
catch { return { state: "invalid" }; }
if (!el) return { state: "missing" };
if (!el.textContent.trim()) return { state: "empty" };
const box = el.getBoundingClientRect();
const hiddenAncestor = (() => {
for (let node = el; node && node !== document.documentElement; node = node.parentElement) {
const style = getComputedStyle(node);
if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse" || style.contentVisibility === "hidden" || parseFloat(style.opacity) <= 0) return true;
}
return false;
})();
if (hiddenAncestor || box.width <= 0 || box.height <= 0) {
return { state: "hidden" };
}
const range = document.createRange();
range.selectNodeContents(el);
const rects = [...range.getClientRects()].filter((r) => r.width > 1 && r.height > 4);
const tops = rects.map((r) => r.top).sort((a, b) => a - b);
const lines = [];
for (const top of tops) if (!lines.length || top - lines[lines.length - 1] > 5) lines.push(top);
return { state: "visible", lines: lines.length, rects: rects.map((r) => ({ left: r.left, right: r.right, top: r.top, bottom: r.bottom })) };
};
window.__headingScaleAudit = (sel) => {
const heading = window.__heading(sel);
if (heading.state !== "visible") return heading;
const el = document.querySelector(sel);
const clipping = [];
const viewportWidth = document.documentElement.clientWidth;
for (const rect of heading.rects) {
if (rect.left < -1 || rect.right > viewportWidth + 1) clipping.push("viewport");
}
for (let ancestor = el; ancestor && ancestor !== document.documentElement; ancestor = ancestor.parentElement) {
const cs = getComputedStyle(ancestor);
const box = ancestor.getBoundingClientRect();
const name = ancestor.className ? "." + String(ancestor.className).trim().split(/\\s+/)[0] : ancestor.tagName.toLowerCase();
if (/^(hidden|clip|auto|scroll)$/.test(cs.overflowX) && heading.rects.some((rect) => rect.left < box.left - 1 || rect.right > box.right + 1)) clipping.push(name + "(x)");
if (/^(hidden|clip|auto|scroll)$/.test(cs.overflowY) && heading.rects.some((rect) => rect.top < box.top - 1 || rect.bottom > box.bottom + 1)) clipping.push(name + "(y)");
}
return {
...heading,
documentOverflow: document.documentElement.scrollWidth - document.documentElement.clientWidth,
clipping: [...new Set(clipping)],
};
};
`;
const browser = await puppeteer.default.launch({ executablePath: CHROME, headless: "new", args: ["--force-device-scale-factor=1"] });
@ -326,14 +372,19 @@ for (const p of CFG.pages) {
}
overs.length ? fail(`${p.name}/${v} 오버플로`, overs.join(" ")) : pass(`${p.name}/${v} 오버플로`, CFG.widths.join("/"));
// h1 줄 수 — 앱 화면은 기본 1줄, 마케팅 헤드라인은 페이지별로 최대 2줄까지 명시할 수 있다.
// 제목은 항상 존재·가시성을 확인하고, 줄 수는 페이지가 명시한 계약일 때만 검사한다.
await page.setViewport({ width: 1440, height: 900 });
const h1l = await page.evaluate(`__lines(${JSON.stringify(p.h1 || "h1")})`);
const h1MaxLines = Number.isInteger(p.h1MaxLines) && p.h1MaxLines > 0 ? p.h1MaxLines : 1;
const h1Rule = h1MaxLines === 1 ? "h1 1줄" : `h1 최대 ${h1MaxLines}줄`;
h1l >= 1 && h1l <= h1MaxLines
? pass(`${p.name}/${v} ${h1Rule}`)
: (h1l > h1MaxLines ? fail(`${p.name}/${v} h1 ${h1l}줄(최대 ${h1MaxLines}줄)`) : skip(`${p.name}/${v} h1`, "요소 없음"));
const h1Selector = p.h1 || "h1";
const h1 = await page.evaluate((sel) => window.__heading(sel), h1Selector);
const h1MaxLines = Number.isInteger(p.h1MaxLines) && p.h1MaxLines > 0 ? p.h1MaxLines : null;
if (h1.state === "missing") fail(`${p.name}/${v} h1`, `선택자 없음 (${h1Selector})`);
else if (h1.state === "empty") fail(`${p.name}/${v} h1`, "텍스트 없음");
else if (h1.state === "hidden") fail(`${p.name}/${v} h1`, `선택자가 숨김 (${h1Selector})`);
else if (h1.state === "invalid") fail(`${p.name}/${v} h1`, `잘못된 선택자 (${h1Selector})`);
else if (h1.lines < 1) fail(`${p.name}/${v} h1`, "텍스트 없음");
else if (h1MaxLines !== null && h1.lines > h1MaxLines) fail(`${p.name}/${v} h1`, `${h1.lines}줄(최대 ${h1MaxLines}줄)`);
else if (h1MaxLines !== null) pass(`${p.name}/${v} h1 최대 ${h1MaxLines}줄`, `${h1.lines}줄`);
else pass(`${p.name}/${v} h1 보임`, `${h1.lines}줄(줄수 계약 없음)`);
// 세로 쌓임(수축) — 짧은 라벨 3줄 이상
if (CFG.checks.stack) {
@ -430,21 +481,34 @@ for (const p of CFG.pages) {
}
}
// ── 스케일×폭 h1 행렬 (안드로이드 글꼴 확대 재현) ──
// ── 스케일×폭 제목 행렬 (글꼴 확대에서 줄 수 증가와 잘림을 분리한다) ──
if (CFG.checks.scaleMatrix) {
const bad = [];
const h1MaxLines = Number.isInteger(p.h1MaxLines) && p.h1MaxLines > 0 ? p.h1MaxLines : 1;
const measurements = [];
for (const w of [375, 390]) {
for (const s of CFG.fontScales) {
await page.setViewport({ width: w, height: 800, isMobile: true, hasTouch: true });
await page.goto(href, { waitUntil: "networkidle0" });
await page.evaluate(UTILS);
await page.addStyleTag({ content: `html { font-size: ${16 * s}px !important; }` });
const l = await page.evaluate(`__lines(${JSON.stringify(p.h1 || "h1")})`);
if (l > h1MaxLines) bad.push(`${w}@${s}x:${l}줄(최대 ${h1MaxLines}줄)`);
const baseRootSize = await page.evaluate(() => parseFloat(getComputedStyle(document.documentElement).fontSize));
if (!Number.isFinite(baseRootSize) || baseRootSize <= 0) {
bad.push(`${w}@${s}x:root font-size 측정 실패`);
continue;
}
await page.addStyleTag({ content: `html { font-size: ${baseRootSize * s}px !important; }` });
const h1 = await page.evaluate((sel) => window.__headingScaleAudit(sel), p.h1 || "h1");
const point = `${w}@${s}x`;
if (h1.state === "missing") { bad.push(`${point}:h1 선택자 없음 (${p.h1 || "h1"})`); continue; }
if (h1.state === "empty") { bad.push(`${point}:h1 텍스트 없음`); continue; }
if (h1.state === "hidden") { bad.push(`${point}:h1 선택자가 숨김 (${p.h1 || "h1"})`); continue; }
if (h1.state === "invalid") { bad.push(`${point}:h1 잘못된 선택자 (${p.h1 || "h1"})`); continue; }
if (h1.lines < 1) { bad.push(`${point}:h1 텍스트 없음`); continue; }
measurements.push(`${point}:${h1.lines}줄`);
if (s > 1 && h1.documentOverflow > 1) bad.push(`${point}:문서 가로오버플로 +${h1.documentOverflow}px`);
if (s > 1 && h1.clipping.length) bad.push(`${point}:제목 텍스트 클리핑 ${h1.clipping.join(",")}`);
}
}
bad.length ? fail(`${p.name} 스케일×폭 h1`, bad.join(",")) : pass(`${p.name} 스케일×폭 h1`, `최대 ${h1MaxLines}줄 · scales ${CFG.fontScales.join("/")}`);
bad.length ? fail(`${p.name} 스케일×폭 h1`, bad.join(",")) : pass(`${p.name} 스케일×폭 h1`, `줄수 정보 ${measurements.join(", ")}`);
// 브랜드 1줄
await page.setViewport({ width: 390, height: 844, isMobile: true, hasTouch: true });
await page.goto(href, { waitUntil: "networkidle0" });