designpaca/packages/skill/references/antipatterns.md
2026-09-12 15:28:46 +09:00

446 lines
35 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# antipatterns.md — 점검 후보
AI가 만든 티는 **못 만들어서** 나는 게 아니라 결정을 설명·검증하지 않을 때 난다.
보라 그라디언트, Inter, 3열 아이콘 카드, `rounded-2xl shadow-lg`, "Get Started"는 반복될 수 있는 출발점이다. 존재만으로 미결정이라고 단정하지 않고, 브리프·프로젝트 계약·실제 렌더로 판단한다.
프리플라이트에서 이 문서를 훑어 후보를 찾는다. 검출은 실패가 아니다. 브리프·프로젝트 계약·실제 렌더를 대조해 유지·수정·제거를 결정하고 그 근거를 남긴다.
---
## 0. 먼저 보는 반복 후보
원자료와 재현 가능한 표본이 없는 빈도 수치는 규칙의 근거가 될 수 없다. 아래 항목은 흔한 기본값을 찾는 검색 후보이며, 색·대문자·번호가 존재한다는 사실만으로 실패가 아니다.
| 후보 | 확인할 질문 |
|---|---|
| 보라·인디고 CTA | 브랜드 팔레트·상태 구분·대비·목표 행동을 설명하는가? |
| 전체 대문자 라벨 | 언어·가독성·정보 위계에 맞는가? |
| 번호 매긴 단계 | 사용자가 실제 순서로 수행하는 과업인가? |
---
## 1. 컬러
다크·그라디언트·강한 색은 그 자체로 실패가 아니다. 화면 역할, 브랜드, 대비, 실제 기기 렌더에서 이유를 설명할 수 있는지 점검한다.
| 지문 | 왜 문제 | 대신 |
|---|---|---|
| **다크 모드를 브리프 근거 없이 기본값으로** (`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`) | 작은 크기·이미지 배경·공유 이미지에서 읽기와 fallback을 점검한다 | 단색·그라디언트·다른 위계 수단을 비교하고, 실제 export와 대비를 확인한다 |
| **크림/베이지(`#FDF8F3`)를 "고급"의 기본값으로** | 보라를 대체한 신종 슬롭 | 크림을 쓸 거면 왜 크림인지 브리프와 연결하고 텍스트·액센트를 그에 맞춰 재설계 |
| **여러 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) |
---
## 2. 타이포그래피
| 지문 | 왜 문제 | 대신 |
|---|---|---|
| **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_` 장식 번호 라벨** | 에디토리얼 흉내인데 구조는 없음 | 번호가 순서를 의미할 때만 |
| **본문/헤드라인 자간이 읽기를 방해함** | 글꼴·언어·크기·폭에 따라 글리프 충돌, 단어 인식, 줄바꿈이 달라진다 | 0, 양수, 음수 자간을 대표 문구·지원 viewport·200% 확대에서 비교한다. 단일 `em` 수치를 보편 기준으로 쓰지 않는다 |
| **본문 크기·행간이 과업에 맞지 않음** | 작은 글자나 촘촘한 행간은 읽기·확대·오류 복구 비용을 높일 수 있다 | 글꼴·언어·콘텐츠 밀도별 후보를 실제 기기·200% 확대·긴 문단에서 검증한다. 16px이나 고정 line-height를 보편 하한으로 선언하지 않는다 |
| **모노스페이스를 장식으로 사용** | 코드·데이터 외 용도에서도 의미·가독성·브랜드 톤을 돕는지 점검한다 | 대상 언어·크기·대비·긴 문자열을 실제 렌더에서 비교한다. 모노스페이스 자체나 용도만으로 실패시키지 않는다. 출처: `references/typography.md` |
---
## 3. 레이아웃 · 구조
| 지문 | 왜 문제 | 대신 |
|---|---|---|
| **표준 골격**: 히어로 → 3열 피처 → 로고월 → 요금제 → FAQ → 푸터 | shadcn/ui 예제·Tailwind UI·Vercel 템플릿의 순서 그대로 | 섹션 순서를 브리프의 설득 논리로 재배열. 예: 문제 제시 → 실제 사용 화면 → 반론 처리 → 가격 |
| **중앙 정렬 히어로** (텍스트 가운데 + 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열로 만들지 마라 |
---
## 4. 컴포넌트
| 지문 | 왜 문제 | 대신 |
|---|---|---|
| **기본 radius·shadow·padding 조합을 그대로 사용** | 컴포넌트 역할·밀도·브랜드 표면이 토큰과 맞는지 점검한다 | radius·선·그림자·여백 후보를 실제 상태에서 비교하고, 역할 토큰으로 기록한다 |
| **여러 깊이 표현을 겹침** | 경계·그림자·표면 차이가 같은 계층을 중복 설명하는지 점검한다 | 하나 또는 여러 단서를 쓸 수 있다. 포커스·상태·인접 표면에서 계층이 읽히는지 검증한다 |
| **글래스모피즘 전면 사용** (`backdrop-blur` 카드 다수) | backdrop filter의 성능·대비·reduced-transparency 대안이 필요할 수 있다 | 대상 기기의 performance, fallback 표면, 텍스트 대비를 측정하고 역할이 있을 때만 채택한다 |
| **이모지를 아이콘 대신** (🚀 ⚡ 🎯 ✨) | 의미·locale·플랫폼 렌더링·접근 가능한 이름이 역할을 돕는지 점검한다 | 이모지·아이콘 세트·텍스트를 실제 대상과 보조기술에서 비교한다 |
| **여러 CTA가 같은 무게로 경쟁함** | 사용자가 다음 행동을 선택하기 어려울 수 있다 | 행동 우선순위·위험·되돌림에 맞춰 주/보조 CTA와 배치를 정하고 과업 테스트한다 |
| **로고월·후기·FAQ·요금제 템플릿** | 사실·질문·가격 구조가 현재 제품의 증거인지 점검한다 | 검증 가능한 출처와 실제 고객 질문·요금 조건을 사용한다. 개수·열 수·배지 존재로 실패시키지 않는다 |
| **펄스 애니메이션 상태 점** (초록 원 깜빡임 + "All systems operational") | 정적 정보에 장식 애니메이션 | 상태가 실제로 바뀔 때만 |
| **자동 스크롤 마퀴** (로고·후기·태그가 끝없이 흐름) | 읽기·움직임 민감도·입력과 충돌할 수 있음 | 목적·정지 수단·감소 모션·성능을 검증하고, 정적 그리드 또는 페이지네이션과 비교 |
| **hover 상태가 과업을 돕지 않음** | 포인터 반응이 행동 가능성·현재 상태·위험을 설명하지 못할 수 있다 | 링크·카드·버튼의 상태를 역할별로 설계하고, touch/keyboard에서도 같은 핵심 단서를 제공한다 |
| **편집 불가 히어로 카피 뒤 깜빡이는 커서 `|`** | 타이핑 흉내. 정보 없음 | 삭제 |
---
## 5. 모션
| 지문 | 왜 문제 | 대신 |
|---|---|---|
| **같은 모션을 모든 요소에 적용** | 모션이 상태·관계·우선순위를 설명하지 못하고 읽기를 방해할 수 있다 | 중요한 변화에만 모션을 연결하고, duration/easing은 과업·거리·reduced-motion에서 검증한다. 고정 ms 범위는 출발값이다 |
| **bounce·elastic·scale·rotate 모션** | 장면의 물성·피드백 의미·멀미·성능과 맞는지 점검한다 | 이징과 속성은 실제 상호작용·reduced-motion·저사양 기기에서 비교한다 |
| **스크롤 reveal 실패 시 콘텐츠가 `opacity: 0`으로 남음** | JS 실패 시 빈 페이지가 됨 | 기본을 visible로 두고 JS가 숨긴 뒤 보여주는 방식. 또는 CSS scroll-driven animation |
| **`prefers-reduced-motion` 미대응** | 사용자 모션 선호를 무시하면 중요한 상태를 이용하기 어려울 수 있다 | `prefers-reduced-motion`에서 비동작 대체 신호와 핵심 과업을 실제로 검증한다 (`evidence-ledger.md`의 `W3C-REDUCED`) |
| **전 섹션 패럴랙스·3D/Spline 씬** | 콘텐츠보다 연출이 우선되거나 저사양·느린 망에서 핵심 경로를 막을 수 있다 | 목적·성능 예산·정적 fallback·정지 제어를 정하고 local proxy와 field data를 구분해 측정한다 |
| **로딩할 게 없는데 프리로더 카운터** | 없는 대기를 만듦 | 삭제 |
---
## 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 | 엑셀 없이 정산이 끝난다 | 명사구 → 주장 |
| 빠르고 안정적인 배포 | 실제 배포·롤백 결과와 조건 | 형용사 → 측정치. 예: 검증된 `p95 롤백 시간`, 기간, 환경 |
### 데모가 주장을 증명하는지 재라
주장을 화면으로 보여주는 섹션이라면, **그 화면이 정말 다른지 숫자로 확인해라.**
"차이는 이 한 줄뿐이다"라고 써놓고 두 쪽이 똑같으면, 그건 증명이 아니라 주장의 반증이다.
> 실측 사례: `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입니다`** | 대조가 실제 차이·조건을 설명하지 못하면 문장이 비어 보일 수 있다 | 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`, `~할 수 있습니다`) | 불확실성의 범위가 숨겨지면 독자가 주장 강도를 판단하기 어렵다 | 검증된 내용은 명확히 쓰고, 불확실하면 조건·근거·한계를 명시한다 |
| CTA가 `Get Started` / `Learn More` / `시작하기` | 무엇이 시작되는지 알 수 없음 | 결과를 말하는 CTA (`무료로 30일 써보기`, `가격표 보기`) |
| 출처·조건 없는 숫자 (`10,000+`, `99.9%`, `50% faster`) | 사실처럼 보이는 주장의 검증 경로가 없다 | 정확/범위 수치 모두 출처·측정 조건·기간을 붙인다. 조건 없는 수치는 제거하거나 예시임을 라벨링한다 |
| 모든 헤딩이 명사구 (`Powerful Features`, `Simple Pricing`) | 헤딩이 정보를 전달하지 않음 | 헤딩에 주장을 담아라 (`엑셀 없이 정산이 끝난다`) |
| 레이블·서브레이블·헬퍼가 같은 말 3번 (`이메일` / `이메일 주소` / `이메일 주소를 입력하세요`) | 화면 소음 | 하나만 남긴다 |
| 이모지로 시작하는 불릿 (`✅ 빠른 속도`) | 정보 위계를 이모지로 대체 | 일반 불릿 또는 문장으로 |
---
## 7. 이미지
| 지문 | 왜 문제 | 대신 |
|---|---|---|
| **스톡 사진·추상 3D·AI 일러스트·일반 SVG 그래픽** | 이미지가 제품 증거·브랜드·정보 이해 대신 빈 자리를 메우는지 점검한다 | 실제 사진·제품 UI·데이터·일러스트·여백 중 과업을 가장 잘 설명하는 것을 선택하고, 출처·라이선스·alt·크롭을 검증한다 |
| **아이콘 세트가 섞임** (라인·필·이모지 혼재) | 시스템이 없다는 증거 | 세트 하나 고정. 굵기·크기·광학 정렬 통일 |
| **`src`가 비었거나 깨진 이미지 태그** | 검증 없이 배포된 흔적 | 배포 전 이미지 로드 전수 확인 |
| **모든 이미지가 같은 비율의 둥근 사각형** | 그리드를 이미지에 강제 | 콘텐츠에 맞는 비율. 풀블리드 1장을 섞으면 리듬이 생긴다 |
---
## 8. 한글 조판 (한국어 프로젝트 필수)
한국어는 글꼴·줄바꿈·fallback·문장 리듬을 실제 콘텐츠와 지원 기기에서 별도로 검증한다. 영문 출발값을 그대로 복사하지 않는다.
| 규칙 | 값 | 이유 |
|---|---|---|
| **한글 글꼴과 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, 마루 부리, Spoqa Han Sans Neo, 본고딕/Noto Sans KR 등은 목적·glyph coverage·라이선스·fallback·전송량을 비교할 후보다.
- 캠페인 display 폰트도 업종 자체로 배제하지 않는다. 실제 문구·브랜드·작은 크기·지원 기기에서 읽기와 인상을 검증한다.
---
## 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()` 사용 결과가 의도한 crop 통계를 반영하는지 확인한다.**
영역을 여섯 군데 잘라 밝기를 쟀는데 여섯 개가 **소수점까지 똑같이** 나왔다.
전부 원본 전체 평균이었기 때문이다. 값이 수상하게 균일하면 도구를 먼저 의심해라.
```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 정지 캡처가 비교에 적합한 경우가 많다.
**④ 개별 요소의 높이로는 "여러 줄에 걸친 것" 을 못 잡는다.**
"컨트롤 행이 지원 폭에서 읽거나 조작할 수 없게 되는가"를 이렇게 쟀다.
```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)를 넘었다. 고칠 필요가 없었다.
밝은 것과 대비가 낮은 것은 다르다. 화려한 배경 위 텍스트는 실제 배경 합성 상태의 contrast ratio와 읽기 관찰을 함께 확인한다.
> 캔버스 접근 가능성은 렌더링 구성에 따라 다르다. 스크린샷 기반 측정은 한 방법이며, 텍스트와 배경의 실제 합성 상태를 대표하는 표본을 골라 계산한다.
---
### 여백이 필요하면 여백을 만들어라 — 문장으로 채우지 마라
sticky 캔버스가 끝까지 돌려면 목록 뒤에 스크롤 구간이 필요했다.
빈 요소를 두기가 뭐해서 마무리 문장을 한 줄 놓았다.
> "여섯 단계 끝에 남는 것은 페이지 하나와 design.md 하나다."
문장 자체는 틀리지 않았다. 다만 **읽는 사람이 그 자리에서 기대하는 말이 아니다.**
사용자가 처음 한 말이 "이건 왜 있는 거예요" 였다.
레이아웃을 위해 추가한 문장이 콘텐츠 역할을 하지 않으면 겉돌 수 있다. 여백과 문장 중 어느 것이 목적을 더 잘 충족하는지 검토한다.
> 자가 진단: 이 문장을 지우면 **레이아웃이 깨지는가, 뜻이 빠지는가?**
> 레이아웃만 깨진다면 그 문장은 여백의 대역이고, 여백으로 바꿔야 한다.
### 지적받은 자리만 고치면 전체가 무너진다
한 페이지를 만들며 사용자 지적이 올 때마다 그 섹션을 손봤다.
각각은 옳은 수정이었는데 합계는 이렇게 됐다.
| 섹션 | 분량 | 역할 |
|---|---|---|
| 히어로 | 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 한글 조판 | | |
**검토 조건 — 근거와 결과를 남긴다**
- [ ] 반복 후보(보라 CTA / 전체 대문자 / 1·2·3 단계)를 발견했으면 프로젝트 계약·브리프·실제 렌더 근거로 유지·수정·제거를 결정했다
- [ ] grep 검사에서 나온 항목은 자동 실패로 취급하지 않고, 출처·관찰·결정·검증을 기록했다
- [ ] 카피가 경쟁사 치환 테스트를 통과한다
- [ ] 모션을 전부 끄고도 페이지가 작동한다
- [ ] 강조색을 지워도 페이지가 읽힌다
- [ ] 색을 회색조로 바꿔도 위계 순서가 비즈니스 우선순위와 일치한다
- [ ] 키보드만으로 전 인터랙션이 가능하고 포커스 링이 보인다
- [ ] (한국어) 글꼴·fallback·줄바꿈 규칙이 실제 지원 기기와 긴 제목·본문·숫자에서 읽히는지 검증하고, 프로젝트 계약에 선택 근거를 남겼다
- [ ] 제목→본문 간격이 각 섹션의 정보 관계를 설명하고, 의도하지 않은 0px·겹침·불균형이 없는지 실제 렌더에서 확인했다
- [ ] 화면에 적은 수치는 **전부 그 화면을 캡처해 잰 값**이다 (추정치·기대치가 섞여 있지 않다)
- [ ] 이미지의 실제 표시 비율·크롭·해상도가 의도한 콘텐츠 역할과 화면 폭에 맞는지 측정했다. 원본 픽셀과의 일치만으로 `aspect-ratio` 동작을 판정하지 않는다
- [ ] 내비·버튼 그룹의 **행 수를 `top` 값으로** 셌다 (개별 높이로는 못 잡는다)
**판정**
후보 수를 점수나 자동 통과 기준으로 쓰지 않는다. 각 후보마다 `프로젝트 계약`, `출처 ID`, `적용 조건`, `실제 렌더 관찰`, `유지·수정·제거 결정`, `검증 결과`를 남긴다. WCAG 성공 기준 또는 실제 기능 실패가 있으면 그 항목은 해결 전 통과로 선언하지 않는다. 그 밖의 스타일 후보는 과업·브랜드·위계·성능의 증거로 판정한다.
> 근거와 한계: `references/evidence-ledger.md`, `references/design-foundations.md`, 프로젝트 브리프와 렌더 검증