14 KiB
preflight — 출시 전 감사
5단계에서 읽는다. 자기 결과물을 남의 것처럼 본다. 통과 못 한 항목은 4단계로 돌아가 고친다.
슬롭 지문 검출과 자가 채점표는 references/antipatterns.md 에 있다. 이 문서는 그 외 전부를 다룬다.
0. 먼저 기계로 검사한다
무엇에 돌리는가부터 정해라.
- 정적 HTML/CSS 프로젝트 → 소스 그대로
- React·Vue·Astro 등 프레임워크 프로젝트 → 프로덕션 빌드 결과에 돌린다. 소스만 보면 렌더된 DOM 이 없어 대비·가로 스크롤·LCP 를 잴 수 없다.
pnpm build && pnpm preview로 띄우고 그 URL 을 검사해라 - 소스만 보고 "측정 불가"로 남기지 마라. 미측정은 통과가 아니다
grep 검사는 주석과 문자열을 제외해라. "100vh 금지"라고 쓴 주석이 게이트 #9 에 걸리는 오탐이 실제로 나왔다.
사람 눈보다 정확하고 빠르다. 셋 다 돌려라.
A. 이펙트 전부 끄기
/* 임시로 주입해서 확인한다 */
*, *::before, *::after {
filter: none !important;
backdrop-filter: none !important;
animation: none !important;
transition: none !important;
transform: none !important;
}
canvas { display: none !important; }
이 상태에서 페이지가 여전히 읽히고 구조가 살아 있어야 한다. 무너지면 이펙트가 구조를 대신하고 있는 것이다. 4-1로 돌아가라.
B. 슬롭 지문 grep
references/antipatterns.md 의 grep 목록을 소스에 돌린다. 검출률 상위 항목(보라 CTA, 전체 대문자 헤드라인, 번호 매긴 1·2·3 단계)부터.
C. 카피 경쟁사 치환
제품명을 경쟁사 이름으로 바꿔 읽는다. 문장이 그대로 성립하면 그 카피는 아무것도 말하지 않았다. 히어로 문구부터 검사한다.
D. 죽은 코드·토큰 위생 (design-gate 내장 검사)
- CSS 클래스가 HTML·JS 어디에도 등장하지 않으면 죽은 것이다. 컴포넌트를 없앨 때 HTML·CSS·JS 를 삼위일체로 지웠는지 대조한다(동적 클래스
is-${type}·seg-${n}처럼 조립되는 접두사는 게이트 설정의deadCssIgnore로 제외한다) - 토큰 파일이 있는데 다른 CSS 파일에 색 리터럴(hex·rgb)이 남아 있으면 실패다. 반지름 리터럴도 마찬가지 — 눈 검사로는 절대 전부 못 잡는다(실측: 8건의 상수색이 눈 검사를 통과했다)
theme-color메타가 실제 배경색과 다르면 낡은 토큰이다. 프리셋 전환에서 브랜드 표면(theme-color·파비콘·og:image)이 누락됐다는 뜻이다
E. 왼쪽 인셋 리듬 (모바일)
오버플로 검사는 오른쪽만 본다 — 왼쪽이 붙어 깨지는 결함은 통과한다(실측: 제목 16px·앱바·카드 24px 혼재가 게이트를 통과해 사용자 보고로 나왔다). 모바일 폭(320·390)에서:
- 화면의 모든 보이는 텍스트 시작점 ≥ 12px
- 헤더 텍스트·화면 제목·카드 텍스트가 같은 축(±10px)
- 측정은 전폭 컨테이너의 보더박스가 아니라 텍스트 시작점,
display:none요소(rect=0)는 제외하고
1. 접근성 — 타협 없음
| 항목 | 확인 방법 | 기준 |
|---|---|---|
| 키보드 도달 | Tab 만으로 순회 | 모든 인터랙티브 요소에 도달, 순서가 시각 순서와 일치 |
| 포커스 표시 | Tab 하며 눈으로 | 항상 보인다. outline: none 만 있고 대체가 없으면 실패 |
| 대비 | 측정 도구 (아래 주의) | 본문 4.5:1, 큰 글자·UI 3:1 |
| 이미지 대체텍스트 | 소스 확인 | 의미 있는 이미지에 alt, 장식은 alt="" |
| 폼 레이블 | 소스 확인 | 모든 입력에 연결된 <label>. placeholder 는 레이블이 아니다 |
| 제목 구조 | 개요 확인 | h1 하나, 건너뛰지 않음 |
| 모션 감소 | prefers-reduced-motion 켜고 확인 |
전부 끄는 게 정답이 아니다 — 위치 이동·시차·자동재생을 죽이고, 페이드·상태 변화는 남긴다 |
| 자동재생 | 확인 | 5초 이상 반복되는 애니메이션에는 정지 수단이 있다 |
| 캔버스/WebGL 실패 | JS 끄고 확인 | 콘텐츠가 여전히 보인다 |
대비를 스크립트로 잴 때 — 반투명 배경을 불투명으로 계산하지 마라.
getComputedStyle(el).backgroundColor로 조상을 거슬러 올라가며 "투명이 아닌 첫 값"을 배경으로 쓰면,rgb(255 255 255 / 0.085)같은 유리 틴트를 순백으로 취급한다. 흰 글자가 1.15:1 로 나온다. 알파가 1 미만인 층은 계속 쌓고, 아래에서 위로 합성한 뒤에 재라:// 알파 < 1 이면 멈추지 말고 계속 쌓는다 const stack = []; let e = el; while (e && e !== document.documentElement) { const p = parse(getComputedStyle(e).backgroundColor); if (p && p[3] > 0) { stack.push(p); if (p[3] >= 1) break; } e = e.parentElement; } let [r, g, b] = rootBg; for (let i = stack.length - 1; i >= 0; i--) { const [sr, sg, sb, sa] = stack[i]; r = sr*sa + r*(1-sa); g = sg*sa + g*(1-sa); b = sb*sa + b*(1-sa); }유리·오버레이·틴트를 쓰는 페이지에서는 이 차이가 통과와 실패를 뒤집는다. 검사 도구가 틀리면 통과도 실패도 의미가 없다. 실패가 나오면 값을 고치기 전에 측정을 먼저 의심해라.
스크린리더를 실제로 켜볼 수 없다면 최소한 소스의 읽기 순서를 확인해라. order·position: absolute 로 시각 순서를 바꿨다면 읽기 순서가 어긋난다.
2. 성능 — 3단계에서 정한 예산과 대조
예산 값은 tokens.md §5 를 보라. 여기서 다시 적지 않는다 — 두 곳에 적으면 갈라진다.
이 문서가 하는 일은 실측을 채우는 것이다.
| 항목 | 실측 | 판정 |
|---|---|---|
| 히어로까지 JS (gzip) | ||
| WebGL/3D 추가분 | ||
| 총 전송량 (첫 화면) | ||
| 첫 인터랙션 (모바일 4G) | ||
| LCP | ||
| CLS | ||
| 폰트 파일 수 |
측정하지 않았으면 통과가 아니다. 예산을 넘겼다면 둘 중 하나다: 이펙트를 빼거나, 예산을 올린 이유를 명시하거나.
폰트 (typography.md §8 이 전체 목록)
- 폴백 메트릭을 보정했거나
font-display: optional을 골랐다. 둘 다 안 했으면 폰트가 교체되는 순간 레이아웃이 튄다 - preload 한 파일이 첫 화면에 실제로 쓰인다.
crossorigin이 있다 - 수치를 나열하는 곳에
tabular-nums가 있다 - 한글 폰트를 명시했고 서브셋을 쓴다
- 웹폰트 임베딩 라이선스를 확인해
design.md에 적었다 - 폰트를 못 받은 상태로 열어봤다. 그 상태로도 읽힌다
자주 걸리는 것
- 폰트 서브셋 안 함 (한글 전체 폰트는 수 MB)
- 첫 화면 밖 이미지에
loading="lazy"누락 - 3D/WebGL을 초기 번들에 포함 (뷰포트 진입 시 동적 import 해야 한다)
- 애니메이션이
top/left/width를 건드림 →transform으로 바꿔라 will-change남발 → 레이어가 늘어 오히려 느려진다
3. 반응형
- 320px 폭에서 가로 스크롤 없음 (하드 게이트 #5 는 320~1920px 전 구간을 요구한다. 375px 만 보면 그 아래가 뚫린다)
- 크롬(탑바·앱바·툴바)에 컨트롤을 더했다면 그 줄의 최소폭 합을 가장 좁은 폭에서 다시 잰다 — 버튼 하나가 320px를 뚫는 사고가 반복된다(실측: 인쇄 버튼 1개로 +18px). 줄이 넘으면 감싸기(2행 랩)가 기본 수습이다
- 1920px 이상에서 콘텐츠가 늘어지지 않음(최대 폭 제한)
- 중간 뷰포트(768~1024px)에서 레이아웃이 깨지지 않음 — 가장 자주 빠뜨리는 구간
- 터치 타깃: 버튼·아이콘·카드 등 독립 컨트롤은 44×44px 이상(Apple/Google 권고)
- 터치 타깃: 본문 안 인라인 텍스트 링크는 24×24px 이상(WCAG 2.5.8 AA). 여기에 44 를 요구하면 정상적인 내비 링크가 오탐된다
- 호버로만 접근되는 기능 없음
- 긴 단어/URL이 넘치지 않음 (
overflow-wrap: anywhere) - 가로 모드에서 첫 화면이 잠기지 않음
4. 콘텐츠
- 히어로가 무엇인지 말한다. 형용사만 있고 명사가 없으면 실패
- 로렘입숨이 남아 있지 않다
- 실제 콘텐츠 길이로 테스트했다 (짧은 제목만으로 맞춘 레이아웃은 긴 제목에서 깨진다)
- 빈 상태·오류 상태·로딩 상태가 있다
- 링크가 어디로 가는지 텍스트만 보고 알 수 있다 ("여기를 클릭" 금지)
- 이미지가 없거나 실패했을 때 레이아웃이 무너지지 않는다(
aspect-ratio지정)
4-1. 상태 완결성 — 동작하는 화면의 빈틈
정적 마케팅 페이지에는 없는 검사다. 입력·파괴·열림이 있는 화면(폼·콘솔·앱)은 네 가지를 갖춰야 프로덕션이다. 실제로 조작해 본다 — 눈으로 봐서는 안 보인다(실측: 범위 밖 101점이 조용히 평균에 반영되고 있었다).
- 수치 입력에는 범위가 있다 — 범위 밖 값은 인라인 오류로 이유를 말하고 제출을 막는다(
aria-invalid). 스트랩 검증만 믿지 마라:type="number"의min/max는 직접 입력을 막지 못한다 - 판정은 순수 함수다 — 가능/불가+사유의 판정 로직은 UI 핸들러에 두지 않고
calc.js같은 모듈로 추출해 단위 테스트(L1)가 같은 판정을 공유하게 한다. UI 는 판정 결과를 문장으로 번역만 한다. 실행취소의 재심사도 같은 함수를 쓴다 - 파괴적 행동에는 되돌림이 있다 — 삭제·취소는 실행취소 또는 확인. 되돌리기가 확인보다 낫다(흐름이 끊기지 않는다). 재신청·복원은 원래 판정 함수로 다시 심사한다 — 사이에 끼어든 다른 변화가 있으면 정당하게 막혀야 한다
- 열리는 것은 Esc 로 닫힌다 — 모달만이 아니다. 드롭다운·팝오버·알림 메뉴도. 닫힐 때 포커스는 연 요소로 돌아간다
- 로딩 상태는 실제 비동기에만 — 동기 인라인 데이터에 스켈레톤을 붙이는 건 저장을 흉내내는 것이다. 없는 지연을 만들지 마라
4-2. 진실 완결성 — 가상·예시·AI·규제 화면
가상 브랜드·예시 데이터·AI 분석·규제 주제를 실제 서비스처럼 보이게 만든 화면에만 돈다. 전역 데모 띠 하나로 끝내지 않는다. 상세 설계는 trustworthy-showcases.md.
- 모든 숫자·주소·상태·추천이 사실 / 예시 / 추론 / 미정 중 하나이고
USER_SELF_REPORT / VERIFIED_SOURCE / SYSTEM_STATE / SYNTHETIC_FIXTURE출처 계층이 추적된다. 자기보고 값 자체를 검증 사실로 승격하지 않았다 예시 가격,거래 불가,실제 접수 아님,연출 이미지라벨이 오해가 생기는 범위 가까이에 있다. 같은 상태·출처·행동 가능성을 공유하는 자식은 가장 가까운 공통 부모가 한 번 소유하고, 다른 자식만 예외 라벨을 가진다- 검증하지 않은 수익·가맹비·매장 수·등록번호·연락처를 만들지 않았다. 모르면
미정이다 - AI 결과에 입력 조건·출처가 붙은 입력/검증 사실·추론·불확실/누락·수정·사람 검토가 있고, 입력을 바꾸면 결과와 근거가 함께 바뀐다. 모델 출력 내용을
SYSTEM_STATE사실로 두지 않았다 - 진단·개인화 추천·예약·전송·거래·게시·내보내기 등 실제 규제 행동은 행동별 필수 검증이 없을 때 실패 폐쇄형이며, 미등록 행동은 기본 거절되고 막힌 이유가 화면에 보인다
- 민감 입력과 파생값의 영속 저장·쿠키·분석 이벤트 포함·네트워크 전송 경계를 E2E로 검증했다
- 공개 화면은 사용자 해석·제한·다음 행동을, 관리자는 내부 원장·검증 이벤트·채널 판정·감사 로그를 소유한다. 사용자 행동에 영향을 주는 제한을 관리자에만 숨기지 않았다
- 지도·차트·도식의 같은 정보가 DOM 목록·표·설명으로도 제공되고 데이터 원천이 하나다
- 생성 이미지는 가격·주소·점수의 증거로 쓰이지 않으며, 모바일 390×844에서 핵심 피사체가 첫 화면의 의도와 맞는다
- URL별 390·1440 기본 화면과 핵심 입력/오류/모달 상태 캡처를 실제로 열어 보고 시각 PASS/RED를 따로 기록했다
5. 디자인 자체
기계로 못 잡는 부분이다. 정직하게 답해라.
- 이 사이트를 다른 회사의 것으로 바꿔도 그대로 쓸 수 있는가? → 그렇다면 브리프에 맞춘 것이 없다
- 2단계에서 정한 한 문장 컨셉이 화면에서 보이는가? → 안 보이면 컨셉이 문서에만 있는 것이다
- 감수하기로 한 리스크가 실제로 들어갔는가? → 구현 중에 안전한 쪽으로 후퇴하지 않았는지
- 1단계 레퍼런스 R2(다른 업종의 톤)의 흔적이 있는가? → 없으면 결국 R1을 베낀 것이다
- 강조색이 하나인가?
- 여백이 의도적인가, 남은 것인가?
6. 최종 게이트
전부 통과해야 끝난다.
- 0장 기계 검사 4종 통과
- 접근성 표 전 항목 통과
- 성능 예산 내 (또는 초과 이유 명시)
- 반응형 체크리스트 통과
- 콘텐츠 체크리스트 통과
- 상태 완결성(§4-1) — 입력 검증·되돌림·Esc 를 실제 조작으로 확인
- 해당하면 진실 완결성(§4-2) — 예시·AI·규제·생성 이미지의 오해 경로를 실제 상태로 확인
- 5장 여섯 질문에 정직하게 답했고, 실패 항목을 고쳤다
antipatterns.md자가 채점표 통과
하나라도 실패하면 출시가 아니라 4단계 복귀다.
근거: research/references/04-ai-slop-signatures.md, research/motion/01-principles.md, research/three/04-performance.md (조사일 2026-08-20)