# antipatterns.md — 금지 목록

AI가 만든 티는 **못 만들어서** 나는 게 아니라 **결정을 안 해서** 난다.
보라 그라디언트, Inter, 3열 아이콘 카드, `rounded-2xl shadow-lg`, "Get Started"는 전부 **미결정의 기본값**이다.

프리플라이트에서 이 문서를 훑고 걸린 항목을 전부 해소한다. **4개 이상 걸리면 폐기하고 다시 시작한다.**

---

## 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"이었다.

---

## 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` 같은 고유 이름 |
| **코드에서 색 이름을 직접 호출** (`purple-500`) | 색이 역할을 못 가짐 | 의미 토큰: `--color-action-primary`, `--color-surface-elevated`, `--color-text-secondary` |
| **그라디언트 텍스트** (`background-clip: text`) | 스캔성을 깎고 정보는 0. OG 이미지에서 깨짐 | 단색. 강조는 크기·굵기·여백으로 |
| **크림/베이지(`#FDF8F3`)를 "고급"의 기본값으로** | 보라를 대체한 신종 슬롭 | 크림을 쓸 거면 왜 크림인지 브리프와 연결하고 텍스트·액센트를 그에 맞춰 재설계 |
| **hue 4개 이상** | 결정 못 한 상태 | hue 3개 이하. 면적 60(지배)/30(중립)/10(강조) |
| **다크에서 본문 대비 미달** (`#0f172a` 위 `#94a3b8`) | 기능적 결함 | 본문 4.5:1 또는 APCA Lc ≥ 75. 다크의 본문은 순백 대신 `#E8E6E3` |

---

## 2. 타이포그래피

| 지문 | 왜 문제 | 대신 |
|---|---|---|
| **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) | 가장 알아보기 쉬운 구조 지문 | 배지를 지워라. 정말 필요하면 문장 안이나 내비 옆으로 |
| **`_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`) | "테크 느낌"의 저비용 흉내 | 모노는 코드·데이터·식별자에만 |

---

## 3. 레이아웃 · 구조

| 지문 | 왜 문제 | 대신 |
|---|---|---|
| **표준 골격**: 히어로 → 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자 폭 |
| **푸터가 링크 4열 + 소셜 아이콘 + 카피라이트뿐** | AI가 가장 성의 없이 만드는 곳 | 실제 정보(연락처, 주소, 사업자 정보)를 넣어라. 링크가 4개뿐이면 4열로 만들지 마라 |

---

## 4. 컴포넌트

| 지문 | 왜 문제 | 대신 |
|---|---|---|
| **`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 템플릿 | 비교 표, 단일 가격, 또는 계산기 |
| **펄스 애니메이션 상태 점** (초록 원 깜빡임 + "All systems operational") | 정적 정보에 장식 애니메이션 | 상태가 실제로 바뀔 때만 |
| **자동 스크롤 마퀴** (로고·후기·태그가 끝없이 흐름) | 읽기를 방해하고 주의를 강탈 | 정적 그리드. 많으면 페이지네이션 |
| **hover가 아무 반응 없거나 전 요소가 동일하게 `scale(1.02)`** | 인터랙션을 설계하지 않았다는 신호 | 요소마다 다른 반응(링크는 밑줄, 카드는 배경 톤, 버튼은 명도). **hover는 가장 먼저 설계한다** |
| **편집 불가 히어로 카피 뒤 깜빡이는 커서 `|`** | 타이핑 흉내. 정보 없음 | 삭제 |

---

## 5. 모션

| 지문 | 왜 문제 | 대신 |
|---|---|---|
| **모든 요소에 동일한 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 |
| **스크롤 reveal 실패 시 콘텐츠가 `opacity: 0`으로 남음** | JS 실패 시 빈 페이지가 됨 | 기본을 visible로 두고 JS가 숨긴 뒤 보여주는 방식. 또는 CSS scroll-driven animation |
| **`prefers-reduced-motion` 미대응** | 접근성 실패 | 모션 제거 경로를 반드시 제공 |
| **전 섹션 패럴랙스** | 스크롤 감각이 어긋나고 성능 저하 | 한 섹션만, 이동량 10–20px |
| **히어로에 3D/Spline 씬을 이유 없이** | JS 런타임 800KB~2MB. 모바일 4G에서 Core Web Vitals 실패 | 브리프가 요구할 때만. 아니면 정적 렌더 이미지 + 미세 모션 |
| **로딩할 게 없는데 프리로더 카운터** | 없는 대기를 만듦 | 삭제 |

---

## 6. 카피 — 경쟁사 치환 테스트

**AI 티는 시각보다 문장에서 먼저 난다.**

### 테스트
카피에서 제품·회사 이름을 **경쟁사 이름으로 바꿔 읽는다.**
**여전히 자연스러우면 그 카피는 아무것도 말하지 않은 것이다. 다시 써라.**

### 작법 — 금지 목록만으로는 못 잡는다

위의 지문 목록은 **나쁜 문장**을 걸러낸다. 그런데 지문에 하나도 안 걸리면서
**아무것도 전달하지 않는 문장**이 있다. 대개 대구가 예뻐서 통과된다.

> 실측 사례: `"표면은 CSS 가 정하고, 공간은 GPU 가 맡는다"`
> 치환 테스트 통과(다른 제품엔 안 맞는다), 금칙어 없음, 리듬도 좋다.
> 그런데 **독자가 얻는 것이 없다.** 구현 이야기를 대구로 포장한 것뿐이다.

**세 가지로 거른다.**

**1. `so you can…` 테스트** — 문장 뒤에 이어 붙여 완성해봐라.

```
"feTurbulence 로 노이즈를 만든다"        so you can → 200KB 텍스처를 300바이트로 줄인다  ✅
"표면은 CSS 가 정하고 공간은 GPU 가 맡는다"  so you can → ???                              ❌
```

완성되지 않으면 기능만 쓴 것이다. **완성된 뒷부분을 헤드라인으로 올려라.**

**2. 6~12단어** — 2초 안에 훑히면서 가치를 전달하는 폭이다.
한국어는 어절 기준으로 6~12, 글자 수로는 12~28자.

**3. 헤딩에 검증 가능한 것을 넣어라** — 숫자·조건·비교 대상 중 하나.

| 고치기 전 | 고친 뒤 | 무엇이 달라졌나 |
|---|---|---|
| 표면은 CSS 가 정하고, 공간은 GPU 가 맡는다 | **텍스처 200KB 를 300바이트가 대신한다** | 구현 → 이득. 수치가 검증 가능 |
| Powerful Features | 엑셀 없이 정산이 끝난다 | 명사구 → 주장 |
| 빠르고 안정적인 배포 | 배포 실패가 롤백까지 90초 | 형용사 → 측정치 |

### 데모가 주장을 증명하는지 재라

주장을 화면으로 보여주는 섹션이라면, **그 화면이 정말 다른지 숫자로 확인해라.**
"차이는 이 한 줄뿐이다"라고 써놓고 두 쪽이 똑같으면, 그건 증명이 아니라 주장의 반증이다.

> 실측 사례: `word-break: break-all` 과 `keep-all` 을 나란히 두고
> "차이는 한 줄뿐"이라고 적었다. 재보니 **줄 수 3, 높이 87px 로 완전히 동일**했다.
> 어절이 짧아 두 쪽이 같은 자리에서 끊긴 것이다. 반년을 그대로 뒀어도 몰랐을 것이다.

```js
// 비교 데모는 이렇게 검증한다
const m = sel => { const e = document.querySelector(sel), r = e.getBoundingClientRect();
  return { h: Math.round(r.height), lines: Math.round(r.height / parseFloat(getComputedStyle(e).lineHeight)) }; };
// m('.bad') 와 m('.good') 이 같으면 그 데모는 아무것도 보여주지 않는다
```

**차이가 안 나면 조건을 극단으로 밀어라** — 폭을 좁히고, 잘리기 쉬운 긴 어절을 넣고,
값 차이를 벌린다. 그래도 안 나면 그 비교는 애초에 보여줄 것이 없는 것이다.

그리고 **설명 문장이 화면과 일치하는지 확인해라.** "A 가 갈린다"고 썼는데 실제로 갈린 건
B 라면, 반응형에서 매번 바뀌는 것을 지목한 것이다. 지목하지 말고 현상만 말해라.

**대구·대조·리듬은 마지막에 얹는 것이다.** 먼저 말할 내용을 정하고,
그것이 정해진 뒤에도 대구가 성립하면 그때 써라. 대구부터 잡으면 내용이 비어도 모른다.

### 구문 지문

| 지문 | 왜 문제 | 대신 |
|---|---|---|
| **`It's not X, it's Y` / `단순한 X가 아니라 Y입니다`** | ChatGPT 최대 지문. 400단어에 3번씩 나옴 | 그냥 Y를 말해라. 대조가 필요하면 비교 대상을 실명으로 |
| **스타카토 3연타** (`No fluff. No filler. No BS.` / `빠르게. 정확하게. 간단하게.`) | 리듬으로 내용 없음을 감춤 | 한 문장으로 구체적으로 |
| **em-dash(—) 한 문단에 2회 이상** | AI 문장 리듬의 대표 지문 | 마침표로 끊거나 쉼표로. 한국어에서 특히 부자연스럽다 |
| **`In today's fast-paced digital landscape...`** 도입 | 아무 말도 하지 않는 문단 | 첫 문장부터 본론. 도입 문단 삭제 |

### 어휘 지문

| 지문 | 왜 문제 | 대신 |
|---|---|---|
| `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명 이하 팀에서는`) |
| CTA가 `Get Started` / `Learn More` / `시작하기` | 무엇이 시작되는지 알 수 없음 | 결과를 말하는 CTA (`무료로 30일 써보기`, `가격표 보기`) |
| 모든 숫자가 어림수 (`10,000+`, `99.9%`, `50% faster`) | 검증 불가 | 정확한 수치와 측정 조건 (`p95 응답 240ms, 2026-07 기준`) |
| 모든 헤딩이 명사구 (`Powerful Features`, `Simple Pricing`) | 헤딩이 정보를 전달하지 않음 | 헤딩에 주장을 담아라 (`엑셀 없이 정산이 끝난다`) |
| 레이블·서브레이블·헬퍼가 같은 말 3번 (`이메일` / `이메일 주소` / `이메일 주소를 입력하세요`) | 화면 소음 | 하나만 남긴다 |
| 이모지로 시작하는 불릿 (`✅ 빠른 속도`) | 정보 위계를 이모지로 대체 | 일반 불릿 또는 문장으로 |

---

## 7. 이미지

| 지문 | 왜 문제 | 대신 |
|---|---|---|
| **랩탑 앞에서 웃는 다국적 팀 스톡 사진** | 방문자는 스톡을 알아보고 **신뢰도가 실제로 하락**한다 | 실제 팀 사진, 실제 작업 공간, 또는 사진 없이 타입으로 |
| **떠 있는 3D 추상 블롭·기하 도형** | 콘텐츠 부재를 덮는 장식 | 제품 실제 화면, 데이터 시각화, 또는 여백 |
| **AI 생성 일러스트** (지나치게 매끄럽고 대칭적) | 결함이 없어도 톤에서 티가 남 | 일러스트 시스템을 직접 정의하거나 아예 쓰지 않는다 |
| **일반 SVG 도형을 조합한 히어로 그래픽** | 플레이스홀더 클립아트처럼 읽힘 | 제품 UI 조각을 실제로 렌더 |
| **아이콘 세트가 섞임** (라인·필·이모지 혼재) | 시스템이 없다는 증거 | 세트 하나 고정. 굵기·크기·광학 정렬 통일 |
| **`src`가 비었거나 깨진 이미지 태그** | 검증 없이 배포된 흔적 | 배포 전 이미지 로드 전수 확인 |
| **모든 이미지가 같은 비율의 둥근 사각형** | 그리드를 이미지에 강제 | 콘텐츠에 맞는 비율. 풀블리드 1장을 섞으면 리듬이 생긴다 |

---

## 8. 한글 조판 (한국어 프로젝트 필수)

서구 갤러리에는 없는 규칙이다. **영문 기준을 그대로 쓰면 전부 깨진다.**

| 규칙 | 값 | 이유 |
|---|---|---|
| **한글 폰트 미지정 금지** | `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 카피를 기계 번역한 티가 난다. `~로`, `~의`, `~에서`로 |

**한글 폰트 선택**
- 기본값: **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에 쓰면 즉시 아마추어**
- 나눔고딕은 너무 흔하다. 폴백으로만.

---

## 9. grep 코드 지문 (프리플라이트 자동 검사)

```
# 색
indigo-|violet-|purple-|fuchsia-
#6366f1|#818cf8|#a855f7|#8b5cf6|#c4b5fd|#a78bfa|#22d3ee
from-purple|to-blue-|from-violet|via-purple
bg-slate-900|bg-gray-900|text-gray-400
#ffffff|#000000

# 컴포넌트 기본값
rounded-2xl|rounded-3xl
shadow-lg|shadow-xl|shadow-2xl
border-l-4|border-t-4
backdrop-blur

# 타이포
font-family:.*Inter
(Space Grotesk.*Instrument Serif)|(Instrument Serif.*Geist)|(Space Grotesk.*Geist)
uppercase tracking-wide

# 카피
Get Started|Learn More|Empower|Streamline|Supercharge|Seamless
world-class|cutting-edge|enterprise-grade|best-in-class
It's not .* it's

# 이모지 아이콘 (텍스트 노드)
🚀|⚡|✨|🎯|🔥|💡|✅
```

**한글 프로젝트 추가 검사**

```
# 있어야 하는 것 — 없으면 실패
word-break:\s*keep-all
Pretendard|Noto Sans KR|본고딕|마루부리

# 없어야 하는 것
letter-spacing:\s*-0\.0[3-9]   (한글 본문)
~를 통해|~에 대한|에 있어서
```

---

## 9-b. 재는 도구가 거짓말하는 세 가지 방식

측정으로 판정하라고 해놓고, 그 측정 자체가 틀리면 잘못된 확신만 남는다.
실측에서 걸린 세 가지다.

**① 등장 애니메이션이 끝나기 전에 재면 간격이 틀리게 나온다.**
`.reveal { transform: translateY(16px) }` 이 걸린 요소는 화면 밖에 있는 동안 계속
16px 아래에 있다. 화면 최상단에서 아래쪽 섹션의 간격을 재면 그 16px 이 여백으로 잡힌다.
한 섹션만 24px 이 아니라 38px 로 나와 한참 원인을 찾았는데, 원인은 CSS 가 아니라
**측정 시점**이었다. 제목과 본문이 나란히 애니메이션되면 같이 움직여 상쇄되지만,
한쪽만 `.reveal` 이면 상쇄되지 않는다.

```js
await page.evaluate(() => el.scrollIntoView({ block: 'center' }));
await page.waitForTimeout(1600);   // 반드시 애니메이션이 끝난 뒤에 잰다
```

**② `sharp(...).extract(...).stats()` 는 잘라내기를 무시하고 원본 전체를 잰다.**
영역을 여섯 군데 잘라 밝기를 쟀는데 여섯 개가 **소수점까지 똑같이** 나왔다.
전부 원본 전체 평균이었기 때문이다. 값이 수상하게 균일하면 도구를 먼저 의심해라.

```js
const buf = await sharp(f).extract(box).png().toBuffer();
const s   = await sharp(buf).stats();   // 잘라낸 버퍼를 다시 물려야 한다
```

**③ Playwright 의 `animations: 'disabled'` 는 애니메이션을 끝 상태로 보낸다.**
움직이는 데모를 중간 프레임에서 재려고 `animation-play-state: paused` 로 세워 뒀는데,
스크린샷 옵션이 그걸 무시하고 100% 지점을 찍었다. 그 프레임에서는 두 도형이 이미
겹쳐 있어서 "필터가 안 붙는다" 는 잘못된 결론이 나왔다. 멈춘 프레임을 그대로
찍으려면 그 옵션을 **빼야** 한다.

> 여기서 더 중요한 결론이 나왔다 — **움직임으로 증명하려 들지 마라.**
> 지나가는 프레임은 방문자도 놓치고 측정도 매번 다른 값을 준다.
> 정지된 before/after 두 장이 더 정확하게 말한다.

**④ 개별 요소의 높이로는 "여러 줄에 걸친 것" 을 못 잡는다.**
"컨트롤이 2줄로 접히는가"(하드 게이트 6)를 이렇게 쟀다.

```js
// 틀렸다 — 이 검사는 "접힘 0" 이라고 답한다
[...document.querySelectorAll('.nav a')]
  .filter(e => e.getBoundingClientRect().height > lineHeight * 1.8)
```

각 링크는 한 줄짜리라 높이가 정상이었다. **줄바꿈된 것은 링크가 아니라 컨테이너였다** —
링크들이 두 행(top 46 / 77)에 나뉘어 놓여 있었다. 게이트를 통과했다고 보고했고,
사용자가 화면을 보고 잡아냈다.

```js
// 맞다 — 행이 몇 개인지는 top 값의 종류로 센다
new Set([...document.querySelectorAll('.nav a')]
  .map(e => Math.round(e.getBoundingClientRect().top))).size
```

같은 함정이 갤러리·태그 목록·버튼 그룹 어디에나 있다.
**"몇 줄인가" 를 물을 때는 높이가 아니라 위치를 세라.**

**⑤ 눈으로 의심한 것이 실측에서 뒤집힐 수 있다.**
WebGL 판이 본문 뒤를 지나가는 화면을 보고 "대비가 죽었다" 고 판단해 캔버스를
어둡게 만들려 했다. 스크린샷에서 글자 사이 빈 띠의 배경 휘도를 재보니
최악 지점이 **9.83:1** — AAA(7:1)를 넘었다. 고칠 필요가 없었다.
밝은 것과 대비가 낮은 것은 다르다. **화려한 배경 위의 대비는 인상이 아니라 숫자로 판정해라.**

> 캔버스 위에서는 `readPixels` 가 안 통한다(three.md 참고). 스크린샷을 찍어
> **글자를 피한 빈 띠**의 평균 휘도를 재는 것이 유일하게 맞는 방법이다.

---

### 여백이 필요하면 여백을 만들어라 — 문장으로 채우지 마라

sticky 캔버스가 끝까지 돌려면 목록 뒤에 스크롤 구간이 필요했다.
빈 요소를 두기가 뭐해서 마무리 문장을 한 줄 놓았다.

> "여섯 단계 끝에 남는 것은 페이지 하나와 design.md 하나다."

문장 자체는 틀리지 않았다. 다만 **읽는 사람이 그 자리에서 기대하는 말이 아니다.**
사용자가 처음 한 말이 "이건 왜 있는 거예요" 였다.

레이아웃이 필요해서 쓴 문장은 예외 없이 겉돈다. 여백이 필요하면
`padding` 을 주고, 그 자리에 놓을 말이 정말 있을 때만 문장을 쓴다.

> 자가 진단: 이 문장을 지우면 **레이아웃이 깨지는가, 뜻이 빠지는가?**
> 레이아웃만 깨진다면 그 문장은 여백의 대역이고, 여백으로 바꿔야 한다.

### 지적받은 자리만 고치면 전체가 무너진다

한 페이지를 만들며 사용자 지적이 올 때마다 그 섹션을 손봤다.
각각은 옳은 수정이었는데 합계는 이렇게 됐다.

| 섹션 | 분량 | 역할 |
|---|---|---|
| 히어로 | 0.7 화면 | 이게 뭔가 |
| 파이프라인 | 2.4 | 어떻게 작동하나 |
| 기능 예시 A | **3.1** | 곁가지 |
| 기능 예시 B | 1.8 | 곁가지 |
| 증거 ①②③④ | 5.5 | 같은 말 네 번 |

**곁가지 하나가 본론의 1.3 배, 첫인상이 가장 짧다.** 분량 배분이 중요도와 정반대다.
사용자 표현으로는 "정신이 없어 보인다".

부분 수정을 몇 번 하고 나면 반드시 전체를 다시 재라.

```js
// 섹션별 화면 수 — 배분이 우선순위와 맞는지 한 번에 보인다
[...document.querySelectorAll('main > section')].map(s => ({
  제목: s.querySelector('h2')?.textContent?.trim().slice(0, 24),
  화면: +(s.getBoundingClientRect().height / innerHeight).toFixed(1),
}))
```

읽는 순서도 같이 본다. **같은 성격끼리 붙어 있어야 한다** — 기능·증거·기능·증거로
번갈아 나오면 독자는 지금 무슨 이야기를 듣고 있는지 놓친다.
소개 페이지의 기본 묶음은 `무엇인가 → 무엇을 아는가 → 정말 그런가 → 행동` 이다.

---

### 데모를 만들기 전에 카피부터 쓰지 마라

실측 사례. 필터 데모 넷을 만들면서 각 데모의 결론 문장을 먼저 썼다 —
"대비 2.1:1 → 12.4:1", "하나로 이어진다", "배경을 휜다".
나중에 재보니 **넷 중 셋이 거짓이었다.**

| 화면에 쓴 말 | 실제 측정 |
|---|---|
| 대비 2.1:1 → 12.4:1 | 1.70:1 → **3.12:1** (AA 미달) |
| 하나로 이어진다 | 필터를 켠 쪽도 **덩어리 3개** |
| 배경을 휜다 | 평균 채널차 **1.66** (거의 변화 없음) |

숫자를 지어낸 것이 아니라 **"이 정도 나오겠지" 를 적고 확인하지 않은 것**이다.
결과는 같다 — 페이지에 거짓이 실렸다.

순서를 뒤집어라.

1. 데모를 만든다
2. **잰다** (before/after 를 각각 캡처해 픽셀로)
3. 잰 값으로 문장을 쓴다
4. 값이 주장을 뒷받침하지 못하면 **문장이 아니라 데모를 고친다**

4번을 지키면 카피가 저절로 구체적이 된다. 위 셋은 고친 뒤 이렇게 됐다 —
"흰 글자 대비 1.70:1 → 4.79:1", "가로로 세어 덩어리 3개 → 1개",
그리고 세 번째는 끝내 작동하지 않아 **섹션에서 뺐다.**

> 작동하지 않는 데모를 남기고 문장만 다듬는 선택지는 없다.

---

## 10. 자가 채점표

| 카테고리 | 걸린 항목 | 조치 |
|---|---|---|
| 0 최우선 3종 | | |
| 1 컬러 | | |
| 2 타이포 | | |
| 3 레이아웃 | | |
| 4 컴포넌트 | | |
| 5 모션 | | |
| 6 카피 | | |
| 7 이미지 | | |
| 8 한글 조판 | | |

**통과 조건 — 전부 예여야 한다**
- [ ] 최우선 3종(보라 CTA / 전체 대문자 / 1·2·3 단계) 중 걸린 것이 없다
- [ ] grep 검사에서 나온 항목을 전부 해소했다
- [ ] 카피가 경쟁사 치환 테스트를 통과한다
- [ ] 모션을 전부 끄고도 페이지가 작동한다
- [ ] 강조색을 지워도 페이지가 읽힌다
- [ ] 색을 회색조로 바꿔도 위계 순서가 비즈니스 우선순위와 일치한다
- [ ] 키보드만으로 전 인터랙션이 가능하고 포커스 링이 보인다
- [ ] (한국어) 한글 폰트가 명시되어 있고 `word-break: keep-all`이 있다
- [ ] 모든 섹션에서 제목→본문 간격이 **같은 값**이다 (0px 인 섹션이 하나도 없다)
- [ ] 화면에 적은 수치는 **전부 그 화면을 캡처해 잰 값**이다 (추정치·기대치가 섞여 있지 않다)
- [ ] 이미지 높이를 실제로 쟀다 — 원본 픽셀 높이가 그대로 나오면 `aspect-ratio` 가 죽은 것이다
- [ ] 내비·버튼 그룹의 **행 수를 `top` 값으로** 셌다 (개별 높이로는 못 잡는다)

**판정**

| 걸린 개수 | 판정 |
|---|---|
| 0–1 | 통과 |
| 2–3 | 해당 항목 수정 후 재검 |
| **4 이상** | **폐기. reference-method.md §3부터 다시** |

> 근거: research/references/04-ai-slop-signatures.md, 03-trends-2026.md (조사일 2026-08-20)
