---
task_id: task-2432
type: design-spec
section: html-skeleton
created: 2026-05-03
status: draft
---

# IDS Phase 4 — Phase 0-C: Satori HTML/CSS Skeleton 설계

> 회장 핵심 원칙: **"토큰 정의로 끝내지 말고 실제 렌더링 구조까지 — 토큰만이 아니라 어디에 어떻게 적용되는가까지 설계"**
>
> task-2428 회장 평가: **"ΔE=0.00이어도 색 대비 안 맞음 = 아마추어 수준"** → 본 skeleton의 출발점. 토큰 정확도가 아니라 **렌더링 결과의 가독성/계층/안전영역**까지 설계 의무.
>
> 자매 산출물: `target-audience.md` (페르소나 기준선) / `mapping-tables.md` (5종 매핑) / `brainstorming.md` (alternatives)

본 문서는 IDS Phase 4 Phase 0의 핵심 산출물 4종 중 1종으로, **Satori 렌더링 컨텍스트에서 컴포넌트가 어디에 어떻게 배치되는가**를 설계한다. 코드(.jsx/.tsx)는 작성하지 않고 의사코드 + CSS 토큰 적용 위치 + 표 형태로 명세한다.

---

## 1. 개요 — Satori 렌더링 컨텍스트

### 1.1 Satori란

Satori는 React-style HTML(JSX 트리) + CSS(인라인 또는 CSS-in-JS 스타일 객체)를 입력으로 받아 SVG로 렌더링하는 라이브러리이다. Playwright headless와 달리 절대 사이즈 캔버스(`width: 1080`, `height: 1080` 등)를 명시하며, max-width / responsive 처리는 무관하다. 본 skeleton은 다음 3가지를 포함한다:

1. **React/JSX 형태의 컴포넌트 트리 (의사코드)** — 실제 컴파일 X, 구조 명세 목적
2. **CSS 토큰 적용 위치 명시** — `--font-headline-min` 같은 토큰이 어느 컴포넌트의 어느 prop에 들어가는가
3. **Aspect ratio 4종 변형** — 1:1 (1080×1080), 4:5 (1080×1350), 9:16 (1080×1920), 1.91:1 (1200×628)

### 1.2 본 skeleton이 다루지 않는 것

- 실제 .jsx/.tsx 파일 생성 (HARD GATE — Phase 0은 설계만)
- skills/satori-cardnews/** 코드 변경 (forbidden)
- 토큰 값 자체의 결정 (mapping-tables.md 담당)
- 페르소나 정의 (target-audience.md 담당)

### 1.3 페르소나 연결 원칙 (target-audience 룰 E 직결)

본 skeleton의 모든 수치/배치는 **target-audience.md의 페르소나 가치**에 근거한다. "Apple 스타일이라서" 같은 미학 근거는 사용하지 않는다. task-2428 회장 평가 재발 차단을 위해 contrast / safe-area / font-size 임계값은 **dq-rules.json 절대 하한** + **페르소나 가치 정당화** 이중 게이트를 통과해야 한다.

---

## 2. Container 구조

### 2.1 외부 wrapper (`<Canvas>`)

| 속성 | 값 / 토큰 | 설명 |
|---|---|---|
| 역할 | 캔버스 경계, 브랜드 background 색/이미지 적용 | Satori 입력 트리의 최상단 |
| width / height | 절대 px (aspect ratio별 분기) | Satori는 절대 사이즈 |
| background | `--canvas-bg` (preset별 결정) | `fa-fintech` = navy/dark, `consumer-warm` = white/cream, `product-modern` = neutral light |
| border-radius | 0 (Satori 출력 SVG는 캔버스 외곽에 라운딩 X) | 캔버스 라운딩은 후속 사용처(SNS 피드)에서 처리 |
| overflow | `hidden` | 텍스트/이미지가 캔버스 밖으로 절대 나가지 않도록 강제 |
| display | `flex`, `flex-direction: column` | Header / Content / Footer 수직 분할 |

**중요**: `max-width` / `width: 100%` 같은 responsive 속성은 Satori에서 무의미하다. 모든 사이즈는 절대 px.

### 2.2 Inner content area (`<SafeArea>`)

| 속성 | 값 / 토큰 | 설명 |
|---|---|---|
| 역할 | 컴포넌트 배치 영역, safe-area padding 적용 후의 가용 공간 | Header/Content/Footer 슬롯의 부모 |
| padding | `--safe-area-padding` (1080px 기준 72px) | 가장자리 텍스트 절단 방지 |
| inner width | 1080 - 2×72 = 936px (1:1 기준) | 모든 컴포넌트는 이 폭 이내 |
| display | `flex`, `flex-direction: column` | 수직 흐름 |
| justify-content | `space-between` (Cover) / `flex-start` + `margin-top:auto` (Body) | knowhow-design 노하우 #75 직결 |

### 2.3 Safe-area padding (★ 회장 질문 직접 답변)

회장 질문: **"5%? 8%?"** → **결정: canvas의 6.7% (1080px → 72px)**

| 근거 | 내용 |
|---|---|
| 8pt 그리드 정렬 | 72 = 8 × 9 (dq-rules `grid_unit: 8` 직결) |
| Instagram safe-area 가이드라인 | Meta 권장 7~8% 범위 내 — 우리 6.7%는 약간 보수적 |
| FA 태블릿 사용 맥락 | 태블릿 베젤/터치 영역 잘림 차단 (target-audience FA 1.2) |
| 소비자 모바일 카톡 썸네일 | 카톡 피드 자동 크롭 시 핵심 메시지 보호 (target-audience 룰 A 매칭 연결) |
| knowhow-cardnews 좌우 마진 64px과 | 64 < 72 — 본 skeleton은 더 보수적 (광고 배너용 안전 여유) |

**텍스트 절대 침범 금지 영역** = 캔버스 외곽 72px 띠. 단, `<BrandBar>` (캔버스 최상단 12px 띠)와 `<BrandFooter>` (옵션, 하단 100px 띠)는 safe-area 밖에 배치된 **시각 요소 전용** 영역으로, 텍스트 침범 금지 룰에서 예외.

---

## 3. 컴포넌트 배치 (위치 + z-index + alignment)

### 3.1 컴포넌트 배치 표

| 컴포넌트 | 위치 (CSS) | z-index | alignment | 공간 점유 비율 | 페르소나 연결 |
|---|---|---|---|---|---|
| `<BrandBar>` | top: 0, full-width, height: 12px | 100 | n/a | 0.9% (1080→12px) | knowhow-cardnews 룰 — FA가 카톡 공유 시 브랜드 즉시 인지 (target-audience FA 1.2 "전문가 이미지 구축") |
| `<Logo>` | safe-area 내 top: 24px, left: 24px | 90 | left top | 130×40px / 약 12% 폭 점유 | knowhow-cardnews 로고 영역 130px 표준 — FA 본인 신뢰도 = 콘텐츠 신뢰도 |
| `<Headline>` | safe-area 내 top center 또는 좌상단 (layout별) | 50 | left or center | 30~35% | dq-rules headline_min 84px = FA 태블릿 1m 거리 1초 가독 (target-audience FA 1.3) |
| `<Subhead>` | Headline 직하 24~32px (8pt 배수) | 50 | Headline과 동일 | 10~15% | dq-rules subhead_min 64px = 헤드 / 서브 1.3배 비율 보장 (knowhow #26) |
| `<Body>` | Subhead 직하 32~48px | 50 | left | 25~30% | knowhow-cardnews 3~5 항목 권장 — FA 정보 밀도 가치 (target-audience 룰 B variant) |
| `<Metric>` (선택) | Body 옆 또는 우측 | 50 | right | 15~20% | FA 페르소나 "정확한 숫자/팩트 우선" (target-audience FA 1.3) |
| `<Badge>` (선택) | top right corner inside safe-area | 60 | right top | 약 5% | 한정/혜택 강조 옵션 — 소비자 페르소나 사회적 압력 활용 (target-audience 소비자 2.1) |
| `<CTA>` | bottom center, safe-area bottom 96px 위쪽 | 70 | center | 8~12% | knowhow #27 width:auto + padding 32px + min-width 220px / 페르소나별 variant (target-audience 룰 B) |
| `<BrandFooter>` (4:5 한정) | bottom 0, full-width, height: 100px | 95 | center | 7.4% (1350→100px) | knowhow-cardnews 카드뉴스 표준 — 시퀀스 마지막 슬라이드까지 브랜드 인지 유지 (target-audience FA 1.2) |

#### 3.1.1 BrandFooter 영역 침범 금지 룰 (★ MEDIUM #10 보강)

`<BrandFooter>`는 **시각 요소 전용** 영역으로, 본문 콘텐츠 텍스트 침범을 절대 금지한다. 다음 룰을 강제한다:

| 항목 | 룰 |
|---|---|
| **BrandFooter 영역 정의** | 시각 요소 (브랜드명 텍스트 + 로고 변형 + 시각 띠) 한정 |
| **침범 금지 콘텐츠** | `<Headline>` / `<Subhead>` / `<Body>` / `<CTA>` / `<Metric>` / `<Badge>` 등 모든 본문 콘텐츠 텍스트 |
| **safe-area 룰 우선** | 본문 콘텐츠는 SafeArea 내부(외측 72px + bottom CTA 96px)에 배치, BrandFooter 100px 띠 침범 금지 |
| **BrandFooter 자체 brandName 텍스트** | safe-area 룰의 **예외** — 브랜드 영역 내 시각 요소이므로 허용 |
| **Lite Evaluator 검증 추가** | 본문 콘텐츠 텍스트 bbox.y_max ≤ canvas_height - BrandFooter_height (= 1350 - 100 = 1250) |
| **위반 시** | FAIL (task-2428 재발 차단 게이트와 동일 우선순위) |

**근거**: knowhow-cardnews BrandFooter는 시퀀스 마지막 슬라이드까지 브랜드 인지를 유지하는 시각 띠로 설계되었다. 본문 콘텐츠가 이 영역으로 흘러들어가면 브랜드 영역의 시각적 무결성이 깨지고, FA 카톡 공유 시 로고/브랜드명 영역에 텍스트가 겹쳐 가독성 저하가 발생한다 (target-audience FA 1.2 "전문가 이미지 구축" 직접 위배).

### 3.2 z-index 정책 (충돌 시 우선순위)

```
100 — BrandBar (최상단, 절대 침범 금지)
 95 — BrandFooter (4:5 한정, 본문 콘텐츠 침범 금지)
 90 — Logo
 70 — CTA (Badge보다 위, 클릭 우선 가정)
 60 — Badge
 50 — 본문 텍스트 (Headline/Subhead/Body/Metric)
 10 — gradient overlay (배경 위, 텍스트 아래)
  1 — 배경 이미지 / 그라데이션
```

### 3.3 Layout 변형 4종 (knowhow-design 성공 템플릿 기반)

#### Layout-A — 1080×1080 (1:1) "중앙 크림 패널"
- 근거: knowhow-design 템플릿 B (96점 검증)
- 구조: 전체 배경 + 반투명 dark/cream 오버레이 + 중앙 패널 (`width: 860px`, `padding: 56px 48px`, `border-radius: 12px`, `backdrop-filter: blur(4px)`)
- 페르소나 적용: FA preset = dark 패널 + 크림 텍스트 / 소비자 preset = 크림 패널 + dark 텍스트
- knowhow #14 직결: 정사각형 전체 오버레이 단독 사용 금지, 중앙 패널 구조 강제

#### Layout-B — 1200×628 (1.91:1) "좌측 텍스트 + 우측 배경"
- 근거: knowhow-design 템플릿 A (97점 검증)
- 구조: 좌측 60~62% 텍스트 영역 + 우측 38~40% 배경 노출, 좌→우 반투명 그라데이션
- 페르소나 적용: FA = navy 그라데이션 + 골드 액센트바 / 소비자 = 화이트 그라데이션 + 컬러 액센트바
- knowhow #67 직결: 하단 밴드 레이아웃은 DQ-06 구조적 한계 → 좌우 분할만 채택

#### Layout-C — 1080×1350 (4:5) "카드뉴스 표준"
- 근거: cardnews-v3-prd + knowhow-cardnews 1080×1350 표준
- 구조: BrandBar (top 12px) + 콘텐츠 영역 (top 12 ~ bottom 100) + BrandFooter (bottom 100px)
- 슬라이드 타입 5종 (knowhow-cardnews): cover / card_list / detail / body / cta
- 페르소나 적용: 5종 테마 (NavyGold / BlackRed / GreenWhite / PurplePink / OrangeCream) 중 페르소나 매칭 — FA = NavyGold 또는 GreenWhite, 소비자 = OrangeCream 또는 PurplePink

#### Layout-D — 1080×1920 (9:16) "스토리/모바일 풀스크린"
- 근거: 모바일 인스타 스토리 / 카카오 모먼트 풀스크린
- 구조: 상단 1/3 헤드라인 / 중앙 1/3 메트릭 또는 이미지 / 하단 1/3 CTA + 로고
- safe-area: 9:16에서 상단/하단 모바일 UI 침범 위험 → safe-area top/bottom 추가 12% (=230px)
- 페르소나 적용: 소비자 우선 (모바일 90%+ 사용 맥락, target-audience 2.1)

---

## 4. Grid 기준

### 4.1 컬럼 그리드 — **12-column 채택**

| 비교 항목 | 8-column | 12-column ★ 채택 |
|---|---|---|
| 1080px 캔버스 분할 | (132+12)×8 = 1152 | (76+12)×12 = 1056 + 외곽 24px |
| 메트릭/aside/badge 표현 | 어색함 (반쪽 칸) | 자연스러움 (4+4+4 / 3+3+3+3 등) |
| FA 정보 밀도 지원 (target-audience FA 1.2) | 약함 | 강함 (sub-column 분할 자유로움) |
| Bootstrap/Tailwind 호환 (개념적) | 일부 | 완전 일치 |

**결정**: 12-column × (col 76px + gutter 12px) ≈ 1056px + outer 24px (= safe-area 일부)

### 4.2 Gutter — 24px

- = 8pt × 3
- knowhow-cardnews 좌우 마진 64px과 일관성 유지 (64 = 8 × 8)
- FA 페르소나 "정렬 강박" 가치 직결 (target-audience FA 1.2)

### 4.3 Baseline grid — 8px

- dq-rules `grid_unit: 8` **직접 인용** — Single Source of Truth
- 모든 spacing(margin/padding/gap)은 `8 × n` (8 / 16 / 24 / 32 / 48 / 64 / 96)
- 4px sub-grid는 **icon 정렬 / line-height 미세 조정 한정**으로 옵션 허용

### 4.4 Line-height 룰

| 텍스트 종류 | line-height 비율 | 절대값 (84px 기준) | 근거 |
|---|---|---|---|
| Headline | 1.1 ~ 1.15 | 92~96px (8pt round-up = 96) | knowhow-design 템플릿 A 헤드라인 1.2 + dq-rules 절대값 round |
| Subhead | 1.2 ~ 1.25 | 76~80px | knowhow-cardnews 서브헤드 1.25 |
| Body | 1.4 ~ 1.6 | 56~64px | 한글 가독성 (소비자 모바일 가독) — knowhow-design Supanova `leading-snug` |
| CTA | 1.0 ~ 1.1 | 44~48px | 버튼 단일 라인 가정 |
| Metric (수치) | 1.0 | 96px = 그대로 | dq-rules core_metric_min 96px / display 임팩트 |

---

## 5. Safe Area 명세

### 5.1 Safe-area 영역 표

| 영역 | 수치 (1080×1080 기준) | 의미 | 근거 |
|---|---|---|---|
| **외측 safe-area** (4면) | **72px** (캔버스 6.7%) | 텍스트 절대 침범 금지 | Instagram 가이드라인 7~8% + dq-rules 8pt 그리드 (72=8×9) |
| **로고 안전영역 (top)** | 130px (캔버스 12%) | 로고 + 주변 minimum spacing | knowhow-cardnews CTA 상단 패딩 130px 표준 — 로고 침범 방지 |
| **BrandBar 영역** | top 12px | 브랜드 인지 띠 | knowhow-cardnews 1.6 — 누락 시 FA 카톡 공유 인지 저하 |
| **CTA bottom 안전영역** | 96px (8pt × 12) | CTA 잘림 차단 | 모바일 노출 우선 — knowhow #75 margin-top:auto 패턴 |
| **9:16 모바일 UI 추가 safe-area** | top 230px / bottom 230px (캔버스 12%) | 인스타 스토리 상단 프로필/하단 CTA UI 가림 차단 | Meta 스토리 가이드 (사실 기반) |

### 5.2 텍스트 절대 침범 금지 — Lite Evaluator 검증 항목

Phase 0.5 Lite Evaluator는 **모든 텍스트 요소의 bbox**가 다음 범위 안에 있는지 검증:

```
texts.bbox.x_min >= safe_area_left (= 72)
texts.bbox.y_min >= safe_area_top (= 72)
texts.bbox.x_max <= canvas_width  - safe_area_right (= 1080 - 72 = 1008)
texts.bbox.y_max <= canvas_height - safe_area_bottom (= 1080 - 72 = 1008)
```

위반 시 즉시 FAIL — task-2428 "아마추어 수준" 재발 차단의 1차 게이트.

### 5.3 Aspect ratio별 safe-area 변환표

| Aspect ratio | canvas | safe-area 외측 | inner 가용 영역 |
|---|---|---|---|
| 1:1 | 1080×1080 | 72px | 936×936 |
| 4:5 | 1080×1350 | 72px (좌우) / 84px (상하, 6.2%) | 936×1182 |
| 9:16 | 1080×1920 | 72px (좌우) / 230px (상하 모바일 UI) | 936×1460 |
| 1.91:1 | 1200×628 | 80px (좌우, 6.7%) / 48px (상하, 7.6%) | 1040×532 |

---

## 5.4 Preset 결정 룰 (Brief → Preset 매핑) ★ CRITICAL #2 보강

> **문제 의식**: §7 Props에 `themePreset` prop은 정의되어 있으나, "누가 brief를 보고 어떤 preset을 결정하는가?"의 결정 권한자 룰이 부재했다. 카드뉴스 5장 시퀀스에서 슬라이드 1번이 FA 톤, 슬라이드 5번이 소비자 톤인 혼합 시나리오 처리 룰도 없었다. Phase 0.5 코드 작성 시 책임 충돌 차단을 위해 본 섹션에서 결정 룰을 명시한다.

### 5.4.1 Brief 입력 필수 필드

모든 brief는 다음 필드를 **강제 입력**해야 한다 (누락 시 Satori 코드 진입 전 fail):

| 필드 | 타입 / 허용값 | 설명 |
|---|---|---|
| `targetPersona` | `"insurance-fa"` \| `"consumer"` \| `"hybrid"` 중 하나 (필수) | 본 brief의 콘텐츠가 누구를 대상으로 하는가 — target-audience.md 페르소나 직결 |

### 5.4.2 자동 매핑 룰 (targetPersona → themePreset)

| `targetPersona` 입력값 | 매핑되는 `themePreset` | 비고 |
|---|---|---|
| `"insurance-fa"` | `"theme-fa-fintech"` | FA 페르소나 (target-audience FA 1.x 직결) |
| `"consumer"` | `"theme-consumer-warm"` | 일반 소비자 페르소나 (target-audience 2.x 직결) |
| `"hybrid"` | **카드뉴스 시퀀스 내 슬라이드별 preset 분리** | 시퀀스(5장) 한정 — 단일 이미지(1장)에는 hybrid 사용 금지 |

### 5.4.3 시퀀스 내 preset 혼용 룰

| 콘텐츠 유형 | preset 적용 룰 |
|---|---|
| **단일 이미지 (1장)** | 단일 preset만 적용. `targetPersona: "hybrid"` 사용 시 brief 단계에서 reject |
| **카드뉴스 시퀀스 (5장)** | 슬라이드별 preset prop 허용 (예: 슬라이드 1~3 `theme-fa-fintech`, 슬라이드 4~5 `theme-consumer-warm`) |
| **base layer 토큰 (preset 무관)** | `font_min` / `contrast_min` / `grid_unit` (8pt) 등은 preset 무관하게 **시퀀스 전체 일관 적용** — 슬라이드별 분리 금지 |

**근거**: dq-rules.json의 절대 하한(font_sizes / color.aaa_contrast / layout.grid_unit)은 페르소나/preset과 무관하게 모든 콘텐츠에 적용되는 base layer다. preset은 색상/톤/액센트 같은 표면 layer에만 분기 허용.

### 5.4.4 Decision Authority (결정 권한자)

| 역할 | 책임 |
|---|---|
| **Brief 작성자 (카피라이터 / 팀장)** | brief의 `targetPersona` 필드 명시 입력 책임. 시퀀스 hybrid의 경우 슬라이드별 preset 매핑도 brief에 명시 |
| **디자이너** | brief의 `targetPersona` / preset을 그대로 따름. 임의 변경 금지 |
| **Satori 코드 (skills/satori-cardnews/**)** | brief 없이 default preset 자동 결정 **금지** — brief 누락 시 배포 시 명시 fail (silent default 금지) |

### 5.4.5 Default 미결 시 Fallback

| 시나리오 | Fallback |
|---|---|
| brief에 `targetPersona` 누락 | **`theme-fa-fintech`** (보험 도메인 base) — 단, fail-loud 경고 동시 발생 (fail 우선, fallback은 임시 안전망) |

**근거**: 본 프로젝트는 보험 도메인 IDS이므로 도메인 base는 FA 페르소나. 단, fallback은 운영 안전망일 뿐 **모든 brief는 명시 필수**가 원칙이다.

---

## 6. Component Tree (의사코드)

> ⚠️ 본 코드는 의사코드. 실제 .jsx/.tsx 파일 생성 금지 (HARD GATE).

```jsx
<Canvas
  ratio={LayoutVariant}      // "1:1" | "4:5" | "9:16" | "1.91:1"
  themePreset={ThemePreset}  // "fa-fintech" | "consumer-warm" | "product-modern"
  density={Density}          // "compact" | "comfortable" | "spacious"
  layout={LayoutId}          // "A-square-panel" | "B-split-60-40" | "C-cardnews" | "D-story"
>
  {/* z-index 100 — 캔버스 최상단 시각 띠, safe-area 밖 */}
  <BrandBar height={12} color={tokens.palette.accent} />

  {/* safe-area = 외곽 72px 텍스트 침범 금지 */}
  <SafeArea padding={tokens.safeAreaPadding}>

    <Header>
      <Logo src={tokens.brand.logoUrl} width={130} height={40} />
      {Badge && <Badge variant="limited" position="top-right" />}
    </Header>

    <Content layout={ContentLayout}>
      {/*
        ContentLayout = "stack" | "split-60-40" | "centered-panel" | "story-3-zone"
        layout prop이 페르소나 preset에 종속되어 자동 결정
        — target-audience 룰 B "Variant prop은 theme preset에 종속"
      */}

      <Headline
        text={headline}                         // ≤ 16자 권장 (FA 1초 가독)
        fontSize={tokens.font.headline.size}    // ≥ 84px (dq-rules)
        weight={tokens.font.headline.weight}    // ≥ 700 (FA 권위), ≥ 800 권장
        lineHeight={1.1}
        letterSpacing="-0.02em"                 // knowhow #23 자간 축소 컨테이너 수용
        whiteSpace="nowrap"                     // knowhow #27 줄바꿈 방지
        emphasis={{ keyword: "지점장",          // knowhow-design #15 골드 강조 1개
                    color: tokens.palette.accent }}
      />

      <Subhead
        text={subhead}
        fontSize={tokens.font.subhead.size}     // ≥ 64px
        weight={tokens.font.subhead.weight}     // 600~700
        lineHeight={1.2}
        breakStrategy="manual-br"               // knowhow #27 자동 줄바꿈 금지
      />

      <Body items={items} layout="3-5-list" />  {/* knowhow-cardnews 3~5 항목 */}

      {metric && (
        <Metric
          value={metric.value}
          label={metric.label}
          fontSize={tokens.font.coreMetric.size} // ≥ 96px
          weight={900}                           // dq-rules core_metric Black
          variant={persona === "fa" ? "dense" : "simple"}
          // target-audience 룰 B 컴포넌트 variant
        />
      )}
    </Content>

    <Footer>
      <CTA
        text={ctaText}
        variant={ctaVariant}                    // "command" (FA) | "explore" (consumer)
        contrast={"AAA-7:1"}                    // ★ task-2428 재발 차단 명시
        contrastValidate={true}                 // 렌더 전 contrast 측정, 미달 시 FAIL
        width="auto"                            // knowhow #27
        padding="0 32px"
        minWidth={220}
        height={68}                             // knowhow #6 1080 CTA 76+ 권장
        fontSize={tokens.font.cta.size}         // ≥ 40px
        weight={700}
        background={tokens.cta.bg}              // 페르소나별 토큰 분기
        color={tokens.cta.fg}
      />
    </Footer>

  </SafeArea>

  {/* 옵션: 카드뉴스 4:5 한정 — knowhow-cardnews BrandFooter */}
  {ratio === "4:5" && <BrandFooter height={100} brandName={tokens.brand.name} />}
</Canvas>
```

---

## 7. Props 인터페이스 (TypeScript-style 의사 정의)

> ⚠️ TypeScript 코드 생성 X — 표 형태 명세만.

| 컴포넌트 | prop | 타입 / 허용값 | 설명 / 페르소나 연결 |
|---|---|---|---|
| `Canvas` | `ratio` | `"1:1" \| "4:5" \| "9:16" \| "1.91:1"` | aspect ratio (Layout-A/B/C/D 결정) |
| `Canvas` | `themePreset` | `"fa-fintech" \| "consumer-warm" \| "product-modern"` | preset 이름 — target-audience 룰 A 직결 |
| `Canvas` | `density` | `"compact" \| "comfortable" \| "spacious"` | 정보 밀도 — target-audience 룰 C |
| `Canvas` | `layout` | `"A-square-panel" \| "B-split-60-40" \| "C-cardnews" \| "D-story"` | 레이아웃 ID |
| `BrandBar` | `height` | `number (px, default 12)` | knowhow-cardnews 12px 표준 |
| `BrandBar` | `color` | `CSSColor` | 토큰 `tokens.palette.accent` 권장 |
| `Logo` | `src`, `width`, `height` | `string`, `number (default 130)`, `number (default 40)` | knowhow-cardnews 130px 표준 |
| `Headline` | `text` | `string (≤ 16자 권장, ≤ 24자 hard limit)` | FA 1초 가독 (target-audience FA 1.3) |
| `Headline` | `fontSize` | `number (≥ 84)` | dq-rules headline_min 직결 |
| `Headline` | `weight` | `number (700 \| 800 \| 900)` | dq-rules font_weights.headline 800~900, 700 최소 |
| `Headline` | `emphasis` | `{ keyword: string, color: CSSColor } \| null` | knowhow #15 골드 강조 1개 |
| `Subhead` | `fontSize` | `number (≥ 64)` | dq-rules subhead_min |
| `Subhead` | `breakStrategy` | `"manual-br" \| "nowrap"` | knowhow #27 자동 줄바꿈 금지 |
| `Body` | `items` | `Array<{ title: string, description?: string }>` | knowhow-cardnews 3~5개 |
| `Body` | `layout` | `"3-5-list" \| "single-paragraph"` | knowhow-cardnews body 슬라이드 패턴 |
| `Metric` | `value`, `label` | `string`, `string` | FA 정보 밀도 |
| `Metric` | `variant` | `"dense" \| "simple"` | 페르소나 분기 (target-audience 룰 B) |
| `CTA` | `text` | `string (≤ 12자 권장)` | 버튼 단일 라인 |
| `CTA` | `variant` | `"command" \| "explore"` | FA 명령형 / 소비자 탐색형 (target-audience 1.4 / 2.4) |
| `CTA` | `contrast` | `"AA" \| "AAA"` | WCAG 등급, 본 skeleton 강제값 = `"AAA"` |
| `CTA` | `contrastValidate` | `boolean (default true)` | 렌더 전 측정, 미달 시 FAIL — task-2428 재발 차단 |

### 7.1 Props 직렬화 포맷 (Phase 0.5 진입 전제) ★ HIGH #6 보강

> **문제 의식**: §7은 props를 표 형태로만 명세하고 TypeScript 코드 생성을 금지(HARD GATE)했으나, Phase 0.5 Lite Evaluator가 컴포넌트 bbox / font-size / fill을 추출해 검증하려면 props 직렬화 포맷이 결정되어 있어야 한다. 미결 시 Phase 0.5 진입 시점에 추가 회장 confirm이 필요해 작업 흐름이 끊긴다.

#### 7.1.1 결정: JSON Schema (Draft 2020-12) 채택

| 후보 | 채택 여부 | 사유 |
|---|---|---|
| **JSON Schema (Draft 2020-12)** | ★ **채택** | 런타임 validate 가능, 언어 무관 (Python/Node 모두), Lite Evaluator가 직접 props 검증 + bbox 측정 모두 활용 |
| TypeScript interface | 미채택 | 컴파일 후 타입 정보 소실 → Lite Evaluator의 런타임 bbox 검증 시 추가 도구 필요 (런타임 schema 별도 작성 = 이중 관리) |
| Zod / io-ts | 미채택 | TS 종속 — 본 프로젝트 Lite Evaluator가 Python 측정 로직 포함 가능성 있음. 언어 중립 포맷 우선 |

#### 7.1.2 채택 근거 (3가지)

1. **런타임 validate**: JSON Schema는 ajv (Node) / jsonschema (Python) 등으로 런타임 validation 가능. TypeScript는 컴파일 후 검증 불가.
2. **언어 무관**: Phase 0.5 Lite Evaluator가 Python(Pillow + bbox 측정) / Node(Satori 호출) 어느 쪽에서든 동일 schema 참조 가능.
3. **Phase 0.5 우위**: Lite Evaluator는 (a) brief props validation + (b) Satori 출력 bbox 측정 두 단계가 모두 필요. JSON Schema는 (a)에 직접 사용, (b)에서는 props 구조 reference로 사용 — 단일 SSoT.

#### 7.1.3 Schema 위치 (Phase 0.5 작성 대상)

| 항목 | 경로 / 책임 |
|---|---|
| **schema 파일 경로** | `skills/satori-cardnews/schemas/canvas-props.schema.json` |
| **Phase 0-C 책임 종료점** | 본 문서는 **props 표 명세 + 직렬화 포맷 결정**까지만 |
| **JSON Schema 실제 작성** | **Phase 0.5 dev-team의 첫 task** (본 문서는 코드 생성 HARD GATE 적용 중) |

#### 7.1.4 Phase 0.5 진입 게이트 추가 항목

기존 Phase 0.5 진입 게이트(Lite Evaluator 5종 측정 항목)에 다음 1개 항목을 **추가**:

- ☐ **JSON Schema (`skills/satori-cardnews/schemas/canvas-props.schema.json`) 작성 = Phase 0.5 첫 task** (Lite Evaluator 측정 코드 작성보다 우선)

---

## 8. Reusable 컴포넌트 vs Variant vs 하드코딩 분리

### 8.1 Reusable (preset 무관 고정 형태)

| 컴포넌트 | 고정 사양 |
|---|---|
| `BrandBar` | 항상 top 0, height 12px, full-width |
| `Logo` | 항상 130×40, top-left in safe-area |
| `SafeArea` | 항상 외측 72px (1080 기준), aspect ratio 비율 변환 |
| `Headline` | 항상 ≥ 84px, weight ≥ 700, line-height 1.1 |
| `Subhead` | 항상 ≥ 64px, weight 600~700 |
| `Body` | 항상 ≥ 40px (dq-rules absolute_min), 3~5 items |
| `CTA` | 항상 height ≥ 68px, contrast AAA, width auto + min 220px |
| `Footer` | 항상 bottom safe-area 96px 위 |

### 8.2 Variant (preset / density / persona에 따라 분기)

| 컴포넌트 | 분기 prop | 변화 항목 |
|---|---|---|
| `Content` | `layout` | `stack` (1:1 panel) / `split-60-40` (1.91:1) / `centered-panel` / `story-3-zone` (9:16) |
| `Metric` | `variant` | `dense` (FA, 단위/맥락 표기) / `simple` (소비자, 큰 숫자만) |
| `Badge` | `variant` | `limited` / `discount` / `new` (옵션, 페르소나 무관) |
| `CTA` | `variant` | `command` (FA: "지금 상담 신청") / `explore` (소비자: "내게 맞는지 확인") |
| Background | `themePreset` | navy/dark grad (fa) / cream/white (consumer) / neutral light (product) |
| Card panel opacity | `density` × `themePreset` | knowhow #14 정사각형 중앙 패널 0.80~0.85 / 글래스 카드 0.30~0.50 (knowhow #21) |

### 8.3 하드코딩 금지 (dq-rules.json 변경으로만 조정)

| 항목 | 절대 하한 | 근거 |
|---|---|---|
| `font.absolute_min` | 40px | dq-rules `font_sizes.absolute_min` |
| `font.headline.min` | 84px | dq-rules `font_sizes.headline.min` |
| `font.subhead.min` | 64px | dq-rules `font_sizes.subhead.min` |
| `font.weight.banned` | 100, 200, 300 | dq-rules `font_weights.banned_weights` |
| `grid.unit` | 8 | dq-rules `layout.grid_unit` |
| `color.cta_min_contrast` | 3.0 (AA) | dq-rules `color.cta_min_contrast_ratio` |
| `color.aaa_contrast` | 7.0 (AAA — 본 skeleton 강제 7:1) | dq-rules `color.aaa_contrast_ratio` 4.5 + 회장 명시 7:1 상향 |
| `text.max_elements_per_slide` | 3 | dq-rules `text_density.max_elements_per_slide` |
| `color.max_brand_colors` | 3 | dq-rules `color.max_brand_colors` |
| `color.max_accent_colors` | 1 | dq-rules `color.max_accent_colors` |

이 항목들은 **Headline 컴포넌트의 fontSize prop 기본값**, **CTA 컴포넌트의 contrastValidate 강제** 같은 방식으로 토큰에서 직접 참조한다. 컴포넌트 코드에 `84` 같은 매직 넘버 하드코딩 절대 금지.

---

## 9. 검증 시나리오 (회장 confirm 항목)

본 skeleton의 핵심 결정 사항은 회장 승인이 필요하다:

- [ ] **Safe-area 6.7% (= 72px) 결정 승인**
- [ ] **12-column 그리드 + 24px gutter 승인** (vs 8-column 채택 안 함)
- [ ] **8px baseline grid 승인** (dq-rules grid_unit 직결)
- [ ] **Aspect ratio 4종 승인** (1:1 / 4:5 / 9:16 / 1.91:1) — Layout A/B/C/D
- [ ] **Theme preset 3종 이름 승인** (`fa-fintech` / `consumer-warm` / `product-modern`)
- [ ] **Density 토큰 승인** (compact / comfortable / spacious)
- [ ] **CTA variant 승인** (`command` / `explore`)
- [ ] **task-2428 재발 차단 메커니즘** — CTA `contrast="AAA-7:1"` + `contrastValidate=true` 강제 명시
- [ ] **Brief `targetPersona` 필드 강제 입력 룰 승인** (§5.4 — `"insurance-fa"` / `"consumer"` / `"hybrid"` 3종 enum)
- [ ] **Preset 자동 매핑 룰 승인** (§5.4.2 — fa→fa-fintech / consumer→consumer-warm / hybrid→슬라이드별 분리)
- [ ] **시퀀스 내 preset 혼용 시 base layer 토큰 일관 적용 룰 승인** (§5.4.3 — font_min/contrast_min/grid는 preset 무관)
- [ ] **Default fallback = `theme-fa-fintech` 승인** (§5.4.5 — 보험 도메인 base, fail-loud 동시)
- [ ] **JSON Schema (Draft 2020-12) 직렬화 포맷 채택 승인** (§7.1 — TypeScript 미채택 사유 포함)
- [ ] **JSON Schema 작성 = Phase 0.5 첫 task 게이트 추가 승인** (§7.1.4)
- [ ] **BrandFooter 영역 본문 콘텐츠 텍스트 침범 금지 룰 승인** (§3.1.1 — Lite Evaluator 검증 항목 추가)

---

## 10. 다음 Phase 0.5 (Lite Evaluator) 연계

본 skeleton은 Phase 0.5 Lite Evaluator의 측정 위치/기준을 그대로 정의한다.

| Lite Evaluator 항목 | 측정 위치 / 기준 (본 skeleton 직결) |
|---|---|
| **L1 Contrast** | `<CTA>` 배경 vs 텍스트 / `<Headline>` 색상 vs 배경 — 7:1 (AAA) 또는 대형 4.5:1 |
| **L2 Safe-area 침범** | 외측 72px (1080 기준) / BrandBar top 12 / Logo top 130 / CTA bottom 96 / 모든 텍스트 bbox ⊂ inner 가용영역 |
| **L3 Font-size** | Headline ≥ 84 / Subhead ≥ 64 / Body ≥ 40 / Core Metric ≥ 96 (dq-rules 직결) |
| **L4 Grid 정렬** | 모든 컴포넌트 bbox의 top/left/width/height % 8 == 0 (8pt baseline) |
| **L5 Color palette** | themePreset 토큰 외 색상 카운트 ≤ 1 (accent 1개), 브랜드 컬러 ≤ 3 (dq-rules `max_brand_colors`) |

**측정 자동화 가능성**: 본 skeleton의 컴포넌트 구조가 명확하므로, Satori 출력 SVG의 element 단위 bbox / fill / font-size / font-weight를 추출하여 L1~L5 자동 검증 가능. 코드 작성은 Phase 0.5 별도 task.

### 10.1 Phase 0.5 진입 시 첫 task — JSON Schema 작성

§7.1 결정에 따라 Phase 0.5의 **첫 task**는 다음과 같이 명시한다:

| 우선순위 | task | 산출물 경로 |
|---|---|---|
| **#1 (첫 task)** | Canvas / 컴포넌트 props JSON Schema (Draft 2020-12) 작성 | `skills/satori-cardnews/schemas/canvas-props.schema.json` |
| #2 | Lite Evaluator L1 (Contrast) 측정 코드 작성 | (Phase 0.5 dev-team 결정) |
| #3 | Lite Evaluator L2 (Safe-area + BrandFooter 침범) 측정 코드 작성 | 동상 |
| #4 | Lite Evaluator L3~L5 측정 코드 작성 | 동상 |

**근거**: Lite Evaluator는 props validation(JSON Schema 필요)과 bbox 측정(schema 참조 필요)을 동시에 수행한다. schema가 먼저 있어야 Evaluator 측정 코드가 어떤 props 구조를 검증할지 결정 가능.

### 10.2 BrandFooter 침범 검증 항목 추가 (Lite Evaluator L2 보강)

§3.1.1 룰에 따라 Lite Evaluator L2 (Safe-area 침범) 측정 항목에 다음 검증을 추가한다:

```
# 4:5 카드뉴스 한정
if ratio == "4:5" and BrandFooter present:
    for text in [Headline, Subhead, Body, CTA, Metric, Badge]:
        assert text.bbox.y_max <= canvas_height - BrandFooter.height
        # 즉, 1350 - 100 = 1250
```

위반 시 FAIL — task-2428 재발 차단 게이트와 동일 우선순위.

---

## 11. 참조

- 회장 명시 (2026-05-03): "Phase 0에 HTML skeleton + Target Audience 추가 — 토큰만이 아니라 어디에 어떻게 적용되는가까지 설계"
- 회장 평가 (2026-05-03, task-2428): "ΔE=0.00이어도 색 대비 안 맞음 = 아마추어 수준" → 본 skeleton CTA contrast AAA 강제 + contrastValidate prop의 출발점
- `memory/specs/dq-rules.json` — font_sizes / font_weights / layout.grid_unit / color.aaa_contrast_ratio (절대 하한)
- `memory/specs/knowhow-cardnews.md` — BrandBar 12px / Logo 130px / 좌우 마진 64px / 슬라이드 5종 / 테마 5종
- `memory/specs/knowhow-design.md` — 템플릿 A/B (97점/96점), 노하우 #14 (정사각 중앙 패널), #15 (골드 강조), #23 (자간 축소), #27 (CTA width:auto), #67 (좌우 분할)
- `memory/specs/cardnews-v3-prd.md` — 1080×1350 카드뉴스 표준
- 자매 산출물:
  - `target-audience.md` (Phase 0-D) — 페르소나 + 매핑 연결 룰 A~E
  - `mapping-tables.md` (Phase 0-B) — 5종 매핑 (dq-rules / 노하우 / brand preset / regression / Lite→Full evaluator)
  - `brainstorming.md` (Phase 0-A) — Visual Companion wireframes + alternatives
