Initial commit: Kei Design Agent

콘텐츠를 시각적으로 구조화된 슬라이드 HTML로 변환하는 독립 에이전트.

아키텍처 (4단계 파이프라인):
  1. Kei 실장 (Opus) — 콘텐츠 유형 분류 + 블록 배치
  2. 디자인 팀장 (Sonnet) — 레이아웃 컨셉 (블록 배치 + 페이지 수)
  3. 텍스트 편집자 (Sonnet) — 슬롯 텍스트 정리 (핵심 유지)
  4. CSS Grid 렌더러 — HTML 조립

블록 템플릿 7종:
  comparison, card-grid, relationship, process,
  quote-block, conclusion-bar, comparison-table

기술 스택:
  FastAPI + Anthropic API + Jinja2 + CSS Grid
  Pretendard Variable 한국어 폰트

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-24 17:25:47 +09:00
commit c42e65fc7e
28 changed files with 3302 additions and 0 deletions

View File

@@ -0,0 +1,388 @@
# Figma → 컴포넌트 추출 + 카탈로그 구축 계획
## 목적
Figma 디자인(바론컨설턴트 홈페이지 기획팀 공유)에서 재사용 가능한 슬라이드 콘텐츠 블록을 추출하고, 디자인 팀장(Sonnet)이 선택할 수 있는 카탈로그로 체계화한다.
---
## 현재 상태
### 보유 자산
| 항목 | 상태 | 위치 |
|------|------|------|
| Figma API 접근 | ✅ 가능 | Token: `.env` |
| 기존 블록 템플릿 7종 | ✅ 완성 | `templates/blocks/` |
| 디자인 토큰 | ✅ 완성 | `static/tokens.css` |
| 슬라이드 렌더러 | ✅ 완성 | `src/renderer.py` |
| 디자인 팀장 (DA-13) | ❌ todo | `src/design_director.py` |
| 블록 카탈로그 | ❌ 없음 | - |
### Figma 파일 구조
```
바론 공유 2025.05.13 (node: 1574-6254)
├── 1장 바론컨설턴트
├── 2장 디지털전환
│ ├── 2-1 건설산업에서의 디지털전환 (1920x8538, 스크롤형)
│ ├── 2-2 디지털전환과 소프트웨어 (1920x9123, 스크롤형)
│ └── 건설산업에서의 디지털전환 (1920x8536, 스크롤형)
│ [자세히보기]
│ ├── 2-1장 자세히보기 (4프레임: 건설산업/BIM/GIS/디지털트윈)
│ ├── 2-2장 자세히보기
│ └── 2-3장 자세히보기
├── 3장 제공서비스
│ ├── 3-1장 솔루션프로그램 자세히보기
│ └── 3-3장 빅룸 자세히보기
└── 모션작업
```
### 기존 블록 vs Figma에서 발견된 패턴
| 패턴 | 기존 블록 | Figma에서 발견 | 갭 |
|------|----------|--------------|-----|
| 2단 비교 | ✅ comparison | ✅ | - |
| 카드 그리드 | ✅ card-grid | ✅ (변형 다수) | 변형 추가 필요 |
| 벤 다이어그램 | ✅ relationship | ✅ | - |
| 단계 흐름 | ✅ process | ✅ | - |
| 강조 인용 | ✅ quote-block | ✅ (큰따옴표 장식) | 변형 추가 필요 |
| 결론 바 | ✅ conclusion-bar | ✅ | - |
| 비교 테이블 | ✅ comparison-table | ✅ | - |
| **이미지 갤러리** | ❌ | ✅ (2열, 3열, 2x2) | **신규** |
| **타임라인** | ❌ | ✅ (세로 원형 4단계) | **신규** |
| **섹션 타이틀** | ❌ | ✅ (영문+한글 공통 헤더) | **신규** |
| **사례 카드** | ❌ | ✅ (출처+불릿 카드) | **신규** |
| **핵심 지표** | ❌ (정의만) | ✅ (큰 숫자+보조) | **신규** |
| **아이콘 리스트** | ❌ | ✅ (아이콘+제목+설명) | **신규** |
| **Hero 섹션** | ❌ | ✅ (배경+원형이미지+텍스트) | **신규** |
| **CTA 버튼 바** | ❌ | ✅ (자세히보기 버튼) | **필요 시** |
---
## 작업 계획
### Phase A: Figma 분석 + 패턴 추출
#### A-1: Figma 전체 섹션 이미지 렌더링
- **작업:** 각 섹션/프레임을 이미지로 렌더링하여 시각적으로 패턴 식별
- **방법:** Figma API `/v1/images/{file_key}?ids={node_ids}`
- **산출물:** `docs/figma-screenshots/` 폴더에 PNG 저장
- **완료 기준:** 모든 자세히보기 프레임(8개)의 스크린샷 확보
#### A-2: Figma 노드 구조 심층 분석
- **작업:** 각 프레임의 depth=5 수준까지 노드 트리 분석
- **방법:** Figma API `/v1/files/{key}/nodes?ids={ids}&depth=5`
- **추출 정보:**
- TEXT 노드: 폰트, 크기, 색상, 내용
- FRAME/GROUP: 레이아웃 방식 (auto-layout, constraints)
- RECTANGLE: 배경색, 테두리, 둥근 모서리
- INSTANCE: 재사용 컴포넌트 식별
- **산출물:** `docs/figma-analysis/` 폴더에 구조 문서
#### A-3: 디자인 패턴 분류 + 명명
- **작업:** 추출된 시각 요소를 재사용 가능한 블록 단위로 분류
- **기준:**
- 2회 이상 반복되는 패턴 → 블록 후보
- 슬롯(교체 가능한 위치)이 명확한 것 → 우선 순위 높음
- 콘텐츠 유형과 매칭되는 것 → 우선 순위 높음
- **산출물:** 패턴 목록 + 각 패턴의 Figma 원본 노드 ID
### Phase B: HTML/CSS 컴포넌트 제작
#### B-1: 신규 블록 템플릿 제작 (6~8종)
- **파일:** `templates/blocks/{name}.html`
- **제작 순서 (우선순위):**
1. `section-title.html` — 공통 헤더 (모든 슬라이드에서 사용)
2. `example-card.html` — 사례 카드 (출처+불릿, 정책 문서 인용)
3. `image-gallery.html` — 이미지 갤러리 (2~4장, 근거 자료)
4. `timeline.html` — 타임라인 (세로/가로, 연혁/로드맵)
5. `big-number.html` — 핵심 지표 (큰 숫자 + 보조 텍스트)
6. `icon-list.html` — 아이콘 리스트 (아이콘+제목+설명, 기능 나열)
- **규칙:**
- 디자인 토큰(`var(--color-*)`) 사용 (하드코딩 색상 금지)
- Jinja2 슬롯 (`{{ variable }}`) 형식
- `<style>` 태그를 블록 HTML 안에 포함 (자체 완결)
- Figma 원본과 시각적으로 유사하되 1:1 복제 아님 (디자인 토큰 기준)
#### B-2: 기존 블록 변형 추가
- **대상:**
- `quote-block.html``quote-block-decorated.html` (큰따옴표 ::before/::after 장식)
- `card-grid.html``card-grid-icon.html` (아이콘 강조 변형)
- `comparison.html``comparison-visual.html` (이미지 포함 비교)
- **규칙:** 기존 슬롯 구조 유지, CSS만 변형
#### B-3: slide-base.html 업데이트
- **내용:** 신규 블록의 grid-area 지원, 디자인 토큰 추가 (필요 시)
- **주의:** 기존 7개 블록의 렌더링이 깨지지 않는지 반드시 검증
### Phase C: 카탈로그 구축
#### C-1: catalog.yaml 생성
- **파일:** `templates/catalog.yaml`
- **구조:**
```yaml
version: "1.0"
blocks:
# --- 기존 블록 ---
- id: quote-block
name: 강조 인용
visual: "좌측 컬러 라인 + 배경색 + 인용 텍스트"
when: "문제 제기, 핵심 주장, 정의 강조할 때"
not_for: "일반 설명문, 사례 나열"
slots:
required: [quote_text]
optional: [source]
character_limits:
quote_text: 150
source: 50
variations: [default, decorated]
figma_ref: null # 기존 블록은 Figma 이전에 제작됨
# --- 신규 블록 ---
- id: example-card
name: 사례 카드
visual: "제목 + 출처(기관/연도) + 불릿 목록, 테두리 박스. 2~3장 나란히."
when: "정책 문서 인용, 법령/지침 사례, 출처가 명확한 근거 제시"
not_for: "용어 정의, 일반 설명, 비교"
slots:
required: [items[].title, items[].bullets[]]
optional: [items[].source_org, items[].source_year]
character_limits:
title: 30
bullet: 60
variations: [single, 2col, 3col]
figma_ref: "1574:54586 > 2-1_01 > examples-row"
- id: image-gallery
name: 이미지 갤러리
visual: "이미지 2~4장 나란히 + 캡션, 중앙 정렬"
when: "근거 자료 사진, 문서 표지, 현장 사진, 참고 이미지"
not_for: "텍스트 콘텐츠, 다이어그램 (다이어그램은 relationship 사용)"
slots:
required: [images[].src, images[].alt]
optional: [images[].caption]
variations: [2col, 3col, 2x2]
figma_ref: "1574:54586 > 2-1_03 > image grid"
- id: timeline
name: 타임라인
visual: "세로/가로 축 위에 원형 마커 + 연도/제목/설명"
when: "연혁, 로드맵, 정책 시행 일정, 단계별 계획"
not_for: "프로세스 흐름 (순서는 있지만 시간이 아닌 것은 process 사용)"
slots:
required: [events[].year, events[].title]
optional: [events[].description]
character_limits:
title: 25
description: 60
variations: [vertical, horizontal]
figma_ref: "1574:54586 > 2-1_01 > timeline"
- id: big-number
name: 핵심 지표
visual: "큰 숫자(2rem+) + 단위 + 보조 설명. 2~4개 나란히."
when: "KPI, 통계, 목표 수치, 성과 지표"
not_for: "텍스트 설명, 정의"
slots:
required: [metrics[].number, metrics[].label]
optional: [metrics[].unit, metrics[].description]
variations: [2col, 3col, 4col]
figma_ref: null
- id: section-title
name: 섹션 타이틀
visual: "영문 소제목(작은 글씨) + 한글 대제목(큰 글씨), 하단 구분선"
when: "모든 슬라이드 상단, 섹션 시작"
not_for: "본문 콘텐츠 영역"
slots:
required: [title_ko]
optional: [title_en, subtitle]
figma_ref: "공통 > section_title 컴포넌트"
- id: icon-list
name: 아이콘 리스트
visual: "아이콘 + 제목 + 설명이 세로로 나열, 좌측 아이콘 정렬"
when: "기능 나열, 특성 목록, 장점 리스트"
not_for: "비교 (comparison 사용), 순서 (process 사용)"
slots:
required: [items[].icon, items[].title, items[].description]
character_limits:
title: 20
description: 80
variations: [vertical, horizontal, grid]
figma_ref: null
layouts:
- id: "65-35"
name: "6.5:3.5 좌우 분할"
grid_columns: "6.5fr 3.5fr"
when: "좌측 메인 콘텐츠 + 우측 보조/정의"
- id: "50-50"
name: "5:5 균등 분할"
grid_columns: "1fr 1fr"
when: "대등한 비교, 병렬 콘텐츠"
- id: "single"
name: "단일 컬럼"
grid_columns: "1fr"
when: "프로세스 흐름, 타임라인, 단순 구조"
- id: "35-65"
name: "3.5:6.5 좌우 분할"
grid_columns: "3.5fr 6.5fr"
when: "좌측 요약/네비게이션 + 우측 메인 콘텐츠"
```
#### C-2: 카탈로그 → 디자인 팀장 프롬프트 연결
- **파일:** `src/design_director.py` 수정
- **방법:** `catalog.yaml` 로드 → 블록 목록을 시스템 프롬프트에 삽입
- **프롬프트 구조:**
```
사용 가능한 블록:
- quote-block: 좌측 컬러 라인 + 인용 텍스트. 문제 제기할 때 사용. 일반 설명문에는 부적합.
- example-card: 제목+출처+불릿. 정책 사례 인용할 때 사용. 2~3장 나란히.
- card-grid: 2~4열 카드. 용어 정의 여러 개 나열할 때 사용.
...
사용 가능한 레이아웃:
- 65-35: 좌측 메인 + 우측 보조. 메인 콘텐츠가 많을 때.
- 50-50: 균등 비교. 대등한 내용일 때.
...
위 블록과 레이아웃만 사용하여 배치해라. 목록에 없는 블록은 만들지 마라.
```
#### C-3: renderer.py 업데이트
- **내용:** 신규 블록 템플릿 로드 지원
- **주의:** 기존 `BLOCK_SLOTS` dict에 신규 블록 추가
---
## 충돌 지점 + 리스크 검토
### 1. Figma 디자인 ≠ 디자인 토큰
| 리스크 | 설명 | 대응 |
|--------|------|------|
| **색상 불일치** | Figma는 파란 그라데이션 배경 사용, 디자인 토큰은 `#2563eb` 단색 | Figma 색상을 참고하되 토큰 체계 우선. 필요 시 토큰 추가 (`--color-accent-light` 등) |
| **폰트 불일치** | Figma는 특정 폰트(웹폰트 아닐 수 있음), 토큰은 Pretendard | Pretendard 유지. Figma 폰트 크기 비율만 참고 |
| **여백 불일치** | Figma 920px 프레임 vs 슬라이드 1280px | 비율 기반으로 변환 (920:1280 = 0.72배) |
**원칙:** Figma 디자인을 1:1 복제하지 않는다. 패턴(구조)만 추출하고, 스타일은 디자인 토큰으로 통일한다.
### 2. 블록 개수 증가 → 디자인 팀장 혼란
| 리스크 | 설명 | 대응 |
|--------|------|------|
| **선택지 과다** | 7개 → 13~15개로 증가 시 Sonnet이 부적절한 블록 선택 가능 | `when` + `not_for` 필드로 선택 기준 명확화. 프롬프트에 "이런 콘텐츠에는 이 블록을 쓰지 마라" 명시 |
| **유사 블록 혼동** | card-grid vs example-card vs icon-list 구분 | 카탈로그에 각 블록의 차이점 명시. 예: "card-grid는 정의, example-card는 출처 있는 사례, icon-list는 기능 나열" |
**대응:** catalog.yaml의 `not_for` 필드가 핵심. "이 블록은 이것에 쓰지 마라"를 명시해야 혼동 감소.
### 3. 기존 파이프라인 깨짐
| 리스크 | 설명 | 대응 |
|--------|------|------|
| **renderer.py 호환성** | 신규 블록 추가 시 기존 렌더링 깨짐 | 기존 7개 블록 테스트 먼저 통과 확인 후 신규 추가 |
| **BLOCK_SLOTS 누락** | design_director.py의 BLOCK_SLOTS에 신규 블록 미등록 | catalog.yaml에서 자동 로드하는 방식으로 전환 |
| **slide-base.html** | grid-template-areas에 신규 area명 미지원 | 동적 생성이므로 문제 없음 (Sonnet이 area명을 직접 지정) |
**대응:** Phase B 완료 후 기존 테스트 케이스(DA-16) 반드시 재실행.
### 4. Figma API 제약
| 리스크 | 설명 | 대응 |
|--------|------|------|
| **CSS 미제공** | Figma API는 CSS를 직접 제공하지 않음. 스타일 속성(fill, fontSize 등)만 제공 | 스타일 속성에서 CSS 수동 변환. 복잡한 것은 스크린샷 보고 직접 작성 |
| **이미지 에셋** | 벡터(VECTOR, ELLIPSE)는 PNG로 렌더링 가능하나 CSS 재현 필요 | 단순 도형은 CSS로 재현, 복잡한 것은 PNG export 후 img 태그 |
| **INSTANCE 참조** | Figma 컴포넌트(Instance)의 master 확인 필요 | `GET /v1/files/{key}/components`로 마스터 컴포넌트 조회 |
| **Rate Limit** | Figma API rate limit 존재 | 한 번에 대량 호출 자제, 결과 캐싱 |
### 5. Starlight(.astro) 연결 시 충돌
| 리스크 | 설명 | 대응 |
|--------|------|------|
| **CSS 변수 충돌** | Starlight 자체 CSS 변수(`--sl-*`)와 디자인 토큰(`--color-*`) 충돌 | 네임스페이스 분리: `--da-color-*` 접두사 사용 검토 |
| **폰트 로딩** | .astro에서 Pretendard CDN 로드 필요 | `<style is:global>`에 @import 포함 |
| **`<style>` 인라인** | 현재 렌더러가 CSS를 인라인하는데, .astro에서 Starlight CSS와 섞임 | `.astro` 출력 시 scoped style 또는 `is:global` + 높은 specificity |
**대응:** .astro 출력은 Phase C 이후 별도 태스크로. 현재는 독립 HTML 출력에 집중.
### 6. 유사 프로젝트 사례에서 발견된 문제
| 사례 | 문제 | 교훈 |
|------|------|------|
| **SlideSpeak** | 16개 레이아웃으로 시작했으나 실제 콘텐츠 다양성 커버 못함 | 블록을 조합하는 방식이 고정 레이아웃보다 유연 (현재 방식 유지) |
| **PPTAgent (EMNLP 2025)** | 레퍼런스 슬라이드 클러스터링 시 과소/과다 분류 문제 | 블록 수를 10~15개로 제한. 너무 세분화하면 AI 선택이 어려워짐 |
| **Beautiful.ai** | 300개 템플릿 중 실제 사용은 20개 | 처음부터 많이 만들지 않기. 실제 사용 빈도 보고 추가 |
| **InfoDesignLM** | 텍스트만으로 레이아웃 생성 시 콘텐츠 양 ↔ 공간 불일치 | character_limits를 카탈로그에 명시, 텍스트 편집자가 강제 준수 |
---
## 작업 순서 (의존 관계)
```
A-1 (Figma 스크린샷) ──┐
├→ A-3 (패턴 분류) → B-1 (신규 블록 제작) → B-3 (base 업데이트)
A-2 (노드 구조 분석) ──┘ ↓
C-1 (catalog.yaml)
B-2 (변형 추가) → C-2 (팀장 프롬프트 연결)
C-3 (renderer 업데이트)
기존 테스트 재실행 (DA-16)
```
### 예상 소요
| Phase | 작업 | 규모 |
|-------|------|------|
| A (분석) | Figma 스크린샷 + 노드 분석 + 패턴 분류 | 탐색/조사 |
| B (제작) | 신규 블록 6~8종 + 변형 3종 + base 업데이트 | 구현 (핵심) |
| C (카탈로그) | catalog.yaml + 팀장 프롬프트 + renderer 업데이트 | 연결/통합 |
**핵심 원칙:** Phase A에서 패턴을 정확히 분류하지 않으면 Phase B에서 쓸모없는 블록을 만들게 된다. 분석 먼저, 제작은 그 다음.
---
## 산출물 목록
```
docs/
├── figma-screenshots/ # A-1: 각 프레임 PNG
├── figma-analysis/ # A-2: 노드 구조 문서
└── FIGMA-COMPONENT-EXTRACTION-PLAN.md # 이 파일
templates/
├── catalog.yaml # C-1: 블록 카탈로그 (AI용 + 사람용)
├── blocks/
│ ├── (기존 7개)
│ ├── section-title.html # B-1: 신규
│ ├── example-card.html # B-1: 신규
│ ├── image-gallery.html # B-1: 신규
│ ├── timeline.html # B-1: 신규
│ ├── big-number.html # B-1: 신규
│ ├── icon-list.html # B-1: 신규
│ ├── quote-block-decorated.html # B-2: 변형
│ ├── card-grid-icon.html # B-2: 변형
│ └── comparison-visual.html # B-2: 변형
└── slide-base.html # B-3: 업데이트
src/
├── design_director.py # C-2: catalog.yaml 연동
└── renderer.py # C-3: 신규 블록 지원
```
---
## 금지 사항
1. Figma 디자인을 1:1 복제하지 않는다 (패턴만 추출, 스타일은 토큰 기준)
2. 기존 7개 블록 템플릿을 수정하지 않는다 (신규/변형은 별도 파일)
3. 한 번에 모든 블록을 만들지 않는다 (A-3 분류 결과를 보고 우선순위 재조정)
4. catalog.yaml 없이 블록을 추가하지 않는다 (카탈로그 미등록 = 디자인 팀장이 모름)
5. Kei Persona Agent 코드를 수정하지 않는다

686
docs/RESEARCH.md Normal file
View File

@@ -0,0 +1,686 @@
# Design Agent - Technology Research Report
Date: 2026-03-24
---
## 1. CSS Grid for Slide Layouts
### 1.1 Fixed-Viewport Approach (16:9, 1280x720)
**Recommended technique: Fixed container + CSS transform scaling.**
The slide container should be authored at a fixed "normal" size (1280x720), then scaled to fit any viewport using `transform: scale()`. This is the same approach used by reveal.js, the dominant HTML presentation framework.
```css
.slide {
width: 1280px;
height: 720px;
aspect-ratio: 16 / 9;
overflow: hidden;
position: relative;
}
```
For preview/embedding, wrap in a container that calculates a scale factor:
```css
.slide-wrapper {
width: 100%;
max-width: 1280px;
aspect-ratio: 16 / 9;
overflow: hidden;
}
.slide-wrapper > .slide {
transform-origin: top left;
transform: scale(var(--slide-scale, 1));
}
```
The scale factor can be computed with minimal JS: `containerWidth / 1280`.
**Key insight from reveal.js:** All presentations have a "normal" size at which they are authored. The framework automatically scales uniformly to fit different resolutions without changing aspect ratio or layout. Default is 960x700; for our use case, 1280x720 is the standard 16:9 HD dimension.
**Why this works for Design Agent:** The renderer produces HTML at exactly 1280x720. It never needs to be "responsive" -- it's a fixed-format document like a PDF page. Scaling is only for preview purposes.
### 1.2 Grid-Template-Areas for Block Combinations
`grid-template-areas` provides named regions that map directly to the block composition concept in the CLAUDE.md:
```css
.layout-quote-compare-cards-conclusion {
display: grid;
grid-template-areas:
"quote quote"
"compare cards"
"diagram diagram"
"conclusion conclusion";
grid-template-columns: 1fr 1fr;
grid-template-rows: auto 1fr auto auto;
gap: var(--spacing-block);
padding: var(--spacing-page);
}
```
**Best practice:** Define each layout as a separate CSS class with its own `grid-template-areas`. The Sonnet agent selects which layout class to apply based on the block combination it decides. This keeps the renderer deterministic -- it just applies the class.
**Reusability:** CSS variables allow the same grid template to adapt:
- Column count: `grid-template-columns: repeat(var(--cols, 3), 1fr)`
- Gap: `gap: var(--spacing-block)`
- Row sizing: `grid-template-rows` can mix `auto` (content-sized) and `1fr` (fill remaining)
### 1.3 Design Tokens
**Naming convention:** `--{category}-{property}-{variant}`
The CLAUDE.md already defines a good token set. The industry standard approach (from EightShapes, Nord Design System) uses kebab-case with semantic naming:
```
--color-primary, --color-accent, --color-neutral
--font-title, --font-subtitle, --font-body, --font-caption
--spacing-page, --spacing-block, --spacing-inner
--radius, --border-width, --accent-border
```
This matches what's already in the project's CLAUDE.md. No changes needed.
**For slide-specific tokens, add:**
```css
--slide-width: 1280px;
--slide-height: 720px;
--slide-aspect: 16 / 9;
```
### 1.4 Overflow Handling in Fixed Pages
Three techniques for ensuring content fits within fixed dimensions:
1. **Single-line truncation:**
```css
.truncate {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
```
2. **Multi-line truncation (line clamping):**
```css
.line-clamp-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
```
3. **Container overflow hidden (safety net):**
```css
.block { overflow: hidden; }
.slide { overflow: hidden; }
```
**Korean-specific consideration:** `word-break: keep-all` affects how text wraps, which impacts line count. Content fitting calculations must account for this. The Sonnet agent should be instructed with character limits per slot, not word limits.
---
## 2. Content-to-Layout Classification
### 2.1 How to Prompt an LLM for Reliable Classification
**Claude Structured Output (recommended):**
Anthropic launched Structured Outputs in November 2025, supporting Claude Sonnet 4.5 and Opus 4.1. This guarantees JSON schema conformance at the token generation level -- the model literally cannot produce tokens that violate the schema.
Implementation:
```python
from pydantic import BaseModel, Field
from anthropic import Anthropic
class ContentBlock(BaseModel):
content_type: str = Field(description="One of: comparison, process, relationship, big-number, definition, list, timeline, emphasis, problem")
evidence: str = Field(description="Which text patterns led to this classification")
suggested_block: str = Field(description="Block type from the template library")
class ContentAnalysis(BaseModel):
blocks: list[ContentBlock]
layout_direction: str = Field(description="How blocks should be arranged on the page")
primary_message: str = Field(description="The single key takeaway for this slide")
```
**Reliability strategies:**
- Set temperature to 0.0-0.1 for classification tasks (reduces format drift)
- Use the `output_format` parameter with JSON schema (not just prompting)
- Include one perfect example in the system prompt
- Add explicit validation instructions
### 2.2 Information Type Taxonomy for Presentations
Based on research from SlideSpeak (16 layout types), PPTAgent (EMNLP 2025), Beautiful.ai (300 templates), and Dr. Andrew Abela's Chart Chooser:
**The CLAUDE.md already defines 9 excellent content types.** Here is how they map to industry precedent:
| CLAUDE.md Type | SlideSpeak Equivalent | PPTAgent Category | Common Slide Type |
|---|---|---|---|
| comparison | SS_ITEMS_*_A/B | Content slide (multi-column) | Comparison slide |
| process | SS_STEPS_3/4/5 | Content slide (sequential) | Process/workflow slide |
| relationship | (custom) | Content slide (diagram) | Venn/tree diagram slide |
| big-number | SS_BIGNUMBER_1/3 | Content slide (metric) | KPI/statistics slide |
| definition (card-grid) | SS_ITEMS_3/4/5/6 | Content slide (grid) | Definition/feature slide |
| list | SS_CONTENT | Content slide (list) | Bullet point slide |
| timeline | (custom) | Content slide (sequential) | Timeline slide |
| emphasis (quote-block) | (custom) | Structural slide | Quote/callout slide |
| problem | (custom) | Structural slide | Problem statement slide |
**Additional types to consider (from industry):**
- **SWOT** (SlideSpeak has SS_SWOT) -- 4-quadrant grid
- **Matrix/Table** (already covered by comparison-table)
- **Cover/Title** -- for when the content is just a single title/subtitle
### 2.3 Structured Output Schema for Layout Decisions
The PPTAgent paper (EMNLP 2025) uses a two-stage approach that aligns perfectly with the Design Agent architecture:
- **Stage 1 (Opus):** Analyze content, classify into functional types, extract content schemas
- **Stage 2 (Sonnet):** Select reference layouts, fill content into slots, apply editing actions
PPTAgent represents all parsed outputs in JSON format for LLM compatibility. The Design Agent should do the same.
---
## 3. Slot-Based Template Systems
### 3.1 SlideSpeak's Named Slot System
SlideSpeak uses a comprehensive naming convention for template placeholders:
**Layout names:** SS_COVER, SS_CONTENT, SS_TABLE_OF_CONTENT, SS_BIGNUMBER_1_A, SS_BIGNUMBER_3_A, SS_ITEMS_3_A through SS_ITEMS_6_B, SS_STEPS_3 through SS_STEPS_5_ICONS, SS_SWOT
**Universal slot names:**
- `SS_TITLE` -- slide title
- `SS_SUBTITLE` -- subtitle
- `SS_LOGO` -- logo placeholder
- `SS_IMAGE` -- general image
- `SS_PAGE` -- page number
- `SS_PRESENTATION_TITLE` -- footer title
**Multi-item slot naming pattern:**
- `SS_ITEM_{N}_TITLE` -- title for item N
- `SS_ITEM_{N}_CONTENT` -- content for item N
- `SS_ITEM_{N}_NUMBER` -- number for item N (big-number layouts)
- `SS_ICON_{N}` -- icon for item N
**Key insight for Design Agent:** The `{{SLOT_NAME}}` convention in CLAUDE.md maps well. Adopt a similar systematic naming: `{{BLOCK_TITLE}}`, `{{ITEM_1_TITLE}}`, `{{ITEM_1_CONTENT}}`, etc.
### 3.2 Jinja2 for Template Rendering
Jinja2 is the recommended engine. It integrates natively with FastAPI and Python.
**Block inheritance for base layout:**
```jinja2
{# base_slide.html #}
<!DOCTYPE html>
<html lang="ko">
<head>
<link rel="stylesheet" href="design-tokens.css">
<style>{% block extra_style %}{% endblock %}</style>
</head>
<body>
<div class="slide">
{% block content %}{% endblock %}
</div>
</body>
</html>
```
**Block-level templates:**
```jinja2
{# blocks/comparison.html #}
<div class="block-comparison">
<div class="col-left">
<h3>{{ left_title }}</h3>
<p>{{ left_content }}</p>
</div>
<div class="col-right">
<h3>{{ right_title }}</h3>
<p>{{ right_content }}</p>
</div>
</div>
```
**Composition via includes:**
```jinja2
{# Generated by renderer based on Sonnet's layout decision #}
{% extends "base_slide.html" %}
{% block content %}
{% include "blocks/quote-block.html" %}
<div class="grid-row-2">
{% include "blocks/comparison.html" %}
{% include "blocks/card-grid.html" %}
</div>
{% include "blocks/conclusion-bar.html" %}
{% endblock %}
```
### 3.3 Slot Constraints
Each slot should have defined constraints that Sonnet respects:
| Slot Type | Max Characters (Korean) | Required | Notes |
|---|---|---|---|
| slide_title | 30 | Yes | Single line |
| block_title | 20 | Yes | Single line |
| item_title | 15 | Yes | Single line |
| item_content | 80 | No | 2-3 lines |
| quote_text | 120 | Yes | 3-4 lines |
| big_number | 8 | Yes | Number + unit |
| conclusion | 60 | Yes | Single line |
| caption | 40 | No | Single line |
**Korean consideration:** Korean characters are roughly 2x the width of Latin characters at the same font size. Character limits should be specified in characters, not words, since Korean doesn't use spaces the same way as English.
---
## 4. HTML to PDF Conversion
### 4.1 Playwright (Recommended)
**Why Playwright over Puppeteer:**
- Native Python SDK (no Node.js dependency for a Python project)
- Multiple browser support (Chromium, Firefox, WebKit), though PDF only works in Chromium
- Growing community, active maintenance, better CI/CD integration
- Full CSS Grid support via real Chromium rendering engine
**Python implementation:**
```python
from playwright.async_api import async_playwright
async def html_to_pdf(html_content: str, output_path: str) -> None:
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
await page.set_content(html_content, wait_until="networkidle")
await page.pdf(
path=output_path,
width="1280px",
height="720px",
print_background=True,
prefer_css_page_size=True,
)
await browser.close()
```
**Key options:**
- `print_background=True` -- required for background colors/images
- `prefer_css_page_size=True` -- lets CSS `@page` rules control dimensions
- `width`/`height` -- custom page dimensions (accepts px, in, mm, cm units)
### 4.2 Print CSS for Slide Format
```css
@media print {
@page {
size: 1280px 720px;
margin: 0;
}
body {
margin: 0;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
.slide {
width: 1280px;
height: 720px;
page-break-after: always;
overflow: hidden;
}
}
```
**`-webkit-print-color-adjust: exact`** is critical -- without it, background colors and images may be stripped in PDF output.
### 4.3 Quality Comparison
Both Puppeteer and Playwright use Chromium's print-to-PDF engine, so output quality is identical. The choice comes down to:
| Factor | Playwright | Puppeteer |
|---|---|---|
| Language | Python, JS, C#, Java | JS/Node.js only |
| PDF engine | Chromium only | Chromium only |
| CSS Grid quality | Excellent (Chromium) | Excellent (Chromium) |
| Korean font rendering | Excellent | Excellent |
| Install size | ~400MB (browser binary) | ~300MB |
| API ergonomics | Better async patterns | More established |
**Recommendation:** Playwright, because the Design Agent backend is Python. No need to bridge to Node.js.
### 4.4 Korean-Specific Considerations
- Fonts must be available on the server. Self-host Pretendard/Noto Sans KR WOFF2 files or use CDN.
- Set `lang="ko"` on the HTML element for proper line-breaking algorithms.
- Ensure `@font-face` declarations are loaded before PDF generation (`wait_until="networkidle"`).
---
## 5. Pure CSS Diagrams
### 5.1 Venn Diagrams (Pure CSS)
**Technique:** Overlapping circles with opacity and negative margins.
```css
.venn-container { display: flex; align-items: center; justify-content: center; }
.venn-circle {
width: 200px; height: 200px;
border-radius: 50%;
opacity: 0.7;
display: flex; align-items: center; justify-content: center;
padding: 20px;
text-align: center;
}
.venn-a { background: var(--color-accent); }
.venn-b { background: var(--color-neutral); margin-left: -60px; }
```
**Advanced approach (Adrian Roselli):** CSS Grid + `shape-outside` for text wrapping within overlapping regions. More complex but better for text-heavy Venn diagrams.
**Limitation:** Pure CSS Venn diagrams work well for 2-3 circles. Beyond that, SVG is more practical.
### 5.2 Flowcharts / Process Arrows (Pure CSS)
**Technique:** Flexbox/Grid layout + pseudo-elements for arrows.
```css
.process-steps { display: flex; align-items: center; gap: 0; }
.process-step {
background: var(--color-bg-subtle);
padding: var(--spacing-inner);
position: relative;
flex: 1;
}
.process-step + .process-step::before {
content: '';
position: absolute;
left: -12px; top: 50%;
transform: translateY(-50%);
border: 8px solid transparent;
border-left-color: var(--color-accent);
}
```
**CSS Anchor Positioning (2025-2026):** A new CSS feature for connecting elements with lines. Supported in Chrome 125+, Safari 26+, not yet in Firefox. Since we target Chromium (for PDF generation), this is usable but adds complexity. For the Design Agent, pseudo-element arrows are simpler and more reliable.
### 5.3 Tree/Hierarchy Diagrams (Pure CSS)
**Technique:** Nested `<ul>/<li>` + pseudo-elements for connector lines.
Libraries:
- **Treeflex** (https://dumptyd.github.io/treeflex/) -- CSS-only library for hierarchy trees, no JS
- Custom implementation using `border-left` on `<li>` for vertical lines, `::before` for horizontal connectors, `::after` for node circles
### 5.4 When to Use SVG Instead
| Use Case | CSS | SVG | Recommendation |
|---|---|---|---|
| Venn (2-3 circles) | Good | Better | CSS for simplicity |
| Process arrows (linear) | Excellent | Overkill | CSS |
| Tree (2-3 levels) | Good | Better | CSS (Treeflex) |
| Complex flowchart (branches) | Difficult | Much better | SVG |
| Curved connectors | Impossible | Easy | SVG |
| Data-driven charts | Impossible | Required | SVG |
| Accessible diagrams | Poor | Excellent | SVG |
**Recommendation for Design Agent:** Use pure CSS for simple diagrams (process arrows, 2-circle Venn, basic tree). Generate inline SVG for anything more complex. Since the renderer produces static HTML for PDF export, there's no JS concern.
**Accessibility note:** SVG has built-in accessibility elements (`<title>`, `<desc>`, `aria-*`). For the Design Agent's output (primarily visual slides for PDF), this is less critical but good practice.
---
## 6. Korean Typography in CSS
### 6.1 Font Selection
**Primary recommendation: Pretendard**
- Modern system-ui replacement font designed for cross-platform use
- Built on Inter (Latin) + Source Han Sans (CJK) + M PLUS 1p
- 9 weights + variable font support
- Dynamic subset via CDN (Google Fonts-style loading for Korean)
- Most popular Korean web font according to HTTP Archive 2024
**CDN options:**
```css
/* Dynamic subset (recommended for web) */
@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css');
/* Or from cdnjs */
@import url('https://cdnjs.cloudflare.com/ajax/libs/pretendard/1.3.9/variable/pretendardvariable-dynamic-subset.min.css');
```
**For PDF generation (self-hosted):**
Download WOFF2 files and serve locally to avoid CDN dependency during headless browser PDF generation.
**Fallback stack:**
```css
font-family: 'Pretendard Variable', 'Pretendard', -apple-system, 'Noto Sans KR',
'Malgun Gothic', sans-serif;
```
**Alternative: Noto Sans KR**
- Google Fonts native, widest browser support
- Available via Google Fonts CDN with automatic Korean subsetting
- Good for when Pretendard is not available
### 6.2 Line-Height and Letter-Spacing
**W3C KLREQ (Korean Layout Requirements) recommendations:**
| Property | Value | Rationale |
|---|---|---|
| `line-height` | 1.6-1.8 | CJK text needs ~1.7 (vs 1.2-1.5 for Latin) due to higher information density per character |
| `letter-spacing` | 0 to -0.02em | Korean text looks best with tight or default spacing. Avoid positive letter-spacing. |
| `word-spacing` | normal | Korean uses spaces between words (unlike Japanese/Chinese) |
**For slides specifically:**
```css
body {
line-height: 1.7;
letter-spacing: -0.01em;
}
h1, h2, h3 {
line-height: 1.3;
letter-spacing: -0.02em;
}
```
### 6.3 Word-Break Rules
```css
/* Korean text: keep syllable blocks together */
body {
word-break: keep-all;
overflow-wrap: break-word;
}
```
**`word-break: keep-all`** prevents line breaks within Korean syllable blocks (e.g., "한글" won't break as "한" / "글" mid-word). This is essential for readable Korean typography.
**`overflow-wrap: break-word`** is the safety net for extremely long strings (URLs, technical terms) that might overflow.
**Browser support:** All modern browsers support `keep-all` since 2016. There is ongoing W3C discussion about a potential `keep-all-hangul` value that would apply `keep-all` only to Hangul characters and `normal` to everything else.
### 6.4 Mixing Korean + English
**Key challenge:** At the same point size, Latin letters appear smaller than Korean characters.
**Solutions:**
1. Use a font designed for mixed text (Pretendard handles this well, being built on Inter + Source Han Sans)
2. Set the Latin font first in `font-family` stack, CJK font second (most CJK fonts include Latin glyphs, but their Latin is often inferior)
3. No need for `font-size-adjust` if using Pretendard (it's designed for optical balance between scripts)
**Punctuation:** Korean uses proportional-width punctuation (like Latin), unlike Japanese/Chinese which use full-width. Pretendard handles this correctly.
### 6.5 Language Attribute
Always set `lang="ko"` on the HTML element:
```html
<html lang="ko">
```
This enables the browser's Korean-specific line-breaking algorithms and font selection.
---
## 7. FastAPI Integration
### 7.1 Serving the Design Agent
The Design Agent should be a FastAPI application with these endpoints:
| Endpoint | Method | Purpose |
|---|---|---|
| `/api/analyze` | POST | Step 1: Send content to Opus for classification |
| `/api/generate` | POST | Steps 2-3: Sonnet selects + renderer produces HTML |
| `/api/generate/stream` | GET (SSE) | Steps 1-3 with real-time progress |
| `/api/preview/{job_id}` | GET | Return generated HTML for iframe preview |
| `/api/download/{job_id}/pdf` | GET | Generate and return PDF |
| `/api/download/{job_id}/html` | GET | Return HTML file |
| `/api/templates` | GET | List available block templates |
### 7.2 SSE Streaming
FastAPI has native SSE support (added to official docs). Two library options:
**Option A: sse-starlette (recommended)**
- Production-ready, W3C SSE spec compliant
- Already used in the HWPX project
- `pip install sse-starlette`
**Option B: fastapi-sse**
- Lighter weight, built specifically for FastAPI
- Supports sending Pydantic models as SSE events
- `pip install fastapi-sse`
**Implementation:**
```python
from sse_starlette.sse import EventSourceResponse
@router.get("/api/generate/stream")
async def stream_generation(content: str):
async def event_generator():
yield {"event": "step_start", "data": "analyzing"}
# ... Opus classification
yield {"event": "step_complete", "data": json.dumps(analysis)}
yield {"event": "step_start", "data": "selecting"}
# ... Sonnet selection
yield {"event": "step_complete", "data": json.dumps(slots)}
yield {"event": "step_start", "data": "rendering"}
# ... CSS Grid rendering
yield {"event": "complete", "data": job_id}
return EventSourceResponse(event_generator())
```
### 7.3 File Upload
```python
from fastapi import UploadFile, File
@router.post("/api/upload")
async def upload_content(file: UploadFile = File(...)):
# Read and extract text from uploaded file
text = await extract_text(file)
return {"text": text, "filename": file.filename}
```
### 7.4 Static File Serving for Preview
**For development:** FastAPI's `StaticFiles` mount for serving generated HTML:
```python
from fastapi.staticfiles import StaticFiles
app.mount("/static", StaticFiles(directory="output"), name="static")
```
**For production:** Serve HTML via API response body (like the HWPX project does with `<iframe srcDoc>`). This is more secure -- no direct file path exposure.
**Font serving:** Self-hosted Pretendard WOFF2 files should be served as static files for reliable PDF generation.
---
## Summary: Technology Stack Recommendation
| Component | Technology | Version | Rationale |
|---|---|---|---|
| LLM (Analysis) | Claude Opus via Anthropic API | Structured Outputs beta | Content classification, layout direction |
| LLM (Selection) | Claude Sonnet via Anthropic API | Structured Outputs beta | Slot filling, content editing |
| Template Engine | Jinja2 | >=3.1 | Native Python, FastAPI integration, block inheritance |
| CSS Layout | CSS Grid + grid-template-areas | Native CSS | Named regions map to block composition |
| Design Tokens | CSS Custom Properties | Native CSS | Already defined in CLAUDE.md |
| Typography | Pretendard Variable | 1.3.9 | Best Korean web font, dynamic subset |
| Fallback Font | Noto Sans KR | Latest | Google Fonts CDN backup |
| PDF Generation | Playwright (Python) | >=1.40 | Native Python SDK, full CSS Grid support |
| Web Framework | FastAPI | >=0.115 | SSE support, file upload, same as HWPX project |
| SSE | sse-starlette | >=2.0 | Production-ready, W3C compliant |
| CSS Diagrams | Pure CSS + inline SVG fallback | N/A | Pseudo-elements for simple, SVG for complex |
| Slide Scaling | CSS transform: scale() | Native CSS | reveal.js-proven approach |
| Korean Line Breaking | word-break: keep-all | Native CSS | W3C KLREQ recommendation |
---
## Key References
### CSS Grid & Layout
- [CSS-Tricks Complete Guide to CSS Grid](https://css-tricks.com/complete-guide-css-grid-layout/)
- [Smashing Magazine: Understanding CSS Grid Template Areas](https://www.smashingmagazine.com/2020/02/understanding-css-grid-template-areas/)
- [MDN: aspect-ratio](https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio)
- [Grid by Example](https://gridbyexample.com/examples/)
- [reveal.js Presentation Size](https://revealjs.com/presentation-size/)
### Design Tokens
- [CSS-Tricks: What Are Design Tokens?](https://css-tricks.com/what-are-design-tokens/)
- [EightShapes: Naming Tokens in Design Systems](https://medium.com/eightshapes-llc/naming-tokens-in-design-systems-9e86c7444676)
- [FrontendTools: CSS Variables Guide](https://www.frontendtools.tech/blog/css-variables-guide-design-tokens-theming-2025)
- [Nord Design System: Naming](https://nordhealth.design/naming/)
### Content Classification & Presentation AI
- [PPTAgent (EMNLP 2025)](https://arxiv.org/abs/2501.03936)
- [SlideSpeak Layouts & Placeholders](https://docs.slidespeak.co/basics/custom-templates/layouts-and-placeholders)
- [SlideSpeak Template Preparation](https://docs.slidespeak.co/basics/custom-templates/preparing)
- [SlideModel: 12 Types of Slides](https://slidemodel.com/types-of-slides/)
- [SlideUpLift: Types of Slides](https://slideuplift.com/blog/types-of-slides/)
- [LLM-Powered Slide Decks Comparison](https://nbrosse.github.io/posts/llm-slides/llm-slides.html)
### Structured Output
- [Anthropic Structured Outputs Docs](https://platform.claude.com/docs/en/build-with-claude/structured-outputs)
- [PromptLayer: How JSON Schema Works for Structured Outputs](https://blog.promptlayer.com/how-json-schema-works-for-structured-outputs-and-tool-integration/)
### PDF Generation
- [Playwright Python page.pdf() API](https://playwright.dev/python/docs/api/class-page)
- [PDF Generation from HTML Comparison (2026)](https://medium.com/@coders.stop/pdf-generation-from-html-i-tested-puppeteer-playwright-and-wkhtmltopdf-so-you-dont-have-to-d14228d28c4c)
- [Checkly: Generating PDFs with Playwright](https://www.checklyhq.com/docs/learn/playwright/generating-pdfs/)
- [Print CSS Cheatsheet](https://www.customjs.space/blog/print-css-cheatsheet/)
### Pure CSS Diagrams
- [Adrian Roselli: A CSS Venn Diagram](https://adrianroselli.com/2018/12/a-css-venn-diagram.html)
- [CSS-Tricks: A CSS Venn Diagram](https://css-tricks.com/a-css-venn-diagram/)
- [FreeFrontend: 17 Pure CSS Flowcharts](https://freefrontend.com/css-flowcharts/)
- [Cory Rylan: Flow Charts with CSS Anchor Positioning](https://coryrylan.com/blog/flow-charts-with-css-anchor-positioning)
- [Treeflex: CSS Tree Library](https://dumptyd.github.io/treeflex/)
- [MDN: CSS Anchor Positioning](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Anchor_positioning)
### Korean Typography
- [W3C KLREQ: Requirements for Hangul Text Layout](https://www.w3.org/TR/klreq/)
- [Pretendard GitHub](https://github.com/orioncactus/pretendard)
- [Noto Sans KR on Google Fonts](https://fonts.google.com/noto/specimen/Noto+Sans+KR)
- [CJK Typesetting in 2025](https://asianabsolute.co.uk/blog/cjk-typesetting-challenges-workflows-and-best-practices/)
- [Typotheque: CJK Typesetting Principles](https://www.typotheque.com/articles/typesetting-cjk-text)
- [CSS WG: word-break for Korean (Issue #4285)](https://github.com/w3c/csswg-drafts/issues/4285)
### FastAPI & SSE
- [FastAPI SSE Tutorial](https://fastapi.tiangolo.com/tutorial/server-sent-events/)
- [sse-starlette on PyPI](https://pypi.org/project/sse-starlette/)
- [Real Python: FastAPI with Jinja2](https://realpython.com/fastapi-jinja2-template/)
### SVG vs CSS
- [Adobe Blog: CSS vs SVG](https://blog.adobe.com/en/publish/2015/09/16/css-vs-svg-the-final-roundup)
- [Sara Soueidan: Accessible Data Charts](https://www.sarasoueidan.com/blog/accessible-data-charts-for-khan-academy-2018-annual-report/)