---
name: satori-cardnews
description: "Satori 기반 카드뉴스/배너/인포그래픽 초고속 생성. HTML/CSS 템플릿으로 한글 100% 정확. Use when: '카드뉴스', '배너', '인포그래픽', '대량 이미지', 'A/B 테스트 이미지'."
---

# Satori 카드뉴스 생성 스킬

Vercel Satori (HTML/CSS→SVG) + resvg-js (SVG→PNG)로 카드뉴스/배너를 초고속 생성합니다.

## 스펙
- **엔진**: Satori + resvg-js (Node.js ESM)
- **속도**: 0.32초/장 (AI 대비 25~75배 빠름!)
- **비용**: $0 (완전 무료, 로컬 실행)
- **한글**: 100% 정확 (HTML 렌더링)
- **해상도**: 자유 설정 (기본 1080x1350)
- **파일 크기**: ~229KB (매우 작음)

## 적합한 용도
- SNS 카드뉴스 (Threads, Instagram)
- 배너/헤더 이미지
- 인포그래픽 (데이터 시각화)
- A/B 테스트용 카피/레이아웃 변형 대량 생산
- 정기 콘텐츠 자동 생성
- 텍스트 중심 광고 배너 (체크리스트 통과 시: 포토리얼 불필요 + 텍스트 70%+ + 로고/CI 불필요)
- 대량 (10장+) 이미지 일괄 생산 (SLA 보호)
- 긴급 콘텐츠 (납기 1시간 이내, 팀장 승인)

## 부적합한 용도
- 포토리얼 배경 필요 콘텐츠 (인물, 제품, 풍경 실사) → hybrid-image 또는 gemini-image 사용
- 복잡한 일러스트/아트워크 → canvas-design 사용
- CTA 포함 광고 (가격, 기한, 할인율 등 전환 유도 문구) → hybrid-image 사용 (정확도 필수)

## 코드 위치
`/home/jay/workspace/tools/ai-image-gen/satori-test/`

## 템플릿 시스템
```
tools/carousel-gen/
  templates/
    infographic.html    # 데이터/수치 중심
    emotional.html      # 감성/CTA 중심
    registry.json       # 템플릿 메타데이터
```

### 템플릿 구조 (JSX-like HTML)
```jsx
// Satori는 CSS Flexbox 레이아웃 엔진 사용
<div style={{
  display: 'flex',
  flexDirection: 'column',
  width: 1080,
  height: 1350,
  background: 'linear-gradient(135deg, #1a365d, #0a1628)',
  fontFamily: 'Noto Sans KR',
  padding: 60
}}>
  <h1 style={{ fontSize: 64, color: '#d4a853' }}>제목</h1>
  <p style={{ fontSize: 36, color: '#ffffff' }}>본문</p>
</div>
```

## 실행 방법
```bash
cd /home/jay/workspace/tools/ai-image-gen/satori-test
node generate.js
```

## 폰트
- Noto Sans KR: SIL Open Font License (상업적 이용 가능)
- Pretendard: SIL Open Font License
- 로컬 캐시: `/home/jay/workspace/tools/ai-image-gen/satori-test/fonts/`

## Double-Bezel 카드 패턴 (Supanova)
프리미엄 카드뉴스를 위한 "가공된 하드웨어" 질감의 이중 베젤 구조:

**다크 배경 기준 (Flexbox/Satori 호환 CSS):**
```css
/* 외부 쉘 */
.outer-shell {
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.10);
  padding: 6px;
  border-radius: 32px;
}
/* 내부 코어 */
.inner-core {
  box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.15);
  border-radius: 26px; /* calc(32px - 6px) */
}
```

"유리판이 알루미늄 트레이 안에 있는" 느낌을 구현. 카드뉴스/인포그래픽에서 프리미엄감 상승.

## Eyebrow Tags 패턴 (Supanova)
헤딩 전 마이크로 뱃지로 카테고리/레이블 표시:
```css
.eyebrow-tag {
  display: inline-flex;
  border-radius: 9999px; /* rounded-full */
  padding: 4px 12px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.15em;
  font-weight: 600;
}
```
카드뉴스 상단에 "STEP 01", "핵심 포인트", "보험 TIP" 등의 마이크로 라벨을 표시할 때 사용.

## Grain Texture 오버레이 (Supanova)
디지털 느낌을 탈피하기 위한 노이즈 텍스처:
```css
.grain-overlay {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 60;
  opacity: 0.03;
  background-image: url("data:image/svg+xml,..."); /* noise SVG */
}
```
카드뉴스 배경에 미세 노이즈를 추가하면 "인쇄물" 느낌의 프리미엄감 확보.

## 품질 체크리스트
- [ ] 해상도 >= 1080x1080
- [ ] 한글 텍스트 렌더링 정상
- [ ] 브랜드 컬러 준수 (design-tokens.json)
- [ ] 레이아웃 오버플로우 없음
- [ ] 이미지 파일 정상 생성 (크기 > 50KB)

## 캐러셀/스토리텔링 비주얼 일관성 규칙
- 슬라이드/캐러셀형 콘텐츠 제작 시, 전체 슬라이드에서 **동일한 비주얼 컨셉**을 유지할 것
  - 1번 슬라이드가 실사형이면 전체 실사형, 일러스트면 전체 일러스트
  - 스타일 혼재 절대 금지 (실사+만화+아이콘 섞기 X)
- **통일된 컬러 팔레트** 사용
  - 슬라이드 1에서 정한 주요 색상(Primary/Secondary/Accent)을 전체에 일관 적용
  - 감정 변화(어두운→밝은)는 같은 색상 계열 내 명도/채도로 표현
- 폰트 스타일도 통일 (같은 서체, 같은 가중치 규칙)

## 모바일 퍼스트 타이포그래피 규칙
- **타겟 디바이스: 모바일 (사용자 80%가 모바일)**
- 광고/카드뉴스의 텍스트는 모바일 화면에서 **즉시 읽을 수 있는 크기**여야 함
- 최소 폰트 크기 가이드 (1080px 기준):
  - 헤드라인: 60px 이상
  - 서브카피/본문: 36px 이상
  - 부가 정보(출처 등): 24px 이상
- 한글은 영문보다 복잡한 자형이므로 **영문 기준보다 10~20% 더 크게**
- 광고 카피가 작아서 안 읽히면 광고 효과 0 — **가독성이 디자인보다 우선**
- Gemini 이미지 생성 시 프롬프트에 "large bold Korean text, easily readable on mobile" 포함

## 참고
- 테스트 보고서: `/home/jay/workspace/memory/reports/task-865.1.md`
- 산출물: `/home/jay/workspace/tools/ai-image-gen/output/v7-satori/`

## 12 매거진 템플릿 (Phase 1)

IDS Phase 1로 추가된 매거진형 템플릿 12종. 각 템플릿은 `templates/{id}.html` (Satori 호환 inline-style HTML), `templates/{id}.css` (참조용 외부 CSS), `templates/{id}.json` (variables schema)의 3-파일 구성. 모든 HTML은 Flexbox 전용·한글 fallback (Pretendard, 'Noto Sans KR') 명시.

| ID | 설명 | 핵심 placeholder |
|----|------|------------------|
| `magazine` | 큰 타이포 + 여백 (잡지형) | title / subtitle / body / caption / cta |
| `grid-4` | 4-cell 격자 (2x2) | title / cell{1..4}_label / cell{1..4}_value |
| `grid-6` | 6-cell 격자 (2x3) | title / cell{1..6}_label / cell{1..6}_value |
| `asymmetric` | 좌측 큰 영역(2/3) + 우측 보조(1/3) | title / body / side_label / side_value |
| `typography-focus` | 텍스트 위주, 미니멀 | title / quote / author |
| `infographic` | 차트/숫자 강조 (큰 % 또는 수치) | title / stat_value / stat_unit / stat_label / detail{1..3} |
| `storytelling` | 시퀀스/단계형 (1→2→3) | title / step{1..3}_title / step{1..3}_body |
| `minimal` | whitespace 극대 (apple/raycast 스타일) | title / subtitle / caption |
| `monochrome` | 단색 (uber/cal 스타일) | title / subtitle / caption / cta |
| `colorful` | 컬러풀 (ferrari/lamborghini 스타일, 그라데이션) | title / subtitle / body / cta |
| `corporate` | 정렬된 (linear/stripe 스타일) | title / point{1..3} / cta |
| `cover` | 잡지 표지형 (큰 헤드라인 + 작은 카피) | title / subtitle / volume / issue |

### 템플릿 사용법
```python
from pathlib import Path

template_id = "magazine"
tpl_root = Path("/home/jay/workspace/skills/satori-cardnews/templates")
html = (tpl_root / f"{template_id}.html").read_text(encoding="utf-8")

# placeholder 치환 (간단한 dict.replace 또는 jinja2 호환)
for key, value in variables.items():
    html = html.replace(f"{{{{{key}}}}}", str(value))
```

각 JSON schema의 `required_vars`/`optional_vars` 검사 후 누락 시 fallback 토큰 사용 권장.

## design-md 자동 매칭

`design_md_loader.py`가 `resources/design-md/{brand}/DESIGN.md` 파싱 → satori 변수 dict로 변환. 외부 API/LLM 호출 없이 정규식·키워드 매칭만 사용.

```python
from sys import path
path.insert(0, "/home/jay/workspace/skills/satori-cardnews")
from design_md_loader import load_design_md, list_brands, fallback_tokens

# 1) 사용 가능한 brand 디렉토리 조회 (60종)
brands = list_brands()  # ['airbnb', 'apple', 'bmw', ..., 'supabase', ...]

# 2) brand → 디자인 토큰 dict
tokens = load_design_md("supabase")
# {
#   'primary': '#0f0f0f', 'secondary': '#171717', 'accent': '#0f0f0f',
#   'background': '#0f0f0f', 'text_primary': '#4d4d4d',
#   'font_display': 'Circular', 'font_body': 'Source Code Pro',
#   'spacing': {'sm': 8, 'md': 16, 'lg': 32, 'xl': 64},
#   'border_radius': '6px', 'shadow': []
# }

# 3) 잘못된 입력 시 fallback
tokens = fallback_tokens()  # 안전한 기본 토큰 dict
```

`60 design-md × 12 satori 템플릿 = 720+ 조합` 자동 생성 가능.

## 5 사이즈 자동

`sizes.py`로 SNS 플랫폼별 표준 사이즈 자동 매핑.

```python
from sys import path
path.insert(0, "/home/jay/workspace/skills/satori-cardnews")
from sizes import get_size, list_platforms, SIZES

# 1) 지원 플랫폼 목록
list_platforms()  # ['instagram', 'facebook', 'twitter', 'threads', 'naver']

# 2) 플랫폼명 → (width, height)
get_size("instagram")  # (1080, 1080)
get_size("facebook")   # (1200, 630)
get_size("twitter")    # (1200, 675)
get_size("threads")    # (1080, 1350)
get_size("naver")      # (800, 800)
get_size("unknown")    # (1080, 1080)  ← fallback
```

각 템플릿의 JSON schema에 `supports_sizes` 필드가 있어, 플랫폼 호환성 사전 검증 가능.
