---
task_id: task-2432
type: design-spec
section: mapping-tables
created: 2026-05-03
status: draft
---

# IDS Phase 4 — Phase 0-B: 매핑표 5종

> 회장 핵심: **"디자인 자산을 코드로 옮기는 것이 아니라, 타겟 사용자에게 먹히는 디자인을 토큰+구조+룰로 번역"**
>
> 회장 명시: **"모든 매핑은 이 타겟 기준(target-audience.md)과 연결되어야 한다."**
>
> Codex 사전 검증 (★): 기존 `quality_evaluator.check_brand_color_match`는 ΔE+면적 비율만 측정, **contrast(text-bg) 미측정** → task-2428 "ΔE=0.00 정확해도 색 대비 안 맞음 = 아마추어 수준" 문제 원인. 매핑표 #4 (회귀 게이트) 와 매핑표 #5 (Lite Evaluator) 분리 필수.

본 문서는 5종 매핑표를 통해 **dq-rules.json 32+ 항목 / 노하우 / design-md 브랜드 / 기존 evaluator / 새 quality gate**를 IDS 토큰·룰·게이트로 번역한다.

---

## 매핑표 #1: dq-rules.json 32+ 항목 → CSS token

**검증 기준**: dq-rules.json 100% 커버 (모든 키를 행으로 작성, 요약/생략 금지)

| dq-rules 키 (path) | 임계값 / 허용범위 | CSS token 이름 | 적용 컴포넌트 | 타겟 연결 근거 |
|---|---|---|---|---|
| `font_sizes._unit` | "px" | `--font-unit: 1px` | 전체 typography | dq-rules SSOT 단위 강제 — preset 무관 단일 단위 |
| `font_sizes._base_canvas` | "1080px" | `--canvas-base: 1080px` | 모든 1:1 캔버스 | FA 태블릿/소비자 모바일 SNS 1:1 비율 표준 |
| `font_sizes.core_metric.min` | 96 | `--font-core-metric-min: 96px` | MetricCard, big number | FA 페르소나 "정확한 숫자" 가치 — 보장금액/보험료 핵심 수치 강조 |
| `font_sizes.core_metric.recommended` | "96-140" | `--font-core-metric-rec: clamp(96px, 10vw, 140px)` | MetricCard | FA 미팅 1m 거리 + 소비자 모바일 30cm 모두 가독 |
| `font_sizes.headline.min` | 84 | `--font-headline-min: 84px` | Headline | FA 태블릿 1m 가독 / 소비자 SNS 스크롤 0.5초 시선 캐치 |
| `font_sizes.headline.recommended` | "84-120" | `--font-headline-rec: clamp(84px, 9vw, 120px)` | Headline | preset 분기 (FA preset = 큰 값, 소비자 preset = 작은 값) |
| `font_sizes.subhead.min` | 64 | `--font-subhead-min: 64px` | Subhead | task-1463 "서브헤드 64px 미만 시 DQ-08 최대 6점 캡" 직결 |
| `font_sizes.subhead.recommended` | "64-84" | `--font-subhead-rec: clamp(64px, 7vw, 84px)` | Subhead | 정보 계층 명확 — head/sub 1.3배 룰 만족 |
| `font_sizes.cta.min` | 40 | `--font-cta-min: 40px` | CTAButton | FA "행동 동사", 소비자 "탐색형" 모두 가독 보장 |
| `font_sizes.cta.recommended` | "44-48" | `--font-cta-rec: 44px` | CTAButton | task-2428 회귀 차단 — CTA 가독성 우선 |
| `font_sizes.disclaimer.min` | 40 | `--font-disclaimer-min: 40px` | Disclaimer/면책 | "보조 정보이므로 허용 X" — 전 텍스트 40px 절대하한 |
| `font_sizes.disclaimer.recommended` | "40" | `--font-disclaimer-rec: 40px` | Disclaimer | 페이지 인디케이터/브랜드 태그 포함 예외 없음 |
| `font_sizes.absolute_min` | 40 | `--font-absolute-min: 40px` | 모든 텍스트 | FA 태블릿/소비자 모바일 양쪽 페르소나 가독 보장 (절대 하한) |
| `font_sizes.absolute_min_rule` | "40px 미만 절대 금지, 예외 없음" | (validator rule) `LR_FONT_ABS_MIN` | Lite Evaluator L3 | 두 페르소나 모두 양보 X (룰 E 절대 우선) |
| `font_weights.core_metric.min/max` | 900 / 900 | `--weight-core-metric: 900` | MetricCard | FA "전문성/확신" 가치 — Black weight로 권위 |
| `font_weights.headline.min/max` | 800 / 900 | `--weight-headline-min: 800` | Headline | FA preset = 900, 소비자 preset = 800 (가벼운 인상 회피) |
| `font_weights.subhead.min/max` | 600 / 700 | `--weight-subhead-min: 600` | Subhead | task-2-audience: weight 300대 = "확신 없음" 인상 차단 |
| `font_weights.cta.min/max` | 700 / 800 | `--weight-cta-min: 700` | CTAButton | CTA 시인성 — 양쪽 페르소나 모두 굵게 |
| `font_weights.disclaimer.min/max` | 400 / 500 | `--weight-disclaimer: 400` | Disclaimer | 절제된 보조 정보 |
| `font_weights.banned_weights` | [100, 200, 300] | `--weight-banned: never` (validator) | 전 typography | FA "확신/권위" + 소비자 "안 읽힘" 양쪽 차단 |
| `font_weights.banned_weights_rule` | "300 이하 메인/서브 카피 절대 금지" | `LR_WEIGHT_NO_THIN` | Lite Evaluator L3 | 페르소나별 양보 불가 |
| `font_ratio.hierarchy` | "4:2:1" | `--ratio-h-s-b: 4 2 1` | Headline:Subhead:Body | 정보 계층 — FA 정보 밀도 高 / 소비자 단순화 모두 |
| `font_ratio.core_metric_multiplier` | "1.5-2x of headline" | `--ratio-metric-mult: 1.5` | MetricCard | FA "수치 강조" 핵심 가치 |
| `font_ratio.min_head_sub_ratio` | 1.3 | `--ratio-head-sub-min: 1.3` | Headline+Subhead 쌍 | task-1339 "헤드라인-서브카피 1.3배 미만 -3점" 직결 |
| `font_ratio.recommended_head_sub_ratio` | 1.5 | `--ratio-head-sub-rec: 1.5` | Headline+Subhead 쌍 | 시각 계층 명확 (FA 1초 가독 + 소비자 스크롤) |
| `font_pairing.min_families` | 2 | `--font-families-min: 2` | typography | 동일 폰트 1종 시 계층 무너짐 차단 |
| `font_pairing.max_families` | 3 | `--font-families-max: 3` | typography | 산만함 차단 (Apple "3개 이상 동시 사용 금지" 룰과 일치) |
| `font_pairing.recommended_pairs.impact` | Black Han Sans+Pretendard | `--pair-impact: 'Black Han Sans', Pretendard` | SNS/이벤트 preset | 소비자 SNS 스크롤 임팩트 (consumer-warm preset) |
| `font_pairing.recommended_pairs.trust` | 나눔명조+Pretendard | `--pair-trust: '나눔명조', Pretendard` | 보험/금융 preset (★1순위) | FA 페르소나 "전문성/권위" — 명조체 격식 |
| `font_pairing.recommended_pairs.emotion` | GMarket Sans+Pretendard | `--pair-emotion: 'GMarket Sans', Pretendard` | 감성/MZ preset | 소비자 30~50대 친근감 |
| `font_pairing.recommended_pairs.minimal` | Pretendard Black+Light | `--pair-minimal: Pretendard` | 미니멀/테크 preset | 디지털 보험 앱 (Supabase 기반 product-modern preset) |
| `font_pairing.banned_fonts` | [궁서, 굴림, 바탕] | `--font-banned: never` (validator) | 전 typography | 양쪽 페르소나 모두 "구식" 인상 차단 |
| `text_density.max_elements_per_slide` | 3 | `--max-elements: 3` | layout | FA preset = 3 (메인+서브+CTA), 소비자 preset = 2~3 (단순화) |
| `text_density.max_words_per_slide` | 8 | `--max-words: 8` | copy guidance | 0.5초 시선 캐치 — 두 페르소나 공통 |
| `text_density.readability_test_seconds` | 3 | (QC rule) `LR_READ_3SEC` | 카피 검증 | FA 미팅 1초 + 소비자 스크롤 3초 안전 마진 |
| `text_density.overflow_text_rule` | "부가 설명은 광고 Primary Text" | (copy rule) | 카피 가이드 | 배너는 1메시지 1슬라이드 |
| `overlay_rules.full_opaque_ban` | true | `--overlay-full-opaque: forbidden` | hybrid-image | 노하우 #14 "전면 오버레이 금지" 룰화 |
| `overlay_rules.allowed` | [text-shadow, 하단 그라디언트, blur 박스 부분] | `--overlay-allowed-list` | hybrid-image | photo bg + 한글 텍스트 가독성 (knowhow-design #59) |
| `layout.grid_unit` | 8 | `--grid-unit: 8px` | 전 layout | FA "정렬 강박" 가치 — 8pt 그리드 표준 |
| `layout.grid_rule` | "8/16/24/32/48px 배수 통일" | `--spacing-scale: 8 16 24 32 48` | spacing | task-1364 "임의 spacing 13/27px 사용 금지" |
| `layout.alignment_rule` | "하나의 정렬 원칙 일관 적용" | `LR_ALIGN_CONSISTENT` | layout validator | FA 시선 분산 차단 |
| `layout.golden_ratio.top` | "20% — 브랜드 로고/태그라인" | `--zone-top: 0 0 100% 20%` | safe area top | FA preset 카톡 전달 시 브랜드 인지 |
| `layout.golden_ratio.center` | "50% — 핵심 메시지" | `--zone-center: 0 20% 100% 50%` | headline+subhead | 양쪽 페르소나 시선 무게 중심 |
| `layout.golden_ratio.bottom` | "30% — CTA 버튼" | `--zone-bottom: 0 70% 100% 30%` | CTAButton | F자형 시선 흐름 종착점 |
| `color.max_brand_colors` | 3 | `--palette-brand-max: 3` | theme preset | preset당 2~3색 (Apple 룰: 단일 accent, Brex 룰: primary+accent) |
| `color.max_accent_colors` | 1 | `--palette-accent-max: 1` | theme preset | Apple/Supabase "단일 accent" 일치 |
| `color.cta_min_contrast_ratio` | 3.0 | `--cta-contrast-min: 3` | CTAButton | task-2428 재발 차단 + WCAG AA Large 충족 |
| `color.aaa_contrast_ratio` | 4.5 | `--text-contrast-aaa: 4.5` | 본문 텍스트 | knowhow-design #54 "그라디언트 끝점도 4.5:1" |
| `color.rule` | "브랜드 2-3색 + 강조 1색, CTA 3:1" | (composite) | preset | 룰 E 절대 우선 — 페르소나 무관 양보 X |
| `dq_scoring.pass_threshold` | 93 | `--dq-pass-threshold: 93` | Full Evaluator F1~F10 | 회장 수준 — "8/10+ 평균 = 93+" |
| `dq_scoring.items.DQ-01~10` | 각 weight 10 | `--dq-item-weight: 10` (10항목) | Full Evaluator | 매핑 #5-B로 각 항목 분기 |
| `dq_scoring.auto_fail_rules[0]` | "absolute_min 위반 즉시 FAIL" | `LR_AUTOFAIL_FONT_MIN` | Lite L3 + Full | 양쪽 페르소나 절대 하한 |
| `dq_scoring.auto_fail_rules[1]` | "headline 84px 미만 → DQ-08 캡 5점" | `LR_AUTOCAP_HEADLINE_84` | Full F8 | FA preset 우선 — 영업 가독성 |
| `dq_scoring.auto_fail_rules[2]` | "subhead 64px 미만 → DQ-08 캡 6점" | `LR_AUTOCAP_SUBHEAD_64` | Full F8 | task-1463 직결 |
| `dq_scoring.auto_fail_rules[3]` | "1종 폰트만 사용 → -3점" | `LR_PENALTY_FONT_PAIRING` | Full F8 | min_families=2 직결 |
| `dq_scoring.auto_fail_rules[4]` | "전면 불투명 오버레이 → -3점" | `LR_PENALTY_FULL_OPAQUE` | Full F3 | overlay_rules 직결 |
| `benchmarking.priority` | "1순위 dq-rules > 2순위 design-benchmark > 3순위 기타" | (메타 룰) | 전체 우선순위 | preset 충돌 시 dq-rules가 양보 X |
| `benchmarking.rule` | "벤치마킹 결과는 필수 준수 기준" | (메타 룰) | preset 매핑 | 매핑 #3 brex/supabase/apple 적용 시에도 dq-rules 우선 |

**커버리지**: dq-rules.json 모든 키 (font_sizes 14키 + font_weights 7키 + font_ratio 5키 + font_pairing 7키 + text_density 5키 + overlay_rules 3키 + layout 6키 + color 5키 + dq_scoring 17키 + benchmarking 3키) = **57항목 100% 매핑**.

### 매핑표 #1 부록 — Grid 8pt 적용 영역 정의 (★ 로키 HIGH #8 보강)

**문제 인식**: dq-rules `cta.recommended=44px`는 8의 배수가 아님 → font-size에 grid 적용 시 모순 발생.

**해결**: grid 8pt 룰은 **레이아웃/spacing 영역에만 적용**, font-size 등 디자인 가독성 영역은 grid 적용 외.

| 카테고리 | grid 8pt 적용 여부 | 근거 |
|---|---|---|
| spacing (margin/padding/gap) | ✅ 적용 (8/16/24/32/48px 배수) | dq-rules `layout.grid_rule` 직결 |
| bbox top/left/width/height | ✅ 적용 | task-1364 "임의 13/27px 차단" |
| 컴포넌트 배치 좌표 | ✅ 적용 | FA "정렬 강박" 가치 |
| **font-size** | ❌ **적용 외** | dq-rules CTA 44px (8의 배수 아님), headline 84px (8의 배수 아님) — 디자인 가독성 우선 |
| **line-height** | ❌ **적용 외** | 1.0~1.4 비율 기반 (Apple 1.07, Supabase 1.00 등) |
| **border-radius** | ❌ **적용 외** | preset별 8px / 980px / 9999px 등 비-grid 값 허용 |
| icon optical alignment | ⚠️ 4px sub-grid 허용 | 미세 광학 정렬 (HIGH #4 화이트리스트) |

**검증 영향**: 매핑표 #5-A **L4 측정 알고리즘은 위 "적용" 카테고리만 검증 대상**. font-size / line-height / border-radius는 L3 (font-size 선언값) 또는 preset 토큰 매칭(F10)으로 검증.

---

## 매핑표 #2: 노하우 (design-qc + cardnews + design + ad-quality-benchmark) → layout rule

**검증 기준**: 노하우 inventory 누락 0 — "절대 ~하지 말 것" / "반드시 ~할 것" 룰 모두 추출.

| 노하우 출처 | 노하우 룰 | layout rule 이름 | 적용 위치 | 타겟 연결 근거 |
|---|---|---|---|---|
| design-qc-knowhow.md (DQ-01) | "Gemini 프롬프트 사진/장면 키워드 필수" | `LR_BG_PHOTO_PROMPT` | hybrid-image 패턴 | FA "전문가" 인상 / 소비자 스톡 회피 |
| design-qc-knowhow.md (DQ-02) | "헤드/서브 크기 차이 1.3배+ 필수" | `LR_HIERARCHY_RATIO_13` | Headline+Subhead | 양쪽 페르소나 시각 계층 명확 |
| design-qc-knowhow.md (DQ-03) | "텍스트 컨테이너 밖 1개라도 -3점" | `LR_NO_OVERFLOW` | bbox 검증 | FA 카톡 전달 시 본인 신뢰도 직결 |
| design-qc-knowhow.md (DQ-03) | "CTA 텍스트가 버튼 영역 밖 -3점" | `LR_CTA_TEXT_INSIDE` | CTAButton | 양쪽 페르소나 클릭 유도 시각 명확 |
| design-qc-knowhow.md (DQ-03) | "정사각 전체 오버레이 opacity 0.45 = CSS 단색" | `LR_NO_FLAT_OVERLAY_SQUARE` | 1:1 캔버스 | FA SNS 1:1 + 소비자 인스타 |
| design-qc-knowhow.md (DQ-06) | "1080x1080 세로 중앙 배치 안 되면 -4점" | `LR_SQUARE_VCENTER` | 1:1 layout | 모바일 1:1 비율 가독 |
| design-qc-knowhow.md (DQ-06) | "상단 70% 이상 콘텐츠 몰림 -3점" | `LR_NO_TOP_HEAVY` | flex layout | FA "정렬 강박" + 소비자 답답함 회피 |
| design-qc-knowhow.md (DQ-08) | "줄바꿈 의미 단위 아니면 -2점" | `LR_LINEBREAK_SEMANTIC` | text wrap | 한글 가독성 — 두 페르소나 공통 |
| design-qc-knowhow.md (DQ-08) | "1080x1080 헤드라인 60px 미만 -2점" | `LR_SQUARE_HEADLINE_60` | 1:1 Headline | 모바일 SNS 임팩트 |
| design-qc-knowhow.md (디자인 실행 1) | "글자 겹침 절대 금지" | `LR_NO_TEXT_OVERLAP` | z-index validator | FA 영업 미팅 1초 인상 / 소비자 SNS 스크롤 |
| design-qc-knowhow.md (디자인 실행 2) | "그리드 기준 명시, 패딩 수치 지정" | `LR_GRID_DECLARED` | layout container | FA "정렬 강박" 가치 |
| design-qc-knowhow.md (디자인 실행 3) | "AI 배경 분위기와 텍스트 톤 일치" | `LR_BG_TEXT_TONE_MATCH` | hybrid-image | FA 전문성 / 소비자 친근감 매칭 |
| design-qc-knowhow.md (디자인 실행 4) | "여러 소재 동일 레이아웃 반복 금지" | `LR_LAYOUT_VARIETY` | 캠페인 레벨 | 광고 피로도 차단 |
| design-qc-knowhow.md (디자인 실행 5) | "브랜드 폰트 미허용 시 자동 FAIL" | `LR_FONT_WHITELIST` | typography | 브랜드 일관성 (DQ-10) |
| design-qc-knowhow.md (디자인 실행 6) | "색상 코드 HEX 복붙, 육안 판단 금지" | `LR_COLOR_HEX_EXACT` | preset 토큰 | preset 일관성 |
| knowhow-cardnews.md (레이아웃) | "BrandBar 12px 상단 height" | `LR_BRANDBAR_HEIGHT_12` | 카드뉴스 wrapper | FA 본인 카톡 전달 시 브랜드 인지 |
| knowhow-cardnews.md (레이아웃) | "BrandFooter 100px 하단" | `LR_BRANDFOOTER_HEIGHT_100` | 카드뉴스 wrapper | 정보 밀도 + 브랜드 안전영역 |
| knowhow-cardnews.md (레이아웃) | "콘텐츠 영역 top 12px ~ bottom 100px" | `LR_CONTENT_BOUNDS` | content area | 카드뉴스 1080x1350 표준 |
| knowhow-cardnews.md (레이아웃) | "좌우 마진 64px" | `LR_HORIZ_MARGIN_64` | safe area | 양쪽 페르소나 모바일 안전 |
| knowhow-cardnews.md (레이아웃) | "로고 우측 상단 width 180px hbottom 128px" | `LR_LOGO_OVERLAY_180_128` | logo zone | 브랜드 일관성 |
| knowhow-cardnews.md (CTA) | "CTA 상단 패딩 130px (로고 침범 방지)" | `LR_CTA_SAFE_TOP_130` | CTA 슬라이드 | 정보 밀도 + 브랜드 일관성 |
| knowhow-cardnews.md (cover) | "강한 훅 타이틀 42-44px Black 900" | `LR_COVER_HOOK_42` | cover 슬라이드 | 소비자 SNS 스크롤 0.5초 |
| knowhow-cardnews.md (실패 패턴 1) | "body 단일 항목 시 빈 공간 과다" | `LR_BODY_MIN_2_ITEMS` | body 슬라이드 | 정보 밀도 균일 |
| knowhow-cardnews.md (실패 패턴 2) | "CTA 메타데이터만 표시 금지" | `LR_CTA_ACTION_REQUIRED` | CTA 슬라이드 | FA "행동 동사" 가치 |
| knowhow-cardnews.md (실패 패턴 3) | "헤드라인-로고 겹침 차단" | `LR_HEADLINE_LOGO_NOOVERLAP` | z-index | 가독성 우선 |
| knowhow-cardnews.md (테마 룰) | "직전 포스팅 같은 테마 사용 금지" | `LR_THEME_NO_REPEAT` | 테마 선택 | 캠페인 다양성 |
| knowhow-design.md (#1) | "CSS 단색만으로 프로 시도 불가" | `LR_BG_USE_HYBRID` | hybrid-image | FA "스톡 회피" / 소비자 차별화 |
| knowhow-design.md (#9) | "Gemini 배경에 아이콘 = 파워포인트 느낌" | `LR_BG_NO_ICON_PROMPT` | bg prompt validator | 전문성 |
| knowhow-design.md (#10) | "배경+오버레이 동일 아이콘 중복 금지" | `LR_NO_DOUBLE_ICON` | layer validator | 시각 정돈 |
| knowhow-design.md (#12) | "포인트 컬러와 배경 컬러 동조 금지" | `LR_ACCENT_BG_SEPARATE` | preset palette | 강조 효과 보장 |
| knowhow-design.md (#13) | "캐러셀 디자인 언어 통일" | `LR_CAROUSEL_LANG_UNIFIED` | 캠페인 레벨 | 브랜드 일관성 |
| knowhow-design.md (#14) | "어둠→빛 전환 점진적 (그라데이션 브릿지)" | `LR_TRANSITION_GRADIENT` | 슬라이드 시퀀스 | 서사 아크 |
| knowhow-design.md (#16) | "전환 그라데이션 opacity 0.5+ / 높이 400px+" | `LR_TRANSITION_OPACITY_05` | 그라데이션 | 시각 인지 |
| knowhow-design.md (#17) | "보조 정보도 40px 미만 금지 (예외 X)" | `LR_NO_TINY_TEXT` | 모든 텍스트 | dq-rules absolute_min 직결 |
| knowhow-design.md (#18) | "weight 300 이하 + 낮은 대비 조합 금지" | `LR_NO_THIN_LOWCONTRAST` | typography | FA "확신 없음" 차단 |
| knowhow-design.md (#19) | "CTA 흰 텍스트 on 밝은 골드 = 대비 미달" | `LR_CTA_LIGHT_ON_LIGHT_BAN` | CTAButton | 룰 E (양쪽 페르소나 절대) |
| knowhow-design.md (#20) | "광고 내부 레이블 노출 금지 (A-3 등)" | `LR_NO_INTERNAL_LABEL` | 광고 카피 | 감정 몰입 보호 |
| knowhow-design.md (#21) | "glassmorphism opacity 0.15~0.30 (조정 시 0.50)" | `LR_GLASS_OPACITY_RANGE` | 카드 panel | 가독성 보장 |
| knowhow-design.md (#22) | "광고 배너에 슬라이드 번호 금지" | `LR_AD_NO_PAGE_INDEX` | 광고 배너 | 카드뉴스만 번호 |
| knowhow-design.md (#23) | "반투명 레이어가 메인 피사체 가림 금지" | `LR_OVERLAY_NO_HERO_COVER` | overlay zone | 배경 의미 보존 |
| knowhow-design.md (#24) | "반투명 레이어 1~2개 제한" | `LR_MAX_OVERLAY_2` | layer count | 산만함 차단 |
| knowhow-design.md (#26) | "텍스트 컨테이너 white-space:nowrap + letter-spacing -0.02~-0.05" | `LR_NOWRAP_TIGHT` | 뱃지/CTA | DQ-03 직결 |
| knowhow-design.md (#27) | "서브카피 의미 단위 줄바꿈 (자동 줄바꿈 금지)" | `LR_SUBCOPY_BR_MANUAL` | Subhead | 가독성 |
| knowhow-design.md (#28) | "1080x1080 justify-content:center 필수" | `LR_SQUARE_FLEX_CENTER` | 1:1 layout | 노하우 #76 직결 |
| knowhow-design.md (#30) | "CTA width:auto + padding 0 32px + min-width 220px" | `LR_CTA_AUTO_WIDTH` | CTAButton | DQ-03/09 직결 |
| knowhow-design.md (#41) | "서브카피 줄바꿈 시 숫자+단위 분리 금지" | `LR_NUM_UNIT_SAME_LINE` | text wrap | FA "정확 수치" 가치 |
| knowhow-design.md (#54) | "CTA 그라디언트 끝점 contrast 4.5:1 보장" | `LR_GRADIENT_ENDPOINT_45` | CTA 그라디언트 | task-2428 재발 차단 |
| knowhow-design.md (#59) | "photo bg + no overlay 시 text-shadow 4겹 필수" | `LR_TEXTSHADOW_4LAYER` | photo+text | 가독성 보장 |
| knowhow-design.md (#62) | "하단 부분 gradient 40~50%는 전면 오버레이 아님" | `LR_PARTIAL_GRADIENT_OK` | 그라데이션 | overlay_rules 보완 |
| knowhow-design.md (#82) | "다크 BG + 인디블루 accent 카드 시스템" | `LR_DARK_BLUE_CARD_PRESET` | preset 변형 | preset 분기 |
| knowhow-design.md (Supanova) | "AI 클리셰 어휘 금지" (혁신적/원활한/차세대) | `LR_NO_AI_CLICHE` | 카피 검증 | 양쪽 페르소나 자연스러움 |
| knowhow-design.md (Supanova) | "Pretendard 필수, Inter/Roboto/Arial 금지" | `LR_FONT_PRETENDARD` | font-family | 한글 가독성 |
| knowhow-design.md (Supanova) | "word-break: keep-all 한글 줄바꿈" | `LR_WORDBREAK_KEEPALL` | 한글 텍스트 | 음절 중간 줄바꿈 차단 |
| knowhow-design.md (Supanova) | "leading-tight ~ leading-snug 한글 수직 간격" | `LR_LINEHEIGHT_KO` | 한글 텍스트 | 가독성 |
| knowhow-design.md (Supanova) | "warm/cool 그레이 혼용 금지" | `LR_GRAY_TONE_UNIFIED` | preset | 시각 정돈 |
| knowhow-design.md (Supanova) | "AI 퍼플 그라디언트 미사용" | `LR_NO_AI_PURPLE_GRADIENT` | preset | "어디선가 본 듯한 느낌" 차단 |
| knowhow-design.md (Supanova) | "순수 검정 #000000 미사용 (#0a0a0a 사용)" | `LR_NO_PURE_BLACK` | preset palette | Apple "near black" 룰 일치 |
| knowhow-design.md (Supanova) | "h-screen 금지 → min-h-[100dvh]" | `LR_VIEWPORT_DVH` | 뷰포트 | iOS Safari 안전 |
| knowhow-design.md (Supanova) | "CTA 터치 타겟 48px+ 높이" | `LR_CTA_TOUCH_48` | CTAButton | 모바일 (소비자 90%+) |
| knowhow-design.md (Supanova) | "라운드 넘버 회피, 유기적 숫자 사용" | `LR_ORGANIC_NUMBERS` | 카피 검증 | "AI Tell" 차단 |
| knowhow-design.md (Supanova) | "AI 더미 이름 회피, 현대적 이름 사용" | `LR_MODERN_KO_NAMES` | 카피 검증 | 자연스러움 |
| knowhow-design.md (Supanova) | "트랜지션 cubic-bezier(0.16,1,0.3,1)" | `LR_MOTION_SPRING` | 모션 | 자연스러운 움직임 |
| ad-quality-benchmark.md (모바일) | "헤드라인 모바일 60px+ (1080 기준)" | `LR_MOBILE_HEADLINE_60` | 1:1 layout | 소비자 모바일 80% |
| ad-quality-benchmark.md (모바일) | "한글은 영문 기준보다 10~20% 더 크게" | `LR_KO_FONT_120PCT` | typography | 한글 자형 복잡도 |
| ad-quality-benchmark.md (캐러셀) | "전체 슬라이드 동일 비주얼 컨셉" | `LR_CAROUSEL_VISUAL_UNIFIED` | 캠페인 | 일관성 |
| ad-quality-benchmark.md (캐러셀) | "통일 컬러 팔레트 (감정 변화는 명도/채도)" | `LR_PALETTE_HSL_VARIATION` | 캠페인 | 노하우 #15 직결 |
| ad-quality-benchmark.md (스토리) | "훅→문제→해결→증거→CTA 5단 구조" | `LR_STORY_5STEP` | 캐러셀 시퀀스 | FA 영업 + 소비자 설득 |
| ad-quality-benchmark.md (감정) | "두려움→공감→희망→행동 감정 아크" | `LR_EMOTION_ARC` | 카피 시퀀스 | 소비자 공감 우선 |

**커버리지**: design-qc-knowhow (DQ 1~10 + 디자인 실행 6항 + Supanova 25+ 항) + knowhow-cardnews (레이아웃 7 + 슬라이드 타입 5 + 실패/성공 8) + knowhow-design (실패 33 + 성공 82 중 핵심 룰화 가능 항목) + ad-quality-benchmark (모바일 + 캐러셀 + 스토리/감정) = **65+ 룰 inventory 추출**, 누락 0.

### 매핑표 #2 부록 — 추가 layout rule (★ 로키 MEDIUM #9, #10, #11 보강)

| 룰 ID | 정의 | 근거 / 출처 |
|---|---|---|
| `LR_BRANDFOOTER_VISUAL_ONLY` | **BrandFooter는 시각 요소(로고/심볼) + 브랜드명 한정 — body 콘텐츠/카피/CTA 침범 X**. body 콘텐츠는 `LR_CONTENT_BOUNDS` (top 12px ~ bottom 100px) 내부에만 배치. | knowhow-cardnews "BrandFooter 100px 하단" + 로키 MEDIUM #10 보강 |
| `LR_STORY_5STEP_PER_CAMPAIGN` | **"훅→문제→해결→증거→CTA 5단" 구조는 캠페인 단위(5장 카드뉴스 시퀀스)에 분배** — 슬라이드 1장당 1단 표현. 슬라이드당 max_words=8은 1단 메시지에 충분. 단일 광고 배너에 5단 모두 압축 X. | ad-quality-benchmark "5단 스토리" + dq-rules `text_density.max_words_per_slide=8` 산술 정합성 (로키 MEDIUM #9) |
| `LR_DECISION_AUTO_DQRULES` | **"보수적 선택" = preset 룰과 dq-rules 임계값이 충돌하는 모든 자동 결정 케이스에서 dq-rules 우선 적용**. 디자이너 주관 판단 X — Lite/Full Evaluator가 자동 검증. preset 양보 경로 차단. | target-audience.md 룰 D "결정 어려움 시 보수적 선택" 자동화 정의 (로키 MEDIUM #11) |

**산술 정합성 검증** (로키 MEDIUM #9):
- 5장 카드뉴스 × 1단/슬라이드 = 5단 스토리 1캠페인 완성
- 슬라이드당 max_words=8 → 1단(예: "월 만 원으로 시작하세요") 충분 표현
- dq-rules + ad-quality-benchmark 모순 없음 (캠페인 단위로 분배)

---

## 매핑표 #3: 132 design-md 브랜드 (Brex/Supabase/Apple 3종) → theme preset

**검증 기준**: preset당 매핑 명시 + preset 이름 부여 + 타겟 연결 근거.

### 3-1. Token 카테고리별 비교

| 토큰 카테고리 | Brex | Supabase | Apple | 보험 도메인 적용 (페르소나 매칭) |
|---|---|---|---|---|
| Primary color | `#FF6E2B` (오렌지) | `#3ECF8E` (에메랄드) | `#0071e3` (Apple Blue) | preset별 분리 — Brex=소비자, Supabase=B2B/디지털 보험 앱, Apple=럭셔리 소비자 |
| Background (light) | `#F8F4EE` (웜크림) | (없음 — 다크 네이티브) | `#f5f5f7` (Light Gray) | FA preset도 light 분기 가능 (소비자는 light 우선) |
| Background (dark) | (없음 — 라이트 네이티브) | `#171717` (니어블랙) | `#000000` (Pure Black) | FA preset 다크 모드 / product-modern preset 다크 |
| Text Primary | `#1A1A1A` | `#fafafa` | `#1d1d1f` (light) / `#ffffff` (dark) | 양 페르소나 가독성 — contrast 우선 (룰 E) |
| Text Secondary | `#6B7280` | `#b4b4b4` | `rgba(0,0,0,0.8)` | 보조 정보 위계 |
| Border | `#E5E7EB` (light) | `#2e2e2e` ~ `#393939` | (rare, 거의 없음) | Apple 룰 = border 회피, Supabase 룰 = border로 depth |
| Headline font | Cooper Light, Inter | Circular | SF Pro Display | 한글 fallback = Pretendard / 나눔명조 (preset별 다름) |
| Body font | Inter | Circular | SF Pro Text | 한글 = Pretendard (전 preset 공통) |
| H1 size | 48px / weight 700 / -0.02em | 72px / weight 400 / 1.00 | 56px / weight 600 / -0.28px | dq-rules 헤드라인 84px 우선 적용 (preset 양보) |
| H2 size | 36px / weight 700 | 36px / weight 400 | 40px / weight 600 | dq-rules subhead 64px 우선 (preset 양보) |
| Body size | 16px / 1.6 | 16px / 1.50 | 17px / 1.47 / -0.374px | 광고 배너에서는 dq-rules 40px 절대 하한 우선 |
| CTA radius | 8px (standard) | 9999px (pill) + 6px (secondary) | 8px (standard) + 980px (pill link) | preset별 변형 — pill=Apple/Supabase, square=Brex |
| CTA padding | 12px 24px | 8px 32px (pill) | 8px 15px | dq-rules CTA 44~48px 우선 (광고 배너) |
| Spacing scale | 4,8,12,16,24,32,48,64,96 | 1,4,6,8,12,16,20,24,32,40,48,90,96,128 | 2~24 (dense) | 8pt 그리드 표준 (FA "정렬 강박") |
| Shadow philosophy | 4단계 (sm~xl) | minimal (border로 depth) | rare (1단계 soft) | 광고 배너 = Apple/Supabase 룰 (overlay 절제) |
| Accent rule | "1~2회/페이지" | "선택적 — 식별 마커" | "유일 accent — interactive only" | dq-rules max_accent_colors=1 일치 |

### 3-2. Preset 1 — `theme-fa-fintech` (Brex 기반 FA preset)

**타겟**: Primary 페르소나 — 보험 FA (financial advisor)

**매핑**:
```
--palette-base: #F8F4EE (웜 크림 — 차가운 navy 단독 회피)
--palette-primary: #FF6E2B (Brex 오렌지 — 핀테크 신뢰 + 따뜻한 색상으로 보험 거리감 완화)
--palette-accent: #1A1A1A (니어블랙 — CTA/상태)
--palette-text-primary: #1A1A1A
--palette-text-secondary: #6B7280
--font-headline-family: Cooper Light, 'Pretendard', sans-serif (한글 fallback)
--font-body-family: 'Pretendard', Inter, sans-serif
--cta-radius: 8px (square — FA "공식성")
--cta-padding: 0 32px (dq-rules CTA min-width 220px 호환)
--shadow-card: 0 1px 3px rgba(0,0,0,0.06)
--grid-unit: 8px
```

**preset 검증 룰**:
- `LR_FA_PRIMARY_LIMIT`: Primary `#FF6E2B`은 페이지당 1~2회만 사용 (Brex 룰 + dq-rules max_accent=1)
- `LR_FA_NO_PURE_BLACK`: 순수 검정 #000000 금지 (Supanova 룰 일치)
- `LR_FA_FONT_NO_BANNED`: Inter/Roboto/Arial/Malgun Gothic 금지

**타겟 연결 근거**:
- Brex orange `#FF6E2B` = FA 페르소나 "전문성 + 따뜻함" 양립 — 차가운 navy 단독 시 거리감 발생, 오렌지로 친근감 가산
- Cooper Light + Pretendard = 한글 가독성 + 핀테크 모던 (FA의 "은행/증권" 연상 충족)
- CTA radius 8px = FA "공식성" — pill(980px)은 소비자 친근감용
- 웜크림 배경 = FA "정중함" + 영업 미팅 화이트 화면 답답함 회피

### 3-3. Preset 2 — `theme-consumer-warm` (Apple 기반 소비자 preset)

**타겟**: Secondary 페르소나 — 일반 소비자 (30~50대, 모바일 90%+, 보험 비전문가)

**매핑**:
```
--palette-base-light: #f5f5f7 (Light Gray — 친근감)
--palette-base-dark: #0a0a0a (니어블랙 — 임팩트, Supanova "no #000000" 룰 적용)
--palette-primary: #0071e3 (Apple Blue — interactive only)
--palette-accent: 단일 — primary와 동일
--palette-text-primary-light: #1d1d1f
--palette-text-primary-dark: #ffffff
--palette-text-secondary: rgba(0,0,0,0.8)
--font-headline-family: SF Pro Display, 'Pretendard', sans-serif
--font-body-family: SF Pro Text, 'Pretendard', sans-serif
--font-letter-spacing-tight: -0.28px (headline) / -0.374px (body 17px) — 한글은 정상 적용 어려움 → -0.02em으로 변환
--cta-radius: 980px (pill — 친근감)
--cta-radius-secondary: 8px
--shadow-card: rgba(0,0,0,0.22) 3px 5px 30px 0px (Apple 1단계만)
--grid-unit: 8px
--section-bg-rhythm: alternating black ↔ #f5f5f7 (cinematic)
```

**preset 검증 룰**:
- `LR_CONSUMER_SINGLE_ACCENT`: Apple Blue 외 다른 accent 금지
- `LR_CONSUMER_NO_BORDER`: 카드 border 회피 (Apple 룰)
- `LR_CONSUMER_PILL_CTA`: 1차 CTA는 pill (980px), secondary는 8px
- `LR_CONSUMER_TIGHT_HEADLINE`: line-height 1.07~1.14 (Apple billboard 룰)
- `LR_CONSUMER_NO_THICK_FONT`: weight 700 이하 (dq-rules headline_min 800과 충돌 — 광고 배너에서는 dq-rules 우선, 일반 UI에서는 Apple 룰)

**타겟 연결 근거**:
- Apple Blue `#0071e3` = 소비자 모바일 친근 + interactive 신호 명확 (소비자 "어디 누르지?" 즉답)
- Pill CTA (980px) = 친근감 + 부담 없는 어조 ("내게 맞는지 확인" 류 탐색형 CTA에 적합)
- SF Pro Display + Pretendard = 프리미엄 감각 (한글은 Pretendard로 대체)
- alternating section bg = 모바일 스크롤 시 "씬" 구분 — 0.5초 시선 캐치 + 스크롤 멈춤 신호
- weight 600 (Apple 룰) — dq-rules headline_min 800과 충돌 시 광고 배너에서는 dq-rules 우선, 일반 페이지에서는 Apple 룰 적용

### 3-4. Preset 3 — `theme-product-modern` (Supabase 기반 디지털 보험 앱 preset)

> ★ **Phase 1 MVP에서 제외 — Phase 2 이후 회장 confirm 시 도입 (로키 HIGH #7 보강)**
>
> **Phase 1 MVP는 `theme-fa-fintech` + `theme-consumer-warm` 2종만 사용**. product-modern은 본 IDS Phase 4 광고/콘텐츠 이미지 범위 외 (보험사 백오피스 SaaS / 디지털 앱 UI 영역).
>
> **타겟 정합성 정당화**: target-audience.md는 FA(Primary) + 일반 소비자(Secondary) 2종 페르소나만 정의. product-modern 페르소나(개발자/B2B SaaS 사용자)는 Phase 1 페르소나 정의 외 → MVP 진입 불가, Phase 2에서 페르소나 확장 결정 후 도입.

**타겟 (Phase 2 이후)**: 옵션 — 디지털 보험 상품(앱) 노출용 / B2B 솔루션 (FA용 백오피스 SaaS) — IDS Phase 4 광고 이미지 영역 외

**매핑**:
```
--palette-base: #171717 (다크 네이티브 — 개발자/디지털 페르소나)
--palette-base-deeper: #0f0f0f
--palette-primary: #3ECF8E (Supabase 그린 — 식별 마커, 사용 절제)
--palette-link: #00c573
--palette-text-primary: #fafafa
--palette-text-secondary: #b4b4b4
--palette-text-muted: #898989
--palette-border-subtle: #242424
--palette-border-standard: #2e2e2e
--palette-border-prominent: #393939
--font-headline-family: Circular, 'Pretendard', sans-serif
--font-body-family: Circular, 'Pretendard', sans-serif
--font-mono: 'Source Code Pro', monospace (technical labels)
--font-headline-line-height: 1.00 (zero-leading 시그니처)
--cta-radius-primary: 9999px (pill)
--cta-radius-secondary: 6px
--shadow: none (border로 depth)
--grid-unit: 8px
```

**preset 검증 룰**:
- `LR_PRODUCT_DARK_NATIVE`: 배경 #171717 미만 라이트화 금지
- `LR_PRODUCT_GREEN_SPARSE`: 그린은 logo/link/border만 (배경/대형 면적 금지)
- `LR_PRODUCT_NO_SHADOW`: box-shadow 금지 (border로 depth)
- `LR_PRODUCT_HERO_LH_100`: hero line-height 1.00 (Supabase 시그니처)
- `LR_PRODUCT_NO_BOLD`: weight 700 금지 (400/500만, dq-rules와 충돌 — 광고 배너 외 영역에만 적용)

**타겟 연결 근거**:
- 다크 네이티브 = 디지털 보험 앱 페르소나 (모바일 OLED 가독성 + 야간 사용)
- Supabase 그린 절제 = "디지털/현대" 식별 마커 (보험 도메인 전통 navy/red와 차별화)
- Circular + Pretendard = 한글 친화 + 미니멀 SaaS 느낌
- 9999px pill CTA = 디지털 친근감 (FA 영업 외 디지털 전환 페르소나에 적합)
- Supabase 룰 = dq-rules와 일부 충돌 (weight 400 vs dq-rules headline weight 800) → 광고 배너에서는 dq-rules 우선, B2B SaaS UI에서는 Supabase 룰 우선

### 3-5. Preset 충돌 해결 룰 (target-audience.md 룰 A~E 적용) ★ 로키 CRITICAL #3 전면 보강

**핵심 전제 (회장 양보 경로 차단)**:

1. **Satori 출력 = 100% 광고 배너/시각 콘텐츠 분류** — 본 IDS Phase 4 산출물 전체가 광고/카드뉴스/배너 영역. "광고 배너 vs 일반 페이지" 분기 정의 자체가 **존재하지 않음** (모든 출력이 광고 배너).
2. **dq-rules `absolute_min` / 임계값 = 절대 양보 X** (룰 E 일관) — preset 룰이 dq-rules와 충돌 시 **preset 값을 무시하고 dq-rules 값 적용**.
3. **preset 룰의 적용 영역 한정**: preset 룰은 dq-rules와 충돌하지 않는 영역(예: radius, spacing scale, font-family 선택, palette 색상 코드)에만 적용 가능.
4. **theme-product-modern preset은 Phase 1 MVP 제외** (3-4 섹션 명시) — Supabase weight 400 같은 dq-rules 충돌 룰은 Phase 1에서 도입조차 X.
5. **자동 결정**: `LR_DECISION_AUTO_DQRULES` (매핑 #2 부록) — 충돌 시 자동으로 dq-rules 적용, 디자이너 주관 X.

**충돌 해결 매트릭스** (양보 경로 차단 명시):

| 충돌 케이스 | 해결 (Phase 1 MVP) | 양보 경로 차단 |
|---|---|---|
| Apple weight 600 vs dq-rules headline weight 800 | **dq-rules 800 적용** (Apple 600 무시) | "일반 페이지" 분기 X — 모든 Satori 출력은 광고 배너 |
| Supabase weight 400 vs dq-rules headline weight 800 | **dq-rules 800 적용** + Supabase preset Phase 1 제외 | product-modern preset 자체가 Phase 1 미진입 |
| Supabase 다크 (Phase 1 제외) | Phase 1 = consumer-warm light/dark + fa-fintech light만 사용 | dark 네이티브는 Phase 2 이후 |
| Brex 8px CTA vs Apple 980px pill | preset 분리 — fa-fintech=8px, consumer-warm=980px (dq-rules는 radius 미규제) | 충돌 없음 (양 preset 모두 허용 영역) |
| Apple letter-spacing -0.374px (한글 부적합) | 한글은 `-0.02em`으로 변환 (Apple letter-spacing 룰 양보) | dq-rules 무관 — 한글 가독성 우선 |
| dq-rules absolute_min 40px vs Apple body 17px | **40px 절대 하한 적용** (Apple 17px 무시) | 광고 배너 = 모든 Satori 출력 (예외 X) |
| dq-rules CTA 44~48px vs Brex/Apple/Supabase CTA | **dq-rules 44~48px 적용** | preset CTA 사이즈 무시 |
| dq-rules max_brand_colors=3 vs preset 추가 색 | **dq-rules 3색 한도 적용** | preset 확장 양보 X |

**preset 룰의 적용 가능 영역** (dq-rules 무관):
- font-family 선택 (Cooper Light / SF Pro / Circular — 단, banned_fonts 룰 준수)
- palette 색상 코드 (#FF6E2B / #0071e3 등 — 단, max_brand_colors=3 준수)
- spacing scale 값 (4/8/12/16... 등 — 8pt 그리드 준수 시)
- border-radius 값 (8px / 980px / 9999px — dq-rules 미규제)
- shadow philosophy (Apple soft / Supabase border / Brex 4단계)
- letter-spacing (한글 변환 시)

**preset 룰의 적용 불가 영역** (dq-rules 양보 X):
- font-size 임계값 (absolute_min / headline.min / subhead.min / cta.min / disclaimer.min)
- font-weight 임계값 (banned_weights / 각 컴포넌트 min~max)
- font_ratio (head/sub 1.3배 미만 X)
- color contrast (CTA 3:1 / AAA 4.5:1)
- max_brand_colors / max_accent_colors
- overlay_rules (full_opaque_ban)
- text_density (max_elements / max_words)

**검증**: `LR_DECISION_AUTO_DQRULES`가 Lite/Full Evaluator에서 자동 적용 → preset이 dq-rules 양보 경로로 작동 불가.

---

## 매핑표 #4: 기존 quality_evaluator 5축 → regression gate (PIL fixture 한정)

**검증 기준**: PIL fixture 연동 명시 + **contrast 미측정 한계 명확히 분리** (★ Codex 발견 — task-2428 원인).

| evaluator 함수 | 측정 항목 | 측정 가능 여부 | regression 사용 가능? | 한계 / 분리 결정 |
|---|---|---|---|---|
| `check_visual_diversity(img)` | RGB std + unique colors + spatial coherence (인접 픽셀 차이 평균) | ✅ PIL only | ✅ (PIL fixture 회귀 게이트) | 다양성/단색 우회 차단 — TV-static 노이즈 silent corruption 차단 (로키 적대적 평가). **디자인 품질 측정 X** — 회귀 fixture 한정 사용 |
| `check_brand_color_match(img, design_md)` | Lab ΔE + 매칭 색상 영역 비율 ≥10% | ✅ (색상만) | ⚠️ 회귀 가능, 게이트 부적합 | **★ contrast 미측정 → task-2428 원인 직결**. ΔE=0.00이어도 텍스트-배경 대비 안 맞음 = 아마추어 평가. → contrast는 매핑 #5 **Lite Evaluator L1**으로 신규 분리. 본 함수는 "팔레트 적용 회귀"만 책임 |
| `check_hybrid_pattern(img, hybrid_pattern)` | 5 hybrid 패턴(h1~h5)별 edge density / unique colors / saturation / smoothness / noise ratio | ✅ PIL only | ✅ (회귀 게이트) | 시각 패턴 분류 회귀만 (h2 unique_colors >1000 등). **디자인 품질 X** — preset 시스템에서는 매핑 #5 Lite/Full로 대체 |
| `check_font_size(img, target_size)` | OCR + 픽셀 검증 (40px 절대 하한) | ⚠️ Tesseract 의존 | ❌ 환경 의존 (CI 불안정) | OCR 부재 시 BLOCKED (silent pass 차단) — 환경 의존이므로 **regression 게이트 부적합**. → 매핑 #5 **Full Evaluator F11**로 이관 (수동 환경에서만 실행) + **Lite L3에서는 CSS font-size 선언값 정적 검증으로 대체** |
| `check_ocr_confidence(img)` | confidence ≥70% AND 한글 유니코드 비율 ≥50% | ⚠️ Tesseract 의존 | ❌ 환경 의존 | 동일 — Full F11로 이관, Lite에서는 미사용 |

### 분리 결정 (★ Codex 발견 반영)

**결론**: `quality_evaluator.py`는 **PIL fixture 회귀 게이트로만 한정**.

- **사용 영역**: procedural PIL renderer (`_pil_render.py`) 출력의 **결정성 회귀 검증**만 수행
  - 동일 seed → 동일 픽셀 sha256 일치 검증
  - hybrid 패턴별 정량 임계 회귀 (h2 unique_colors > 1000 등)
  - 브랜드 색상 ΔE 회귀 (preset 변경 시 회귀 차단)
- **사용 X 영역**: 디자인 품질 평가 (contrast / hierarchy / safe-area / typography / WCAG / preset 일관성)
  - 이유 1: `check_brand_color_match`는 contrast 미측정 → task-2428 재발 위험
  - 이유 2: OCR 의존 함수는 환경 의존 → silent pass / silent BLOCKED 위험
  - 이유 3: 디자인 품질 임계는 CSS/HTML 정적 분석이 더 적합 (Satori 렌더링 전 단계)
- **신규 분리**: 디자인 품질은 매핑 #5 **Phase 0.5 Lite Evaluator (5항목, OCR 비의존)** + **Phase 2 Full Evaluator (10+ 항목, OCR 가능)** 로 분리.

### PIL fixture 연동 명시

```
fixture path: skills/satori-cardnews/tests/fixtures/pil_regression/
- baseline_h1_v1_seed42.png (sha256 hash)
- baseline_h2_v3_seed42.png
- baseline_h3_v2_seed42.png
- baseline_h4_v3_seed42.png
- baseline_h5_v4_seed42.png

regression test:
1. _pil_render.py(seed=42, theme="brex") → output.png
2. quality_evaluator.check_visual_diversity(output) → assert passed=True
3. quality_evaluator.check_hybrid_pattern(output, "h2") → assert passed=True
4. quality_evaluator.check_brand_color_match(output, brex_design_md) → assert ΔE<30, area>=10%
5. sha256(output) == sha256(baseline_h2_v3_seed42.png)
```

검증 함수 4종 (visual_diversity / brand_color_match / hybrid_pattern / + sha256) = **PIL 회귀 게이트 4축**. font_size / ocr_confidence는 환경 의존이므로 Full Evaluator로 분리.

---

## 매핑표 #5: 새 design quality gate (Lite 5항목 ↔ Full 10+ 항목 분리)

**검증 기준**: gate vs decoration 분리 — Lite는 환경 비의존 즉시 PASS/FAIL, Full은 휴먼/OCR 포함 의사결정.

### 5-A. Phase 0.5 Lite Evaluator (5항목, OCR 비의존, PIL/CSS 정적 분석)

| # | 항목 | 측정 방법 | 임계값 | 차단 시 의미 | 타겟 연결 근거 |
|---|---|---|---|---|---|
| **L1** | **Contrast (text vs bg) — 글리프 픽셀별 분포 측정** | 글리프 픽셀 추출 → 각 픽셀별 WCAG ratio 계산 → 분포 percentile 검증 (★ 알고리즘 상세는 아래 L1 코드 박스) | **5th percentile ≥ 4.5:1** (AA 최저 — 절대 하한) AND **95th percentile ≥ 7:1** (AAA 권장) AND **CTA min 3:1** (dq-rules `cta_min_contrast_ratio`) | **task-2428 재발 차단 (★ Codex 발견 + 로키 CRITICAL #1 반영)** — 그라데이션 끝점/photo bg 우회 차단 | FA 1초 가독 + 소비자 모바일 30cm 가독 — 룰 E 절대 우선 |
| **L2** | **Safe-area 침범** | CSS bbox / canvas 좌표 → 가장자리 distance 계산 | safe-area = canvas의 5%~8% (top/bottom/left/right) — 텍스트 절대 침범 X | 텍스트 잘림 차단 (모바일 카톡 썸네일 크롭 안전) | 1:1 / 4:5 / 9:16 모두 적용 — FA 카톡 전달 + 소비자 인스타 피드 |
| **L3** | **Font-size 선언값** | CSS font-size 정적 분석 (OCR 비의존) | dq-rules `absolute_min` 40px / `headline.min` 84px / `subhead.min` 64px / `cta.min` 40px / `disclaimer.min` 40px | 가독성 임계값 위반 차단 (knowhow #17 "보조 정보도 예외 없음") | dq-rules 직결 — FA 태블릿 + 소비자 모바일 양쪽 양보 X |
| **L4** | **Grid 정렬 (spacing/bbox 한정)** | spacing/bbox top/left/width/height만 grid-unit (8px)으로 검증. font-size/line-height/border-radius 제외 (매핑 #1 부록 정의) | **정렬률 100%** (8pt 그리드) — 예외: **4px sub-grid 화이트리스트** (icon optical alignment, line-height 미세 조정만) | 시각 정돈성 — 임의 13/27px spacing 차단 (task-1364), 5% 임의 이탈 경로 차단 (★ 로키 HIGH #4 보강) | FA "정렬 강박" 가치 + 소비자 답답함 회피 |
| **L5** | **Color palette 분포 + AI 퍼플 검출** | PIL 색상 클러스터링 (k-means k=5 + hue Δhue<15° 통합) + 그라데이션 3샘플 추출 + AI 퍼플 hue range 검출 (★ 알고리즘 상세는 아래 L5 코드 박스) | 브랜드 2~3색 + accent 1색 = **4색 이내** (dq-rules `color.max_brand_colors=3` + `max_accent_colors=1`) AND **AI 퍼플 그라디언트 (hue 270°~300° + sat>0.5) 캔버스 10%+ 차지 시 즉시 FAIL** | 다채성/AI 퍼플 그라디언트 차단 (Supanova `LR_NO_AI_PURPLE_GRADIENT` 직결) | preset 일관성 — fa-fintech / consumer-warm 2종 매핑 검증 (product-modern Phase 1 제외) |

#### L1 측정 알고리즘 상세 (★ 로키 CRITICAL #1 핵심 보강 — 그라데이션/photo-bg 우회 차단)

**문제 인식**: 단일 픽셀쌍 WCAG ratio는 그라데이션/photo bg 위 텍스트에서 끝점만 7:1 맞추면 PASS 우회 가능 → task-2428 회장 평가 "ΔE=0.00이어도 색 대비 안 맞음" 재발 직격탄.

**해결**: 글리프 픽셀별 contrast 분포를 측정하여 percentile 임계값으로 검증.

```python
# L1 contrast 측정 알고리즘 (Phase 0.5 Lite Evaluator)

def measure_text_bg_contrast(rendered_png, text_bbox, text_alpha_mask, layers):
    """
    rendered_png: Satori 최종 출력 PNG (PIL Image, RGBA)
    text_bbox: 텍스트 영역 bbox (x, y, w, h) — Satori 레이아웃 메타데이터
    text_alpha_mask: 글리프 alpha mask (PIL L 모드, 0~255)
    layers: [bg_layer, ...overlay_layers, text_layer] — alpha-composite 순서
    """

    # Step 1: 글리프 픽셀 추출 (텍스트 영역 bbox + alpha > 128)
    glyph_pixels = []  # [(x, y), ...]
    for y in range(text_bbox.y, text_bbox.y + text_bbox.h):
        for x in range(text_bbox.x, text_bbox.x + text_bbox.w):
            if text_alpha_mask.getpixel((x - text_bbox.x, y - text_bbox.y)) > 128:
                glyph_pixels.append((x, y))

    if not glyph_pixels:
        raise RuntimeError("LITE_FAIL: L1_NO_GLYPH_PIXELS")

    # Step 2: 반투명 오버레이 처리 — text 아래 모든 레이어 alpha-composite
    #         (그라데이션/photo bg + 오버레이 박스 → effective background 합성)
    effective_bg = compose_layers_below_text(layers)  # PIL Image, RGB

    # Step 3: 글리프 픽셀별 contrast 계산
    contrasts = []
    for (x, y) in glyph_pixels:
        text_rgb = layers.text_layer.getpixel((x, y))[:3]  # 글리프 텍스트 색
        bg_rgb = effective_bg.getpixel((x, y))             # 해당 위치 합성 배경 색
        ratio = wcag_contrast_ratio(text_rgb, bg_rgb)      # (L1+0.05)/(L2+0.05)
        contrasts.append(ratio)

    # Step 4: 분포 percentile 임계값 검증
    contrasts.sort()
    p5 = percentile(contrasts, 5)    # 최악 5% 픽셀 (그라데이션 어두운 끝점 등)
    p95 = percentile(contrasts, 95)  # 상위 5% 픽셀

    # AA 절대 하한: 5th percentile ≥ 4.5:1 (대형 텍스트는 3:1)
    if p5 < 4.5:
        raise RuntimeError(f"LITE_FAIL: L1_CONTRAST_P5={p5:.2f} < 4.5 (그라데이션/photo bg 끝점 가독성 미달)")

    # AAA 권장: 95th percentile ≥ 7:1
    if p95 < 7.0:
        warn(f"L1_CONTRAST_P95={p95:.2f} < 7.0 (AAA 미달 — 권장 수준 하회)")

    # CTA 추가 검증: dq-rules cta_min_contrast_ratio
    if is_cta_text(text_bbox):
        if p5 < 3.0:
            raise RuntimeError(f"LITE_FAIL: L1_CTA_CONTRAST_P5={p5:.2f} < 3.0")

    return {"p5": p5, "p95": p95, "passed": p5 >= 4.5}


def compose_layers_below_text(layers):
    """반투명 오버레이/그라데이션을 text 레이어 직전까지 alpha-composite"""
    base = layers.bg_layer.convert("RGBA")
    for overlay in layers.overlay_layers:
        base = Image.alpha_composite(base, overlay.convert("RGBA"))
    return base.convert("RGB")
```

**처리 케이스별 동작**:

| 배경 유형 | L1 동작 | 차단 효과 |
|---|---|---|
| 단색 배경 | 모든 글리프 픽셀 contrast 동일 → p5=p95 | 기존 단일 픽셀 측정과 동등 |
| 그라데이션 배경 | 글리프 픽셀별 contrast 분포 발생 (어두운 끝~밝은 끝) → p5가 어두운/밝은 끝점 측정 | 끝점만 맞추는 우회 차단 (knowhow #54 직결) |
| Photo bg + 텍스트 | 글리프마다 다른 배경 픽셀 → contrast 분포 광범위 | 한 부분만 contrast 좋고 나머지 미달 시 p5 즉시 FAIL |
| 반투명 오버레이 + 텍스트 | alpha-composite 후 effective bg 계산 → 정확한 contrast | 오버레이 의도 무시 우회 차단 |
| 그림자/text-shadow 4겹 | shadow는 effective bg에 포함됨 (knowhow #59) | text-shadow가 contrast 보정 효과 자동 반영 |

**knowhow-design `LR_GRADIENT_CONTRAST` 통합**: 매핑 #2의 `LR_GRADIENT_ENDPOINT_45` ("그라디언트 끝점도 4.5:1") 룰은 본 L1 알고리즘의 5th percentile ≥ 4.5:1 요구로 자동 충족 — 끝점이 가장 낮은 contrast 영역이므로 p5에 자연 포착됨.

**측정 입력 요구사항**:
- Satori 출력 시 텍스트 bbox 메타데이터 필수
- 텍스트 글리프 alpha mask 필수 (text 레이어의 alpha 채널)
- 레이어 스택 순서 명시 (bg → overlays → text)

#### L5 측정 알고리즘 상세 (★ 로키 HIGH #5 보강 — 그라데이션 카운트 + AI 퍼플 검출)

```python
# L5 color palette 측정 알고리즘 (Phase 0.5 Lite Evaluator)

def measure_color_palette(rendered_png, gradient_regions, preset):
    """
    rendered_png: Satori 최종 출력 PNG
    gradient_regions: 그라데이션 영역 좌표 리스트 (Satori 메타데이터)
    preset: theme-fa-fintech | theme-consumer-warm
    """

    # Step 1: 그라데이션 처리 — 시작/중간/끝 3샘플 추출 후 hue 분류
    gradient_hues = []
    for region in gradient_regions:
        for t in [0.0, 0.5, 1.0]:  # 시작/중간/끝 3샘플
            rgb = sample_gradient_at(rendered_png, region, t)
            gradient_hues.append(rgb_to_hsl(rgb))

    # Step 2: k-means k=5로 주요 색상 추출
    pixels = list(rendered_png.getdata())
    clusters = kmeans(pixels, k=5)  # 5개 클러스터

    # Step 3: hue 통합 — Δhue < 15°는 동일 색으로 간주 (그라데이션 카운트 모호 해결)
    unified_hues = []
    for cluster in clusters:
        h, s, l = rgb_to_hsl(cluster.center)
        merged = False
        for existing in unified_hues:
            if abs(h - existing.h) < 15 or abs(h - existing.h) > 345:  # 원형 hue
                existing.weight += cluster.weight
                merged = True
                break
        if not merged:
            unified_hues.append(HueGroup(h=h, s=s, l=l, weight=cluster.weight))

    # Step 4: 그라데이션 hue도 unified_hues에 통합
    for grad_hsl in gradient_hues:
        merged = False
        for existing in unified_hues:
            if abs(grad_hsl.h - existing.h) < 15:
                merged = True
                break
        if not merged:
            unified_hues.append(HueGroup(h=grad_hsl.h, s=grad_hsl.s, l=grad_hsl.l, weight=0.05))

    # Step 5: 4색 이내 검증 (brand 2~3 + accent 1)
    significant_hues = [h for h in unified_hues if h.weight > 0.02]  # 캔버스 2%+
    if len(significant_hues) > 4:
        raise RuntimeError(f"LITE_FAIL: L5_TOO_MANY_COLORS={len(significant_hues)} > 4")

    # Step 6: AI 퍼플 그라디언트 검출 (★ 핵심 보강)
    purple_pixel_count = 0
    total_pixels = rendered_png.width * rendered_png.height
    for pixel in pixels:
        h, s, l = rgb_to_hsl(pixel)
        if 270 <= h <= 300 and s > 0.5:
            purple_pixel_count += 1

    purple_ratio = purple_pixel_count / total_pixels
    if purple_ratio > 0.10:  # 캔버스 10%+
        raise RuntimeError(
            f"LITE_FAIL: L5_AI_PURPLE_GRADIENT={purple_ratio:.1%} > 10% "
            f"(LR_NO_AI_PURPLE_GRADIENT 직결 — Supanova 룰 위반)"
        )

    # Step 7: preset palette 매칭 (브랜드 색상 일치)
    preset_hues = preset.get_hue_set()  # {primary, accent, base} hue 추출
    preset_match = compute_hue_overlap(significant_hues, preset_hues)
    if preset_match < 0.7:
        warn(f"L5_PRESET_MATCH={preset_match:.1%} < 70% (preset 일관성 약화)")

    return {
        "color_count": len(significant_hues),
        "ai_purple_ratio": purple_ratio,
        "preset_match": preset_match,
        "passed": True,
    }
```

**처리 케이스별 동작**:

| 케이스 | L5 동작 |
|---|---|
| 단색 4종 + accent 1종 | k-means 5색 → hue 통합 후 5개 → significant 5개면 FAIL, accent 작은 면적이면 4개로 PASS |
| 그라데이션 (3색) | 3샘플 추출 → 비슷한 hue는 1색으로 통합 (Δhue<15°) → 그라데이션이 5색 카운트 부풀리지 않음 |
| AI 퍼플 그라데이션 | hue 270~300° + sat>0.5 픽셀 카운트 → 10%+ 즉시 FAIL |
| Brex 오렌지 + 니어블랙 + 웜크림 | 3색 PASS (brand 2~3 + accent 1 = 4 이내) |

**Lite Evaluator 사용 시점**: Satori HTML/CSS 렌더링 직후, **OCR 없이 정적 분석으로 즉시 PASS/FAIL** 결정. CI 통합 가능 (환경 비의존).

**차단 시 동작**: 5항목 중 1개라도 FAIL → `RuntimeError("LITE_FAIL: <항목>")` 명시 raise. Phase 1 Satori 렌더 → Lite PASS → Phase 2 Full 평가 진행.

### 5-B. Phase 2 Full Evaluator (10+ 항목, OCR 포함 가능, 휴먼 평가 포함)

| # | 항목 | 측정 방법 | 임계값 | dq-rules 매핑 |
|---|---|---|---|---|
| **F1** | DQ-01 레퍼런스 재현도 | 시각 유사도 (LPIPS / SSIM) + 휴먼 평가 | 8/10+ (총 93+점) | `dq_scoring.items.DQ-01` weight 10 |
| **F2** | DQ-02 시각적 계층 | bbox + font-size 분포 → 헤드>>서브>>본문 비율 (dq-rules `font_ratio.hierarchy=4:2:1`) | head/sub ≥1.3 (auto-cap 룰) | `dq_scoring.items.DQ-02` |
| **F3** | DQ-03 프로 완성도 | 사인 패턴 (오버플로우/정렬/줄바꿈/요소 수용) + 휴먼 | 8/10+ — 텍스트 컨테이너 밖 1개라도 -3 | `dq_scoring.items.DQ-03` + `auto_fail[0,4]` |
| **F4** | DQ-04 피드 차별화 | 컬러 히스토그램 vs 평균 피드 + 레이아웃 다양성 | 차별화 점수 8/10+ | `dq_scoring.items.DQ-04` |
| **F5** | DQ-05 감정 임팩트 | 색상 심리 분석 + 휴먼 평가 | 8/10+ | `dq_scoring.items.DQ-05` |
| **F6** | DQ-06 여백/레이아웃 | bbox 분포 + 8pt 그리드 + golden ratio (top 20% / center 50% / bottom 30%) | 8/10+ — 상단 70% 몰림 -3 | `dq_scoring.items.DQ-06` + `layout.golden_ratio` |
| **F7** | DQ-07 색상 가시성 | WCAG AAA contrast (Lite L1 결과 재활용) + ΔE | AAA 7:1 (대형 4.5:1) | `dq_scoring.items.DQ-07` + `color.aaa_contrast_ratio` |
| **F8** | DQ-08 타이포그래피 | font 가족 수 (2~3) + weight (banned [100,200,300]) + tracking + head/sub 비율 | weight 600+, families 2~3, head 84+, sub 64+ | `dq_scoring.items.DQ-08` + `font_pairing` + `font_weights` |
| **F9** | DQ-09 CTA 효과성 | bbox + 색상 + 행동 동사 + contrast | min 3:1 + 명확 동사 ("상담 신청" 등) | `dq_scoring.items.DQ-09` + `color.cta_min_contrast_ratio` |
| **F10** | DQ-10 브랜드 일관성 | preset 토큰 일치 (palette + font-family + radius + spacing) | 100% preset 토큰 매칭 | `dq_scoring.items.DQ-10` |
| **F11** | OCR 한글 신뢰도 | Tesseract pytesseract — confidence + 한글 유니코드 비율 | 70%+ AND 한글 비율 ≥50% | (dq-rules 외 — quality_evaluator.check_ocr_confidence 이관) |
| **F12** | OCR 픽셀 폰트 검증 | OCR + 픽셀 측정 → font_size 절대 하한 | dq-rules `font_sizes.absolute_min=40` | (quality_evaluator.check_font_size 이관) |

**Pass threshold**: 총합 ≥93점 (dq-rules `dq_scoring.pass_threshold=93`).

**Auto-fail rules** (dq-rules 명시):
1. `font_sizes.absolute_min` 위반 → 즉시 FAIL
2. `font_sizes.headline.min` 위반 → DQ-08 최대 5점 캡
3. `font_sizes.subhead.min` 위반 → DQ-08 최대 6점 캡
4. `font_pairing.min_families` 위반 (1종만) → DQ-08 -3점
5. `overlay_rules.full_opaque_ban` 위반 → DQ-03 -3점

### 5-C. Lite vs Full 분리 결정 근거 (★ Codex 발견 직결)

| 측면 | Lite (5항목) | Full (10+ 항목) |
|---|---|---|
| 환경 의존 | 비의존 (PIL/CSS 정적) | 의존 (Tesseract / 휴먼 평가) |
| 실행 속도 | <1s / image | 30s+ (OCR + 휴먼) |
| CI 통합 | ✅ 가능 | ❌ 불가 (휴먼 + OCR 환경) |
| 사용 시점 | Phase 1 Satori 렌더 직후 | Phase 2 최종 평가 |
| 차단 동작 | RuntimeError 명시 raise | DQ 점수 산정 + 93+ 게이트 |
| **task-2428 재발 차단** | **L1 contrast 측정으로 1차 차단** | F7 AAA contrast 2차 검증 |
| 분리 이유 | OCR BLOCKED → silent FAIL 위험 차단 (Codex 발견) | 휴먼/OCR 포함 의사결정용 |

**핵심 분리 이유** (Codex 발견):
- 기존 `quality_evaluator`는 OCR 의존 함수가 BLOCKED일 때 `passed=False` (전체 FAIL) 처리됨 (task-2428 사례)
- 시각/브랜드/하이브리드 PASS 27/27이어도 OCR/font_size BLOCKED만으로 전체 FAIL → **silent BLOCKED → 잘못된 FAIL**
- Lite는 OCR 미의존 5항목으로 즉시 PASS/FAIL 결정 → CI 안정성
- Full은 별도 휴먼 게이트로 분리 → 환경 의존 항목이 CI를 막지 않음

**gate vs decoration 분리**:
- **Gate (필수 차단)**: Lite 5항목 — 위반 시 렌더링 자체 차단 (Phase 1 Satori → Phase 2 진입 봉쇄)
- **Decoration (점수 평가)**: Full 10+ 항목 — 점수 산정 후 93+ 미달 시 재작업 요청 (수동 의사결정 가능)

---

## 매핑표 5종 종합 검증 (target-audience.md 체크리스트 적용)

- [x] **모든 디자인 결정에 페르소나 근거 존재** — 매핑 #1~5 각 행에 "타겟 연결 근거" 컬럼 충족
- [x] **FA / 소비자별 토큰 분기** — 매핑 #3에서 Phase 1 MVP는 `theme-fa-fintech` + `theme-consumer-warm` 2종 (product-modern은 Phase 2 이후)
- [x] **dq-rules 임계값 페르소나 정당화** — 매핑 #1 모든 행에서 "FA 태블릿/소비자 모바일" 등 디바이스 가정 명시
- [x] **task-2428 "아마추어 수준" 재발 차단** — 매핑 #4 contrast 미측정 한계 분리 + 매핑 #5 Lite L1 글리프 픽셀별 분포 측정 (5th percentile ≥ 4.5:1, 95th percentile ≥ 7:1)
- [x] **CTA 톤 토큰 페르소나 분기** — preset별 CTA radius/padding 분리 (FA 8px square / 소비자 980px pill)
- [x] **충돌 해결 룰 적용 사례** — 매핑 #3-5에서 Satori = 100% 광고 배너 분류 + dq-rules 양보 경로 차단 명시
- [x] **컨텍스트 디바이스 매칭** — FA 태블릿 1m (84px+) vs 소비자 모바일 30cm (모바일 60px+) 반영
- [x] **메시지 톤 분기 명시** — Headline/Body/CTA 카피 가이드 페르소나별 분리 (정확수치 vs 일상환산)

### 로키(레드팀) 약점 보강 체크 (★ 본 보강 라운드)

- [x] **CRITICAL #1 — L1 contrast 그라데이션/photo-bg 우회 차단** — 글리프 픽셀별 WCAG ratio 분포 + 5th percentile 4.5:1 + 95th percentile 7:1 + alpha-composite 처리 + AI 퍼플 검출 알고리즘 추가 (매핑 #5-A L1 코드 박스)
- [x] **CRITICAL #3 — preset 양보 경로 차단** — Satori = 100% 광고 배너 분류, dq-rules 절대 양보 X, "광고 배너 vs 일반 페이지" 분기 정의 자체 제거 (매핑 #3-5 전면 보강)
- [x] **HIGH #4 — Grid 정렬 100% + 4px sub-grid 화이트리스트** — 95% → 100% 상향, 의도적 5% 이탈 경로 차단, icon optical alignment만 4px 허용 (매핑 #5-A L4)
- [x] **HIGH #5 — Color palette 그라데이션 카운트 + AI 퍼플 검출** — 그라데이션 3샘플 + Δhue<15° 통합 + AI 퍼플 hue 270~300° + sat>0.5 + 캔버스 10%+ FAIL (매핑 #5-A L5 코드 박스)
- [x] **HIGH #7 — product-modern Phase 1 MVP 제외** — Phase 1은 fa-fintech + consumer-warm 2종만, product-modern은 Phase 2 이후 회장 confirm 시 도입 (매핑 #3-4)
- [x] **HIGH #8 — font-size vs grid 8 모순 해소** — grid 8pt는 spacing/bbox에만 적용, font-size/line-height/border-radius 적용 외 (매핑 #1 부록)
- [x] **MEDIUM #9 — 5단 스토리 vs 슬라이드당 8단어 산술 정합성** — 캠페인 단위(5장 카드뉴스 시퀀스) 분배, 1슬라이드 1단 (`LR_STORY_5STEP_PER_CAMPAIGN`)
- [x] **MEDIUM #10 — BrandFooter 텍스트 침범 룰** — BrandFooter는 시각 요소 + 브랜드명 한정, body 콘텐츠 침범 X (`LR_BRANDFOOTER_VISUAL_ONLY`)
- [x] **MEDIUM #11 — "보수적 선택" 자동 룰 정의** — preset+dq-rules 충돌 시 자동으로 dq-rules 적용, 디자이너 주관 X (`LR_DECISION_AUTO_DQRULES`)

---

## 참조

- task-2432.md (Phase 0-B 정의)
- target-audience.md (페르소나 + 룰 A~E + 검증 체크리스트)
- /home/jay/workspace/memory/specs/dq-rules.json (32+ 항목 SSOT)
- /home/jay/workspace/memory/specs/design-qc-knowhow.md (DQ 1~10 + 디자인 실행 6항)
- /home/jay/workspace/memory/specs/knowhow-cardnews.md (5종 테마 + 슬라이드 타입 + 레이아웃 상수)
- /home/jay/workspace/memory/specs/knowhow-design.md (실패 33 + 성공 82 + Supanova)
- /home/jay/workspace/memory/specs/ad-quality-benchmark.md (모바일 + 캐러셀 + 스토리)
- /home/jay/workspace/resources/design-md/brex/DESIGN.md
- /home/jay/workspace/resources/design-md/supabase/DESIGN.md
- /home/jay/workspace/resources/design-md/apple/DESIGN.md
- /home/jay/workspace/skills/satori-cardnews/scripts/quality_evaluator.py (5축 함수 분리 결정)
- task-2428 회장 평가 (★ Codex 발견 — contrast 미측정 한계 직결)
