04. design_agent 추가 — 콘텐츠 시각 구조화 슬라이드 생성기

5단계 AI 파이프라인:
1. Kei 실장(Opus via Kei API) — 꼭지 추출 + 정보 구조 파악
2. 디자인 팀장 — FAISS 블록 검색 + Opus 추천 + Sonnet 블록 매핑
3. Kei 편집자(Kei API) — 도메인 전문 텍스트 정리
4. 디자인 실무자(Sonnet + Jinja2) — CSS 변수 조정 + HTML 조립
5. 디자인 팀장(Sonnet) — 균형 재검토 (최대 2회 루프)

블록 라이브러리 46개 (6 카테고리) + _legacy 13개
FAISS 블록 검색 (bge-m3, 1024차원)
SVG N개 동적 배치 (cos/sin 좌표 계산)
Pillow 이미지 크기 측정 + base64 인라인
컨테이너 예산 기반 블록 배치 (zone별 높이 px)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 18:47:13 +09:00
parent 9b905a4313
commit 688ddbbb17
244 changed files with 23955 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
ANTHROPIC_API_KEY=sk-ant-...
KEI_API_URL=http://localhost:8000
LOG_LEVEL=DEBUG

11
04. design_agent/.gitignore vendored Normal file
View File

@@ -0,0 +1,11 @@
.env
__pycache__/
*.pyc
.pytest_cache/
.ruff_cache/
*.egg-info/
dist/
build/
.venv/
node_modules/
data/

516
04. design_agent/CLAUDE.md Normal file
View File

@@ -0,0 +1,516 @@
# Design Agent — 콘텐츠 시각 구조화 슬라이드 생성기
## 프로젝트 목적
텍스트/MDX 콘텐츠를 **가로 슬라이드(1페이지 또는 다중 페이지)**로 시각 구조화하는 독립 에이전트.
콘텐츠의 의미를 분석하여 적합한 레이아웃 블록을 선택하고, 핵심만 추출하여 깔끔한 HTML/CSS로 렌더링한다.
**핵심 원칙:**
- 전체 페이지를 하나의 고정 템플릿으로 찍어내는 것이 아니라, 콘텐츠를 분석 → 각 덩어리별로 적합한 레이아웃 블록 선택 → 조합하여 배치
- 기획자(편집자)가 정리한 텍스트가 기준. **디자인이 텍스트에 맞춘다** (텍스트가 디자인에 맞추는 것이 아님)
- **모든 판단은 실장/팀장/편집자의 사고. 하드코딩 없음**
---
## 아키텍처 (5단계 파이프라인)
```
[1단계] Kei 실장 (Sonnet) — AI 사고
꼭지 추출 → 정보 구조 파악 → 레이어/강조/배치/role 판단
[2단계] 디자인 팀장 — 2-Step
Step A: 레이아웃 프리셋 선택 (규칙 기반, LLM 불필요)
- 실장의 role 분석을 보고 프리셋 자동 결정
Step B: 프리셋 안에서 블록 매핑 + 글자 수 가이드 (Sonnet)
- 선택된 프리셋의 CSS가 프롬프트에 포함됨
- flow → body/main, reference → sidebar
[3단계] Kei 텍스트 편집자 (Sonnet) — AI 사고
글자 수 가이드 참고하되 내용 의미 우선. 도메인 용어 보존하며 편집
[4단계] 디자인 실무자 (Sonnet + Jinja2 + CSS) — AI + 코드
편집자가 정리한 텍스트에 맞게 디자인 조정 + HTML 조립
[5단계] 디자인 팀장 (Sonnet) — AI 사고
전체 균형 재검토 → 공간 재배분 → 2차 조정 지시
```
### 레이아웃 프리셋 (2단계 Step A)
실장의 role 분석을 보고 **규칙 기반**으로 프리셋을 자동 선택한다. LLM 호출 불필요.
| 프리셋 | 조건 | CSS grid |
|--------|------|----------|
| `sidebar-right` | reference 꼭지가 1개 이상 있음 | `"title title" "body sidebar" "footer footer"` / 65fr 35fr |
| `two-column` | 모든 flow 꼭지가 대등한 비교 | `"title title" "left right" "footer footer"` / 1fr 1fr |
| `hero-detail` | 고강조 꼭지 1개 + 나머지 보조 | `"title title" "hero hero" "detail detail" "footer footer"` |
| `single-column` | 모든 꼭지가 flow, 순차적 | `"title" "body" "footer"` / 1fr |
**선택 규칙:**
```
reference 꼭지 있음 → sidebar-right
모든 flow가 대등 비교 → two-column
고강조 1개 + 나머지 보조 → hero-detail
나머지 → single-column
```
### 역할 분리
| 역할 | 담당 | 방식 | 하는 일 | 하지 않는 일 |
|------|------|------|---------|------------|
| Kei 실장 | Sonnet | AI | 꼭지 추출, 정보 구조 파악, 레이어/강조/배치/role 판단 | 디자인, 텍스트 편집 |
| 디자인 팀장 Step A | 코드 | 규칙 | 실장의 role에 따라 레이아웃 프리셋 자동 선택 | AI 판단 불필요 |
| 디자인 팀장 Step B | Sonnet | AI | 프리셋 안에서 블록 매핑, 글자 수 가이드, zone 배정 | 레이아웃 구조 결정 (이미 정해짐) |
| 텍스트 편집자 | Sonnet | AI | 도메인 용어 보존하며 편집, 출처 보존, 표 편집 | 레이아웃 결정 |
| 디자인 실무자 | Sonnet + 코드 | AI + 코드 | 텍스트에 맞게 디자인 조정, HTML/CSS 조립 | 콘텐츠 의미 판단 |
---
## 핵심 프로세스
```
사용자 콘텐츠 입력 (텍스트/MDX 붙여넣기 또는 파일 업로드)
[1단계] Kei 실장 — 꼭지 추출 + 분석
꼭지 추출:
- 본문에서 핵심 꼭지 추출 (2~5개)
- 1페이지 적정 꼭지 수: 5개
페이지 분리 판단:
- 중요도가 5개를 넘고 레이어도 동등하면 → 2페이지로 분리
- 내용과 의미 기반으로 자연스러운 분할 (2/3, 3/4, 4/3, 5/2 등)
- 5개인데 내용이 많으면 → 꼭지 레이어를 보고 세부 내용은 자세히보기로
각 꼭지 분석:
- 레이어 수준 (도입/핵심/보조/결론)
- 강조 판단 (어떤 꼭지를 눈에 띄게 할 것인가)
- 배치 방향 (세로로 긴 꼭지, 가로 나열 꼭지)
이미지 판단:
- 몇 개인지, 어떤 꼭지에 속하는지
- 핵심인지(도표/차트) 보조인지(참고 문서 표지)
- 텍스트가 포함된 이미지인지 (너무 축소하면 안 됨)
표 판단:
- 행/열 규모
- 전체 표시 가능한지, 요약 필요한지
상세 콘텐츠 판단:
- 너무 구체적/세부적인 내용은 "자세히보기" 대상
[2단계] 디자인 팀장 — Step A + Step B
Step A: 레이아웃 프리셋 선택 (규칙 기반, LLM 불필요)
- 실장의 role 분석을 보고 자동 선택:
reference 있음 → sidebar-right
대등 비교 → two-column
고강조 1개 → hero-detail
나머지 → single-column
- 선택된 프리셋의 CSS grid가 Step B 프롬프트에 포함됨
Step B: 프리셋 안에서 블록 매핑 (Sonnet)
- 선택된 프리셋의 zone(body/sidebar/footer)에 꼭지를 배정
- flow 꼭지 → body/main zone
- reference 꼭지 → sidebar zone
- detail_target 꼭지 → popup 연결
- catalog에서 각 꼭지에 적합한 블록 타입 선택
- 각 블록의 대략적 글자 수 가이드
이미지 배치:
- 원본 이미지 크기 확인 (Pillow Image.open().size)
- 가로/세로 비율에 따라 영역 결정
- 이미지는 원본 그대로 사용, 크기만 조절
표 배치:
- 행×열 규모 보고 공간 안에 들어가는지 판단
- 안 들어가면 요약 요청 또는 popup
자세히보기:
- detail_target 꼭지는 <details>/<summary>로 popup 연결
[3단계] Kei 텍스트 편집자 — 텍스트 정리
- 팀장의 글자 수 가이드 참고하되, 내용 의미가 우선
- 전체 컨텍스트와 핵심 용어 유지
- 도메인 전문가로서 세련된 표현으로 편집
- 출처 보존, 개조식 작성, 날조 금지
- 결과 글자 수가 가이드와 다를 수 있음 (의미 > 글자 수)
- 표 내용도 편집 (핵심 행/열 선택, 요약 등)
- 자세히보기 대상은 요약 버전 + 상세 버전 둘 다 작성
[4단계] 디자인 실무자 — 디자인 조정 + HTML 조립
텍스트 맞춤:
- 편집자가 정리한 텍스트 양에 맞게 디자인 조정
- 텍스트가 가이드보다 길면 → 폰트/여백/박스를 조정 (텍스트를 자르지 않음)
- 빈 공간 방치 안 함 (박스 줄이거나 공간 활용)
이미지 처리:
- object-fit: contain (비율 유지, 잘리지 않게)
- 팀장이 정한 영역 크기에 맞춤
표 처리:
- table-layout: fixed + container query 폰트 스케일링
- 행/열 수에 따라 셀 크기 자동 조정
자세히보기:
- <details>/<summary>로 접기/펼치기
- 인쇄 시 자동 펼침 (JavaScript)
HTML 조립:
- Jinja2 템플릿 렌더링
- 다중 페이지 시 page-break 처리
[5단계] 디자인 팀장 — 전체 재검토
균형 점검:
- 1차 조립 결과의 전체 균형 확인
- 블록별 채움 비율 (텍스트 양 vs 공간)
- 블록 간 균형 (한쪽만 빽빽하고 다른 쪽 비어있지 않은지)
이미지/표 점검:
- 이미지 크기가 적절한지 (너무 작아서 안 보이지 않는지)
- 표가 읽을 수 있는 크기인지
조정:
- 필요 시 공간 재배분 → 실무자에게 2차 조정 지시
- 좌우 불균형, 어색한 빈 공간 해소
- 최종 HTML 출력
미리보기 → HTML 다운로드
```
**핵심 원칙:**
- 디자인 팀장은 레이아웃 + 공간 배분. 텍스트를 건드리지 않는다.
- 텍스트 편집자가 정리한 텍스트가 기준. 디자인이 텍스트에 맞춘다.
- 실무자는 텍스트를 자르지 않고, 디자인을 조정한다.
- 이미지는 원본 그대로 사용, 크기만 조절.
- 표는 표로 유지, 공간 안 되면 요약하거나 페이지 분리.
- 상세 내용은 `<details>`로 접기/펼치기.
- 1차 조립 후 팀장이 전체 균형을 재검토하여 2차 조정.
- **모든 기준은 하드코딩 없이 실장/팀장/편집자의 사고로 판단.**
---
## 콘텐츠 유형 분류 기준
실장이 콘텐츠를 분석하여 아래 유형으로 분류한다.
**이 분류는 하드코딩이 아니라, 실장이 매번 사고하여 판단한다.**
| 텍스트 패턴 | 유형 | 적합한 블록 |
|------------|------|-----------|
| "A vs B", 장단점, 차이점 | 비교 | 2단 병렬 / 비교 테이블 |
| "1단계 → 2단계 → 3단계" | 프로세스 | 플로우차트 / 단계 카드 |
| "X는 Y를 포함한다", 상위-하위 | 구성/관계 | 벤 다이어그램 / 트리 |
| 수치, KPI, 통계 | 핵심 지표 | 큰 숫자 + 보조 텍스트 |
| 용어 정의, 개념 설명 | 정의 | 카드 3열 / 아이콘 카드 |
| 기능/특성 나열 | 목록 | 아이콘 리스트 / 카드 그리드 |
| 연도별 사건, 로드맵 | 시간 순서 | 타임라인 (가로/세로) |
| 핵심 메시지, 결론 | 강조 | 결론 바 / 인용 블록 |
| 문제 상황, 경고 | 문제 제기 | 경고 박스 / 강조 인용 |
| 이미지 + 텍스트 | 이미지 참조 | 전체너비 / 사이드 / 썸네일 |
| 다항목 데이터 | 표 | 비교 테이블 |
| 세부/구체적 내용 | 자세히보기 | `<details>/<summary>` |
---
## 블록 타입 정의
6개 카테고리, 18개 블록 변형. **상세는 `templates/blocks/INDEX.md` 참조.**
| 카테고리 | 블록 수 | 구현 방식 | 주요 블록 |
|---------|--------|---------|---------|
| headers/ | 2 | HTML/CSS | section-title-with-bg, topic-left-right |
| cards/ | 3 | HTML/CSS | card-image-3col, card-text-grid, card-dark-overlay |
| tables/ | 1 | HTML/CSS | compare-3col-badge |
| visuals/ | 4 | **SVG** (수학적 좌표 계산) | venn-diagram, circle-gradient, compare-pill-pair, process-horizontal |
| emphasis/ | 5 | HTML/CSS | quote-left-border, conclusion-accent-bar, comparison-2col, banner-gradient, quote-question |
| media/ | 3 | HTML/CSS + 이미지 | image-row-2col, image-grid-2x2, image-side-text |
각 블록은 독립적인 컴포넌트로 슬롯(교체 가능한 위치)을 가지며, `catalog.yaml`에 when/not_for/slots가 정의되어 있다.
---
## 이미지 처리 원칙
- 원본 이미지를 **그대로 사용** (crop/재구성은 극히 예외)
- 팀장이 슬라이드 구조에 맞게 **크기만 조절** (가로/세로 비율 유지)
- 이미지 크기 읽기: Pillow `Image.open().size` (헤더만 읽음, 전체 로드 안 함)
- 가로형(ratio > 1.2) → 전체 너비 배치
- 세로형(ratio < 0.8) → 텍스트 옆 배치
- 텍스트 포함 도표 → 너무 작게 축소하면 안 됨
- CSS: `object-fit: contain` (전체 보이게, 비율 유지)
## 표 처리 원칙
- 표는 **표로 유지** (다른 형태로 전환하지 않음)
- 공간에 안 들어가면: 요약하거나 페이지 분리
- CSS: `table-layout: fixed` + container query 폰트 스케일링
- 표 내용 편집은 Kei 텍스트 편집자가 담당
## 자세히보기 (상세 콘텐츠) 원칙
- HTML 네이티브 `<details>/<summary>` 사용 (JavaScript 불필요)
- 슬라이드 표면: 요약/핵심만 표시
- 펼치면: 전체 상세 내용 표시
- 인쇄 시: JavaScript 6줄로 자동 펼침
- 정보 밀도는 실장/팀장이 사고로 판단 (하드코딩 기준 없음)
---
## 페이지 구성 원칙
### 레이아웃 배치 규칙
- CSS Grid 기반 (`grid-template-areas`)
- 가로 슬라이드 비율: 16:9 (1280×720)
- 1페이지 적정 꼭지 수: 5개
- 정보 계층: 위 → 아래 (문제 제기 → 분석 → 결론)
- 여백: 블록 간 최소 20px, 페이지 패딩 40px
### 페이지 분리 기준
- 꼭지 5개 이하 + 내용 적절 → 1페이지
- 꼭지 5개 + 내용 많음 → 1페이지 + 일부 자세히보기
- 꼭지 5개 초과 + 레이어 동등 → 2페이지 (의미 기반 분할)
- 분할 비율: 2/3, 3/4, 4/3, 5/2 등 내용에 따라
### 블록 조합 예시
```
┌─────────────────────────────────────────────┐
│ [강조 인용] 문제 제기 │
├──────────────────┬──────────────────────────┤
│ [사례 카드 2열] │ [카드 그리드 3열] │
│ 정책 사례 │ 용어 정의 │
├──────────────────┴──────────────────────────┤
│ [관계도] 벤 다이어그램 │
├─────────────────────────────────────────────┤
│ [결론 바] 핵심 한 줄 │
└─────────────────────────────────────────────┘
```
---
## 글자 수 추정 (타이포그래피 기반)
한글은 글자 너비가 거의 일정하므로, 블록 크기에서 글자 수를 계산할 수 있다.
**이 계산은 팀장의 글자 수 가이드를 보조하는 참고 도구이지, 하드코딩 기준이 아니다.**
```
계산식:
한 줄 글자 수 = 블록 너비(px) ÷ (폰트 크기(px) × 0.97)
줄 수 = 블록 높이(px) ÷ (폰트 크기(px) × 줄간격)
총 글자 수 = 한 줄 글자 수 × 줄 수 × 안전 계수(0.85)
```
Pretendard 폰트 크기별 참고값 (1회 측정, 상수 저장):
| 폰트 크기 | 한글 글자 너비 | 줄간격 1.6 기준 줄 높이 |
|----------|-------------|---------------------|
| 12px | ~11.6px | 19.2px |
| 16px | ~15.5px | 25.6px |
| 20px | ~19.4px | 32.0px |
| 24px | ~23.3px | 38.4px |
---
## 디자인 원칙 (절대 규칙)
### DO (해야 하는 것)
- 여백을 충분히 확보한다 (여백 > 장식)
- 색상은 최대 3개 (메인 1개 + 포인트 1개 + 중성 1개)
- 폰트 크기 체계를 일관되게 유지 (제목/소제목/본문/캡션 4단계)
- 흑백 기조 + 포인트 컬러 최소 사용
- 정보 계층을 시각적으로 명확히 표현
### DON'T (하지 않는 것)
- HTML/CSS 블록의 배경 그라데이션 금지 (**SVG 시각화 블록, 디자인 의도가 명확한 블록(배너, 오버레이, 콜아웃 등)은 허용**)
- CSS 애니메이션/트랜지션 금지
- 호버 효과 금지
- 그림자(box-shadow) 최소화 (1개 레벨만. SVG filter는 예외)
- 다크 테마 금지 (요청하지 않는 한)
- 둥근 모서리 과다 사용 금지 (border-radius 최대 8px)
- 텍스트를 자르지 않는다 (디자인이 텍스트에 맞춘다)
---
## 디자인 토큰
```css
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
}
```
---
## Kei API 연동
### 호출 포인트
| 단계 | API | 용도 |
|------|-----|------|
| 1단계 꼭지 추출 | Anthropic API (Sonnet) | 꼭지 추출 + 레이어 + 강조 + 배치 + 이미지/표/상세 판단 |
| 2단계 레이아웃 설계 | Anthropic API (Sonnet) | 블록 매핑 + 공간 배분 + 글자 수 가이드 |
| 3단계 텍스트 정리 | Anthropic API (Sonnet) | 의미 보존 편집 + 표 편집 + 자세히보기 작성 |
| 4단계 디자인 조정 + 조립 | Anthropic API (Sonnet) + Jinja2/CSS | 텍스트에 맞게 디자인 조정 + HTML 생성 |
| 5단계 재검토 | Anthropic API (Sonnet) | 균형 점검 + 2차 조정 |
### 독립 실행 가능
- Kei API 없이 Anthropic API 직접 호출로 동작
- Kei API 연결 시 1단계에서 Kei RAG 지식 활용 가능
---
## 블록 라이브러리
### 핵심 원칙: 변형 기반 선택
하나의 블록 유형에 **여러 변형(variation)**이 존재하고, 디자인 팀장이 콘텐츠 성격에 맞는 변형을 **검색하여 선택**한다.
```
콘텐츠 분석 → "이 꼭지는 좌:질문 우:설명 구조다"
블록 라이브러리 검색 → headers/ 카테고리에서 후보 3~4개 반환
팀장이 선택 → topic-left-right.html
```
### 카테고리 구조 (6개, 18개 블록)
```
templates/blocks/
├── INDEX.md ← 전체 인덱스 + 추가 예정 목록
├── headers/ (2개) ← 타이틀, 꼭지 헤더
│ ├── section-title-with-bg.html 배경 이미지 + 영문/한글 타이틀
│ └── topic-left-right.html 좌:파란 제목 + 우:설명
├── cards/ (3개) ← 카드 계열 (정보 나열)
│ ├── card-image-3col.html 상단 이미지 + 하단 텍스트 3열
│ ├── card-text-grid.html 텍스트만 카드 2~4열
│ └── card-dark-overlay.html 다크 이미지 배경 + 흰 텍스트 오버레이
├── tables/ (1개) ← 표/비교 계열
│ └── compare-3col-badge.html A|VS배지|B 3단 비교
├── visuals/ (4개) ← 시각 요소 — **SVG로 제작**
│ ├── circle-gradient.html 파란 그라데이션 원 + 텍스트
│ ├── compare-pill-pair.html 둥근 테두리 박스 2개 + VS
│ ├── venn-diagram.html 벤 다이어그램 (**SVG premium, 수학적 계산**)
│ └── process-horizontal.html 가로 단계 흐름
├── emphasis/ (5개) ← 강조 (인용, 결론)
│ ├── quote-left-border.html 좌측 라인 + 인용
│ ├── conclusion-accent-bar.html 좌측 파란 라인 + 밝은 배경 결론
│ ├── comparison-2col.html 2단 병렬 비교
│ ├── banner-gradient.html 파란 그라데이션 전체 너비 배너
│ └── quote-question.html 질문형 강조 박스
└── media/ (3개) ← 이미지/미디어
├── image-row-2col.html 이미지 2장 나란히
├── image-grid-2x2.html 이미지 4장 2x2 격자
└── image-side-text.html 좌:이미지 우:텍스트+불릿
```
### 시각화 방식 (Phase 1 확정)
**블록 유형별 구현 방식:**
| 블록 유형 | 구현 방식 | 이유 |
|----------|---------|------|
| 텍스트 블록 (카드, 비교, 표, 인용, 결론) | **HTML/CSS** | Jinja2 템플릿으로 조립 |
| 시각화 다이어그램 (관계도, 프로세스, 순환도) | **SVG** | 좌표 정확 + 그라데이션/글로우 가능 + 개수 자동 대응 |
| 실사 이미지 (시공 사진, 3D 렌더링) | **Figma export 또는 원본 이미지** | CSS/SVG로 재현 불가 |
| 배경 텍스처 (보케, 분위기) | **Gemini API 이미지 생성** | 실사 배경 전용 |
**SVG 시각화 원칙 (검증 완료):**
- SVG 안에 `<text>` 포함 → 원 좌표와 텍스트 좌표가 같은 공간 → **위치 100% 정확**
- `radialGradient`, `linearGradient` → 고급 그라데이션
- `filter: feGaussianBlur`, `feDropShadow` → 글로우/그림자
- 하이라이트 오버레이 → 구체(3D) 느낌
- 수학적 계산 (`cos/sin`) → N개 원소 자동 배치 (`360/N` 간격) — **Phase 2 구현 예정. 현재는 3개 고정 SVG**
**AI 이미지 생성은 시각화에 사용하지 않는다:**
- AI 생성 이미지는 원 위치가 매번 달라 텍스트 위치를 맞출 수 없음 (검증 완료)
- AI 이미지는 **실사 배경 텍스처** (보케, 헥사곤, 분위기)에만 사용
### 변형 검색 (향후)
변형이 수십~수백 개로 늘어나면:
1. 각 블록 HTML의 구조/슬롯/용도를 임베딩
2. FAISS 인덱스로 구축
3. 콘텐츠 분석 결과를 쿼리 → 적절한 변형 3~4개 반환
4. 팀장이 최종 선택
현재 규모(13개)에서는 catalog.yaml만으로 충분. 40개 이상부터 FAISS 도입 검토.
### catalog.yaml
- 디자인 팀장의 "메뉴판"
- 각 블록의 id, 시각 설명, 언제 쓰는지(when), 슬롯 목록
- Figma에서 디자인 추출 → 템플릿 변환 → catalog에 등록
- 블록 추가 시 반드시 catalog에도 등록 (미등록 = 팀장이 모름)
### Figma 에셋
Figma에서 추출한 이미지 에셋은 `docs/figma-assets/`에 저장:
- 배경 이미지 (bg_header.png 등)
- 3D 렌더링 이미지 (card_img_design.png 등)
- 시각화 에셋 (mountain_viz.png 등)
- 블록에서 `src="{{ image_path }}"`로 참조
---
## 기술 스택
| 역할 | 도구 | 비고 |
|------|------|------|
| 서버 | FastAPI + uvicorn | 포트 8001 |
| 템플릿 | Jinja2 | 카테고리별 블록 조합 |
| 렌더링 | CSS Grid + 디자인 토큰 | 슬라이드(16:9) + 웹(세로 스크롤) |
| 폰트 | Pretendard Variable | word-break: keep-all |
| AI | Anthropic API (Sonnet) | 5단계 모두 |
| 이미지 크기 | Pillow Image.open().size | 헤더만 읽음 |
| 시각화 | SVG (radialGradient, filter, 수학적 좌표 계산) | 다이어그램/관계도 |
| 실사 배경 | Gemini API | 배경 텍스처 전용 (시각화에는 사용 금지) |
| 자세히보기 | `<details>/<summary>` | HTML 내장 |
| Figma 연동 | Figma REST API + Framelink MCP | 에셋 추출, 디자인 토큰 |
| 변형 검색 | FAISS (향후) | 블록 40개+ 시 도입 |
| 출력 | HTML 다운로드 / .astro (Starlight) | |
---
## 향후 연결 가능성
```
현재: 독립 개발 + 테스트
검증 후 선택지:
A) Kei 본체에 합치기 (대화 안에서 "슬라이드로 정리해줘")
B) 글벗에 붙이기 (문서 자동화 → 시각화 단계)
C) 둘 다
```
---
## 금지 사항
1. Kei Persona Agent 코드를 수정하지 않는다
2. 디자인 판단을 하드코딩하지 않는다 (AI가 사고한다)
3. 전체 페이지를 하나의 고정 템플릿으로 만들지 않는다 (블록 조합 방식)
4. 텍스트를 자르지 않는다 (디자인이 텍스트에 맞춘다)
5. 이미지를 crop하지 않는다 (크기만 조절)
6. 그라데이션, 애니메이션, 다크 테마를 기본으로 사용하지 않는다

View File

@@ -0,0 +1,491 @@
# Phase A: 슬라이드 품질 핵심 — 실행 상세
> "프레임에 내용이 안 보인다"의 직접 원인 해결. 8개 항목.
> 원칙: 하드코딩 금지. 모든 판단은 AI 사고. 회귀 금지.
---
## 실행 순서
```
[독립] A-6 (cover→contain), A-7 (table-layout: fixed)
→ A-8 (container query, A-7 후)
→ A-1 (Sonnet 디자인 조정 — 가장 큰 작업)
→ A-2 (HTML 전달), A-3 (shrink), A-4 (rewrite) — 병렬 가능
→ A-5 (overflow 재검토, A-1 완료 후)
```
---
## A-6: object-fit: cover → contain ✅ 완료
### 현재 상태
- `image-row-2col.html:30``object-fit: cover;`
- `image-grid-2x2.html:31``object-fit: cover;`
- cover는 이미지를 crop → CLAUDE.md "이미지를 crop하지 않는다" 위반
### 작업
두 파일에서 `cover``contain` 변경 (CSS 1줄씩)
### 충돌/회귀
- 충돌: 없음. CSS 속성값만 변경
- 회귀: 없음. CLAUDE.md 원칙 복구
- 부작용: contain은 이미지 주변에 빈 공간(letterbox) 가능 → `background: var(--color-bg-subtle)` 추가로 자연스럽게 처리
### 수정 파일
- `templates/blocks/media/image-row-2col.html`
- `templates/blocks/media/image-grid-2x2.html`
### 구현 결과
- `image-row-2col.html:29~31``object-fit: contain; height: 100%; background: var(--color-bg-subtle, #f8fafc);`
- cover → contain, 높이 하드코딩(354px) → 100%(부모 기준), letterbox 배경색 추가
- `image-grid-2x2.html:29~31` — 동일 패턴 적용 (200px 하드코딩도 함께 제거)
---
## A-7: table-layout: fixed ✅ 완료
### 현재 상태
- `compare-3col-badge.html`에 table-layout 미지정
- 열 너비가 내용 길이에 따라 불안정하게 변동
### 작업
```css
.ct-table {
table-layout: fixed;
width: 100%; /* fixed는 width 필수 */
}
```
### 충돌/회귀
- 충돌: 없음. 기존 테이블 스타일에 속성 추가만
- 회귀: 없음. fixed는 열 너비를 균등하게 고정 — 더 안정적
### 수정 파일
- `templates/blocks/tables/compare-3col-badge.html`
### 구현 결과
- `.block-table-figma table``table-layout: fixed;` 추가 (기존 `width: 100%`는 이미 있었음)
---
## A-8: container query 폰트 스케일링 ✅ 완료
### 현재 상태
- 표 셀 폰트 크기 고정 → 좁은 공간(sidebar 35%)에서 텍스트 잘림/넘침
- @container 규칙 없음
### 작업
```css
.block-compare-table {
container-type: inline-size;
}
@container (max-width: 40rem) {
.ct-cell, .ct-header {
font-size: var(--font-caption); /* 0.8rem */
}
}
@container (max-width: 25rem) {
.ct-cell, .ct-header {
font-size: var(--font-small); /* 0.7rem */
}
}
```
### 하드코딩 점검
- `40rem`, `25rem`은 font-size 기반 상대값 (px 고정이 아님)
- `var(--font-caption)`, `var(--font-small)`은 디자인 토큰 → 하드코딩 아님
### 충돌/회귀
- 충돌: 없음. 신규 CSS 규칙 추가
- 회귀: 없음. @container 미지원 브라우저에서는 무시 → 기존과 동일
- 의존성: A-7 (table-layout: fixed) 먼저 적용해야 열 너비가 안정적
### 수정 파일
- `templates/blocks/tables/compare-3col-badge.html`
### 구현 결과
- `.block-table-figma``container-type: inline-size;` 추가
- `@container (max-width: 40rem)` — 테이블/헤더/셀 폰트 축소 + 패딩 축소
- `@container (max-width: 25rem)` — 추가 축소 + badge 패딩 축소
- **추가:** `tr:hover` 제거 (Phase C-2 선행 처리 — CLAUDE.md "호버 효과 금지")
---
## A-1: 4단계 Sonnet 디자인 조정 ✅ 완료
### 현재 상태
- pipeline.py:73에서 `render_slide(layout_concept)` 직접 호출
- 텍스트 양에 맞는 디자인 조정 과정이 없음 → 텍스트 넘침/빈공간 원인
- CLAUDE.md: "디자인 실무자 (Sonnet + Jinja2 + CSS) — 텍스트에 맞게 폰트/여백/박스 조정"
### API 선택
- **Sonnet** (CLAUDE.md "4단계: Anthropic API (Sonnet)")
- 디자인 실무자는 Kei가 아님 — Sonnet이 맞음
### 핵심 아이디어: CSS 변수 cascade
블록 템플릿 20개가 이미 CSS 변수(`var(--font-body)`, `var(--spacing-inner)` 등)를 187회 사용 중.
area div에서 CSS 변수를 override하면 **템플릿 수정 없이** 모든 블록이 자동 조정됨.
```html
<!-- 예시: Sonnet이 body area의 폰트를 줄이기로 결정 -->
<div class="area-body" style="--font-body: 0.85rem; --spacing-inner: 10px;">
{{ block.html }} <!-- 내부 블록들이 자동으로 작은 폰트/여백 적용 -->
</div>
```
### 파이프라인 흐름 변경
```
기존:
3단계 fill_content → 4단계 render_slide → 5단계 review
변경:
3단계 fill_content → [신규] _adjust_design → 4단계 render_slide → 5단계 review
```
### 신규 함수: _adjust_design()
**위치:** pipeline.py
**입력:** layout_concept (data 채워진 상태)
**처리:**
1. 코드가 각 area별 블록 수, 텍스트 총량(글자 수), zone budget_px를 계산
2. Sonnet에게 전달: area별 정보 + 사용 가능한 CSS 변수 목록
3. Sonnet이 area별 CSS 변수 override를 결정하여 JSON 반환
4. layout_concept에 area_styles 저장
**Sonnet 프롬프트 구성:**
```
당신은 디자인 실무자이다. 편집자가 정리한 텍스트가 각 영역에 잘 들어가도록 CSS를 조정한다.
## 원칙
- 텍스트를 자르지 않는다. 디자인이 텍스트에 맞춘다.
- 빈 공간을 방치하지 않는다.
- 조정 가능한 CSS 변수: --font-body, --font-subtitle, --font-caption, --spacing-inner, --spacing-block, --spacing-small
## 각 영역 현황
- body (예산 490px, 너비 65%): 3개 블록, 총 820자
- quote-question: 120자
- topic-header: 200자
- comparison-table: 500자
- sidebar (예산 490px, 너비 35%): 2개 블록, 총 400자
- card-image: 250자
- card-image: 150자
- footer (예산 60px): 1개 블록, 80자
## 출력 (JSON만)
{"area_styles": {"body": "--font-body: 0.85rem; --spacing-inner: 10px;", "sidebar": "", "footer": ""}}
```
**Sonnet 출력 파싱:**
- `area_styles` dict 추출
- 각 area별 CSS 문자열 → layout_concept 페이지에 저장
**실패 시:** area_styles가 빈 dict → style="" → 기존과 동일하게 렌더링 (안전)
### renderer.py 변경
**render_multi_page() 192~197행:**
기존:
```python
pages_rendered.append({
"grid_areas": page.get("grid_areas", "'main'"),
...
"blocks": blocks_grouped,
"page_number": page_idx + 1,
})
```
변경:
```python
# area_styles를 각 grouped block에 주입
area_styles = page.get("area_styles", {})
for grouped_block in blocks_grouped:
grouped_block["style_override"] = area_styles.get(grouped_block["area"], "")
pages_rendered.append({
"grid_areas": page.get("grid_areas", "'main'"),
...
"blocks": blocks_grouped,
"page_number": page_idx + 1,
})
```
### slide-base.html 변경
**45행:**
기존:
```html
<div class="area-{{ block.area }}">
```
변경:
```html
<div class="area-{{ block.area }}" style="{{ block.style_override | default('') }}">
```
### 하드코딩 점검
- CSS 조정값: Sonnet이 결정 → AI 판단 ✅
- CSS 변수 목록: 프롬프트에 "조정 가능한 변수" 안내 → 가이드일 뿐 강제 아님 ✅
- area별 글자 수: 코드가 계산 → 객관적 수치 ✅
- 하드코딩 없음 ✅
### 충돌/회귀
- pipeline.py: render_slide() 전에 삽입. 기존 흐름 안 건드림 ✅
- renderer.py: blocks_grouped에 style_override 키 추가. 기존 키 영향 없음 ✅
- slide-base.html: style 속성 추가. area_styles 없으면 빈 문자열 → 기존 동일 ✅
- 템플릿 수정: 불필요 (CSS 변수 cascade로 자동 적용) ✅
- 회귀: 없음. 실패 시 기존과 동일 동작 ✅
### 수정 파일
- `src/pipeline.py` — _adjust_design() 신규 함수 + generate_slide()에 호출 추가
- `src/renderer.py` — render_multi_page()에서 area_styles 주입
- `templates/slide-base.html` — area div에 style_override 적용
### 구현 결과
- **pipeline.py** `_adjust_design()` 신규 함수 (약 80행):
- 각 area별 block_count, total_chars, budget_px, width_pct, block_types 자동 집계 (코드)
- Sonnet(디자인 실무자)에게 area별 현황 전달 → CSS 변수 override를 JSON으로 반환받음
- 출력: `page["area_styles"] = {"body": "--font-body: 0.85rem; ...", "sidebar": "", ...}`
- 실패 시: `area_styles = {}` → style="" → 기존과 동일 렌더링 (안전)
- **pipeline.py** `generate_slide()` 72행: `_adjust_design()` 호출 삽입 (render_slide 직전)
- **renderer.py** `render_multi_page()` 192~196행: area_styles를 grouped block의 `style_override`에 주입
- **slide-base.html** 45행: `<div class="area-{{ block.area }}" style="{{ block.style_override | default('') }}">`
- **CSS 변수 cascade 방식:** 블록 템플릿 수정 불필요 — 이미 `var(--font-body)` 등 187회 사용 중이므로 area div에서 override하면 자동 적용
---
## A-2: 5단계 HTML을 프롬프트에 전달 ✅ 완료
### 현재 상태
- pipeline.py:103 `_review_balance(html, ...)` — html 파라미터 있지만 141~146행 프롬프트에서 미사용
- 블록별 데이터 길이만 전달 → 시각적 균형 판단 불가
### 작업
`_review_balance()` 프롬프트에 html 전문 포함
```python
user_prompt = (
f"## 1차 조립 HTML\n{html}\n\n"
f"## 블록별 데이터 양\n" + "\n".join(block_summary) + ...
)
```
### 하드코딩 점검
- html 전문 전달 (임의 잘라내기 없음) ✅
- Sonnet 200K context → 전문 전달 가능 ✅
### 충돌/회귀
- 프롬프트 텍스트만 변경. 파싱/함수 시그니처 동일 ✅
- 회귀: 없음 ✅
### 수정 파일
- `src/pipeline.py``_review_balance()` 프롬프트 부분
### 구현 결과
- `_review_balance()` user_prompt에 `f"## 1차 조립 HTML\n{html}\n\n"` 추가 — html 전문 전달
- 시스템 프롬프트에 "5. HTML 구조: 블록이 영역 안에 잘 배치되었는지" 점검 항목 추가
- 출력 형식에 `target_ratio` 필드 추가 (A-3과 연동)
---
## A-3: 5단계 shrink action + 기존 expand 하드코딩 수정 ✅ 완료
### 현재 상태
- shrink: 조건에 없어서 무시됨
- expand: `char_guide * 1.5` 하드코딩 (pipeline.py:186)
- CLAUDE.md: "모든 판단은 사고로. 하드코딩 없음"
### 작업
**1) 5단계 프롬프트 출력 형식 변경**
기존:
```json
{"adjustments": [{"block_area": "...", "action": "expand|shrink|rewrite", "detail": "..."}]}
```
변경:
```json
{"adjustments": [{"block_area": "...", "action": "expand|shrink|rewrite", "target_ratio": 1.4, "detail": "..."}]}
```
→ Sonnet(디자인 팀장)이 **얼마나** 조정할지를 `target_ratio`로 결정
**2) _apply_adjustments() 코드 변경**
```python
for adj in adjustments:
area = adj.get("block_area", "")
action = adj.get("action", "")
ratio = adj.get("target_ratio")
detail = adj.get("detail", "")
for page in layout_concept.get("pages", []):
for block in page.get("blocks", []):
if block.get("area") == area:
if action == "expand" and ratio:
for key in block.get("char_guide", {}):
block["char_guide"][key] = int(block["char_guide"][key] * ratio)
elif action == "shrink" and ratio:
for key in block.get("char_guide", {}):
block["char_guide"][key] = int(block["char_guide"][key] * ratio)
logger.info(f"조정: {area}{action} ×{ratio} ({detail})")
```
→ ratio가 없으면(Sonnet 누락) 조정 안 함 (무동작이 안전)
→ expand/shrink 모두 Sonnet이 결정한 ratio 사용
### 하드코딩 점검
- ratio: Sonnet이 결정 ✅ (기존 `1.5` 하드코딩 제거)
- ratio 없을 때 기본값: 적용 안 함 (하드코딩 기본값 없음) ✅
### 충돌/회귀
- 기존 expand `* 1.5` 제거 → **기존 하드코딩을 수정하는 것이므로 회귀 아님, 개선임**
- 5단계 프롬프트 출력 형식 변경 → `_parse_json()` 파싱에 영향 없음 (JSON 구조)
- Sonnet이 target_ratio를 안 넣으면 → 조정 안 함 → 기존보다 보수적 (안전)
### 수정 파일
- `src/pipeline.py``_review_balance()` 프롬프트 + `_apply_adjustments()` 코드
### 구현 결과
- `_apply_adjustments()` 전면 재작성:
- `ratio = adj.get("target_ratio")` — Sonnet이 결정한 비율 추출
- `action == "expand" and ratio``char_guide[key] * ratio`
- `action == "shrink" and ratio``char_guide[key] * ratio`
- ratio 없으면(Sonnet 누락) 조정 안 함 → 안전
- 기존 `* 1.5` 하드코딩 완전 제거
- `_review_balance()` 프롬프트에 action별 target_ratio 설명 추가
---
## A-4: 5단계 rewrite action ✅ 완료
### 현재 상태
- rewrite가 expand와 같은 조건(`action in ("expand", "rewrite")`)에 들어가지만
- `if action == "expand"` 안에만 실제 로직 → rewrite는 로그만 찍고 끝 (no-op)
### 작업
A-3에서 변경한 코드에 rewrite 분기 추가:
```python
elif action == "rewrite":
if "data" in block:
del block["data"]
block["reason"] = f"재작성: {detail}"
logger.info(f"조정: {area} → rewrite ({detail})")
```
- data 삭제 → fill_content() 재호출(192행) 시 재매칭
### 하드코딩 점검
- 없음 ✅
### 충돌/회귀
- 기존 no-op → 실제 동작으로 변경 (개선, 회귀 아님)
- fill_content 재호출 시 topic_id 매칭으로 다른 블록도 재편집될 수 있음 → 기존 expand도 동일 동작
- 회귀: 없음 ✅
### 수정 파일
- `src/pipeline.py``_apply_adjustments()` (A-3과 같은 함수)
### 구현 결과
- `_apply_adjustments()``elif action == "rewrite"` 분기 추가
- data 삭제 (`del block["data"]`) → fill_content 재호출 시 topic_id로 재매칭
- `block["reason"]` 업데이트 → 편집자에게 재작성 방향 전달
---
## A-5: overflow 정책 재검토 ✅ 완료
### 현재 상태
- base.css:16 `.slide { overflow: hidden }` — 프레임 경계
- base.css:74 `.slide > div { overflow: hidden }` — BF-8에서 추가한 area별 안전망
### 작업
```css
/* base.css:74 변경 */
.slide > div {
overflow: visible; /* hidden → visible: A-1이 사전 조정하므로 잘림 방지 불필요 */
min-height: 0;
min-width: 0;
}
```
`.slide { overflow: hidden }`(16행)은 **유지** — 프레임 바깥은 잘려야 함
### 하드코딩 점검
- 없음 ✅
### 충돌/회귀
- BF-8에서 추가한 `.slide > div { overflow: hidden }` 제거 → BF-8과 방향 다름
- **그러나 BF-8의 overflow: hidden은 "텍스트를 자르지 않는다" 원칙과 충돌하는 임시 조치였음**
- A-1(Sonnet 디자인 조정)이 넘침을 사전 방지 → 안전망 불필요
- **반드시 A-1 완료 후 적용**
### 수정 파일
- `static/base.css`
### 구현 결과
- base.css `.slide > div``overflow: hidden``overflow: visible`
- 주석 추가: "A-1(Sonnet 디자인 조정)이 텍스트 양에 맞게 CSS를 사전 조정하므로, area 레벨에서는 overflow: visible로 텍스트 잘림을 방지한다."
- `.slide { overflow: hidden }`(16행)은 유지 — 프레임 경계 보호
---
## 수정 파일 총괄
| 파일 | 항목 | 변경 성격 |
|------|------|----------|
| `templates/blocks/media/image-row-2col.html` | A-6 | CSS 1줄 변경 |
| `templates/blocks/media/image-grid-2x2.html` | A-6 | CSS 1줄 변경 |
| `templates/blocks/tables/compare-3col-badge.html` | A-7, A-8 | CSS 추가 |
| `src/pipeline.py` | A-1, A-2, A-3, A-4 | 신규 함수 + 기존 함수 수정 |
| `src/renderer.py` | A-1 | area_styles 주입 (3줄) |
| `templates/slide-base.html` | A-1 | style 속성 추가 (1줄) |
| `static/base.css` | A-5 | overflow 변경 (1줄) |
---
## 검증 체크리스트
- [ ] A-6: image-row, image-grid에서 이미지가 crop 안 됨
- [ ] A-7: 테이블 열 너비가 내용과 무관하게 균등
- [ ] A-8: sidebar(35%)에 표가 들어가면 폰트 자동 축소
- [ ] A-1: Sonnet이 area별 CSS 변수 override 출력 → 렌더링에 반영
- [ ] A-1: _adjust_design 실패 시 기존과 동일하게 렌더링 (안전)
- [ ] A-2: 5단계 Sonnet이 HTML 구조를 보고 균형 판단
- [ ] A-3: shrink 시 Sonnet이 결정한 ratio로 char_guide 축소
- [ ] A-3: 기존 expand 1.5 하드코딩 제거됨
- [ ] A-4: rewrite 시 해당 블록 data 삭제 후 재편집
- [ ] A-5: area div에서 텍스트 잘림 없음
---
## 수정 이력
| 날짜 | 내용 |
|------|------|
| 2026-03-25 | 초안. 하드코딩 제거 반영 (A-2 html 전문, A-3 target_ratio, A-8 상대값). |
| 2026-03-25 | Phase A 전체 구현 완료. 검증 통과. |
## 구현 완료 확인
| 항목 | 검증 결과 |
|------|----------|
| A-1 | `_adjust_design()` 함수 존재, pipeline에서 호출, renderer에서 area_styles 주입, slide-base에서 style_override 적용 |
| A-2 | `_review_balance()` 프롬프트에 html 전문 포함, target_ratio 출력 형식 추가 |
| A-3 | shrink action 구현 + expand 하드코딩(×1.5) 제거 → Sonnet target_ratio 기반 |
| A-4 | rewrite action 구현 — data 삭제 + reason 업데이트 + fill_content 재호출 |
| A-5 | `.slide > div { overflow: visible }` — 프레임(.slide)은 hidden 유지 |
| A-6 | image-row, image-grid: cover → contain + 높이 하드코딩 제거 |
| A-7 | table-layout: fixed + width: 100% |
| A-8 | container-type: inline-size + @container (40rem, 25rem) |
| 추가 | compare-3col-badge tr:hover 제거 (Phase C-2 선행 처리) |

View File

@@ -0,0 +1,368 @@
# Phase B: 누락 기능 구현 — 실행 상세
> 누락된 기능 구현. 실작업 5개 (B-6, B-7은 해결됨).
> 원칙: 하드코딩 금지. 모든 판단은 AI 사고. 회귀 금지.
---
## 실행 순서
```
[독립] B-1 (details 템플릿), B-4+B-5 (이미지/표 판단), B-8 (fallback 교체)
→ B-2 (인쇄 JS, B-1 후)
→ B-3 (catalog 등록, B-1 후)
```
---
## B-1: details-block 템플릿 제작 ✅ 완료
### 현재 상태
- BLOCK_SLOTS에 정의 있음 (design_director.py:47~49): `required: ["summary_text", "detail_content"], optional: ["label"]`
- _apply_defaults에 기본값 있음 (content_editor.py:248): `{"summary_text": "(상세 내용)", "detail_content": ""}`
- **HTML 템플릿 파일이 templates/blocks/ 어디에도 없음** → 렌더링 불가
### API 선택
- AI 호출 없음. HTML/CSS 템플릿만.
### 작업
`templates/blocks/emphasis/details-block.html` 신규 제작
**구조:**
```html
<details class="block-details">
<summary class="dt-summary">
{% if label %}<span class="dt-label">{{ label }}</span>{% endif %}
{{ summary_text }}
</summary>
<div class="dt-content">{{ detail_content }}</div>
</details>
```
### 하드코딩 점검 — CSS 규칙
- 색상: `var(--color-*)` 만 사용. `#직접값` 금지
- 폰트: `var(--font-*)` 사용
- 여백: `var(--spacing-*)` 사용
- 테두리: `var(--border-width)`, `var(--accent-border)`, `var(--radius)` 사용
- **기존 emphasis 블록의 직접값(#1e3a5f 등)을 따라하지 않는다**
### 충돌/회귀
- 충돌: 없음. 신규 파일 추가만
- 회귀: 없음. BLOCK_SLOTS/_apply_defaults와 정합
- 단발성: 아님. `<details>/<summary>`는 HTML 표준 — 브라우저 내장, 의존성 없음
### 수정 파일
- 신규: `templates/blocks/emphasis/details-block.html`
### 구현 결과
- `templates/blocks/emphasis/details-block.html` 신규 제작 완료
- HTML 구조: `<details class="block-details">``<summary class="dt-summary">` (label + summary_text) → `<div class="dt-content">` (detail_content)
- CSS: **`#직접값` 0개** — 전부 디자인 토큰으로 구현
- 배경: `var(--color-bg-subtle)`, 테두리: `var(--color-border)`, 액센트: `var(--color-accent)`
- 폰트: `var(--font-body)`, `var(--font-caption)`, 여백: `var(--spacing-inner)`, `var(--spacing-block)`
- summary 마커: 기본 브라우저 마커 숨기고(`list-style: none`, `::-webkit-details-marker { display: none }`) 커스텀 ▶/▼ 표시
- 좌측 파란 액센트 라인: `border-left: var(--accent-border) solid var(--color-accent)` — quote-left-border와 톤 통일
---
## B-2: 인쇄 시 details 자동 펼침 JS ✅ 완료
### 현재 상태
- slide-base.html의 `@media print`에 page-break만 있음
- details 자동 펼침 JS 없음
- CLAUDE.md: "인쇄 시 JavaScript 6줄로 자동 펼침"
### 작업
slide-base.html `</body>` 직전에 삽입:
```html
<script>
window.onbeforeprint = function() {
document.querySelectorAll('details').forEach(function(d) { d.open = true; });
};
window.onafterprint = function() {
document.querySelectorAll('details').forEach(function(d) { d.open = false; });
};
</script>
```
### 하드코딩 점검
- 없음. DOM API만 사용. 고정값 없음.
### 충돌/회귀
- 충돌: 없음. `{% endfor %}` 이후, Jinja2 루프 밖에 삽입
- 회귀: 없음. 기존 HTML에 `<details>` 없으면 querySelectorAll이 빈 NodeList → 무동작
- 다운로드 HTML: renderer.py의 CSS 인라인 삽입은 `<link>``<style>` 교체만. `<script>`는 그대로 포함됨 ✅
### 수정 파일
- `templates/slide-base.html`
### 의존성
- B-1 완료 후 의미 있음 (details 태그가 있어야 JS가 동작)
### 구현 결과
- slide-base.html `</body>` 직전에 `<script>` 블록 삽입
- `window.onbeforeprint`: 모든 `<details>` 요소에 `open = true` 설정 (인쇄 시 펼침)
- `window.onafterprint`: 모든 `<details>` 요소에 `open = false` 복원 (인쇄 후 접힘)
- `<details>` 태그가 없으면 `querySelectorAll` 빈 NodeList → 무동작 (기존 슬라이드에 영향 없음)
- 다운로드 HTML에도 JS 그대로 포함됨 (renderer의 CSS 인라인 처리는 `<link>``<style>` 교체만)
---
## B-3: catalog에 details-block 등록 ✅ 완료
### 현재 상태
- catalog.yaml에 미등록 → Sonnet(팀장)이 이 블록을 선택할 수 없음
### 작업
catalog.yaml blocks 배열에 추가:
```yaml
- id: details-block
name: 자세히보기 (접기/펼치기)
template: blocks/emphasis/details-block.html
height_cost: "~60px (compact, 접힌 상태 기준. 펼치면 내용에 따라 가변)"
visual: "접힌 요약 1줄 + 클릭하면 상세 내용 펼침. HTML 네이티브 <details> 사용."
when: >
너무 구체적/세부적인 내용을 접어서 보여줄 때.
본문 흐름을 끊지 않으면서 상세 데이터를 제공할 때.
비교표, 상세 스펙 등 detail_target 꼭지.
not_for: >
본문 핵심 내용 (접으면 안 됨).
결론이나 강조 메시지 (항상 보여야 함).
일반 텍스트 (topic-header 또는 card 사용).
slots:
required: [summary_text, detail_content]
optional: [label]
character_limits:
summary_text: 60
detail_content: 500
label: 10
```
### 하드코딩 점검
- height_cost: 접힌 상태의 사실적 높이. AI가 zone 예산 계산에 사용하는 참고값
- character_limits: AI 참고용 가이드. 강제 아님 (CLAUDE.md: "의미 우선")
### 충돌/회귀
- 충돌: 없음. catalog에 항목 추가만
- _load_catalog_map(): id+template만 추출하므로 정상 로드
- 높이 참고표 주석(14행): compact 목록에 details-block 추가 필요
### 수정 파일
- `templates/catalog.yaml`
### 의존성
- B-1 (템플릿이 존재해야 catalog에 등록 의미 있음)
### 구현 결과
- catalog.yaml emphasis 섹션 마지막(divider-text 뒤, media 섹션 전)에 삽입
- id: `details-block`, template: `blocks/emphasis/details-block.html`
- height_cost: `compact` (접힌 상태 기준)
- when: "너무 구체적/세부적인 내용을 접어서 보여줄 때. detail_target 꼭지."
- not_for: "본문 핵심 내용 (접으면 안 됨). 결론 → conclusion-accent-bar."
- slots: `required: [summary_text, detail_content], optional: [label]`
- `_load_catalog_map()` 정상 로드 확인 (총 46개 블록)
---
## B-4 + B-5: 1단계 이미지/표 상세 판단 필드 ✅ 완료
### 현재 상태
- KEI_PROMPT 출력 형식(kei_client.py:44~53행)에 `content_type: "text|image|table|mixed"` 한 줄만
- 이미지 개수/소속/핵심여부/텍스트포함, 표 행/열 규모 등 상세 필드 없음
- CLAUDE.md: "몇 개인지, 어떤 꼭지 소속인지, 핵심/보조인지", "행/열 규모, 전체 표시 가능 여부"
### API 선택
- **Kei API** (1차). KEI_PROMPT가 Kei API로 전달됨 (kei_client.py:96행)
- Sonnet 직접이 아님 ✅
### 작업 — 3곳 동기화 필수
**1) KEI_PROMPT (kei_client.py:20~56행)**
프롬프트 본문에 이미지/표 판단 규칙 보강 + 출력 형식에 필드 추가:
현재 출력 형식:
```json
{"title": "...", "total_pages": 1, "info_structure": "...",
"topics": [{"id": 1, ..., "content_type": "text|image|table|mixed", "detail_target": false, "page": 1}]}
```
변경:
```json
{"title": "...", "total_pages": 1, "info_structure": "...",
"topics": [{"id": 1, ..., "content_type": "text|image|table|mixed", "detail_target": false, "page": 1}],
"images": [{"topic_id": 1, "role": "key|supporting", "has_text": false, "description": "이미지 설명"}],
"tables": [{"topic_id": 2, "rows": 5, "cols": 3, "fits_single_page": true, "description": "표 설명"}]}
```
**2) fallback system_prompt (kei_client.py:168~184행) — 동기화**
현재 문제:
- `role` 필드 누락 → sidebar-right 프리셋 절대 선택 안 됨
- `info_structure` 필드 누락
- `images[]`, `tables[]` 없음
→ KEI_PROMPT와 **동일한 출력 스키마**로 전면 동기화.
이것은 단발성 패치가 아니라, "같은 역할(1단계 실장)의 두 경로가 동일한 출력 구조를 사용해야 한다"는 구조적 원칙.
**3) manual_classify (kei_client.py:225~243행) — 기본값 추가**
```python
return {
...
"images": [],
"tables": [],
}
```
### 하드코딩 점검
- images[]/tables[] 필드: AI가 판단하여 채움. 스키마 정의일 뿐 고정값 아님 ✅
- "key|supporting", "true|false": AI가 선택하는 enum. 하드코딩 아님 ✅
### 하류 영향 분석 (에이전트 검증 완료)
| 모듈 | images[]/tables[] 추가 영향 |
|------|---------------------------|
| pipeline.py | `.get("topics")`, `.get("total_pages")`만 접근. 무시됨 ✅ |
| design_director.py select_preset() | topics의 role/emphasis만 사용. 무시됨 ✅ |
| design_director.py create_layout_concept() | user_prompt에 analysis 텍스트로 포함 → 이점 (Sonnet이 참고) ✅ |
| content_editor.py fill_content() | analysis 미참조 (인자로만 받음). 완전 무관 ✅ |
| pipeline.py _adjust_design() | select_preset()만 호출. 무시됨 ✅ |
### 충돌/회귀
- 충돌: 없음. 기존 필드 변경 없이 필드 추가만
- 회귀: 없음. images[]/tables[]가 없어도 하류 `.get()` 패턴으로 안전
- **기존 결함 수리 포함**: fallback에 role/info_structure 누락 문제도 함께 해결
### 수정 파일
- `src/kei_client.py` — KEI_PROMPT, fallback system_prompt, manual_classify (3곳)
### 구현 결과 — 3곳 동기화
**1) KEI_PROMPT (kei_client.py:38~40행 프롬프트 본문, 46~56행 출력 형식)**
- 프롬프트 본문: "이미지/표가 있으면 그것도 판단해줘" → 구체화
- "이미지가 있으면: 몇 개인지, 어떤 꼭지 소속인지, 핵심인지 보조인지, 텍스트 포함 여부 판단"
- "표가 있으면: 행/열 규모, 1페이지 전체 표시 가능 여부 판단"
- "이미지/표 판단 결과를 images[], tables[] 배열에 기록"
- 출력 형식: topics[] 뒤에 images[], tables[] 배열 추가
- images: `[{"topic_id": 1, "role": "key|supporting", "has_text": false, "description": "..."}]`
- tables: `[{"topic_id": 2, "rows": 5, "cols": 3, "fits_single_page": true, "description": "..."}]`
**2) fallback system_prompt (kei_client.py:172~195행)**
- 기존 누락 필드 전부 추가: `role: "flow|reference"`, `info_structure`, `images: []`, `tables: []`
- 꼭지 추출 규칙에 "참조 정보는 role: 'reference'" 추가
- 출력 스키마가 KEI_PROMPT와 동일한 구조로 동기화
- **기존 결함 수리**: fallback에서도 sidebar-right 프리셋이 선택 가능해짐 (role 필드 존재)
**3) manual_classify (kei_client.py:238~258행)**
- `info_structure: ""` 추가
- topics[0]에 `role: "flow"` 추가
- 최상위에 `images: []`, `tables: []` 추가
---
## B-6, B-7: 해결됨 (작업 불필요)
- B-6: quote-left-border → 등록 안 함 확정 (구 블록 제거 방향)
- B-7: comparison-2col → 등록 안 함 확정 (구 블록 제거 방향)
---
## B-8: fallback_layout에서 card-grid → topic-header 교체 ✅ 완료
### 현재 상태
- `_fallback_layout()` (design_director.py:438행): `"type": "card-grid"`
- card-grid는 BLOCK_SLOTS에서 제거됨 (주석 24행), _apply_defaults에서도 제거됨, catalog에도 없음
- **현재 fallback 경로가 이미 깨져있음** (정합성 분석으로 확인)
### 작업
```python
# 변경 전
"type": "card-grid",
...
"char_guide": {"title": 20, "description": 100},
# 변경 후
"type": "topic-header",
...
# char_guide 제거 — 편집자가 자체 판단 (하드코딩 방지)
```
### topic-header 정합성 (에이전트 검증 완료)
| 체인 | 존재 여부 |
|------|:--------:|
| BLOCK_SLOTS (design_director.py:77~80) | ✅ `required: ["title", "description"]` |
| _apply_defaults (content_editor.py:257) | ✅ `{"title": "(소제목)", "description": ""}` |
| catalog.yaml | ✅ `id: topic-header, template: blocks/headers/topic-left-right.html` |
| 템플릿 파일 | ✅ `templates/blocks/headers/topic-left-right.html` 존재 |
### 하드코딩 점검
- 기존 `char_guide: {"title": 20, "description": 100}`**제거**
- 편집자(3단계)가 가이드 없이 자체 판단 (CLAUDE.md: "글자 수 가이드는 참고. 의미 우선")
- 하드코딩 0개 ✅
### 충돌/회귀
- 충돌: 없음. fallback 블록 타입 변경만
- 회귀: 아님. **깨진 fallback을 수리하는 변경** (card-grid는 이미 체인에서 제거됨)
### 수정 파일
- `src/design_director.py``_fallback_layout()` (438행)
### 구현 결과
- `_fallback_layout()` 436~442행:
- `"type": "card-grid"``"type": "topic-header"` 변경
- `"char_guide": {"title": 20, "description": 100}` **완전 제거** (하드코딩 제거)
- `"size": "medium"` 유지 (AI 판단 참고용)
- topic-header 정합성 검증 통과:
- BLOCK_SLOTS ✅, _apply_defaults ✅, catalog ✅, 템플릿 ✅
- **깨진 fallback 수리 완료**: card-grid는 BLOCK_SLOTS/defaults/catalog 모두에서 제거된 블록이었음 → topic-header로 교체하여 전체 체인 정합
---
## 수정 파일 총괄
| 파일 | 항목 | 변경 성격 |
|------|------|----------|
| 신규 `templates/blocks/emphasis/details-block.html` | B-1 | HTML/CSS 템플릿 제작 |
| `templates/slide-base.html` | B-2 | `<script>` 6줄 추가 |
| `templates/catalog.yaml` | B-3 | details-block 항목 + 높이 참고표 업데이트 |
| `src/kei_client.py` | B-4, B-5 | KEI_PROMPT + fallback + manual_classify (3곳 동기화) |
| `src/design_director.py` | B-8 | _fallback_layout() 블록 타입 교체 + char_guide 제거 |
---
## 검증 체크리스트
- [ ] B-1: details-block.html이 `<details>/<summary>` 사용. CSS에 `var(--*)` 만 사용. `#직접값` 없음
- [ ] B-1: BLOCK_SLOTS 슬롯명(summary_text, detail_content, label)과 템플릿 변수명 일치
- [ ] B-2: 인쇄 시 details 자동 펼침. 화면에서는 접힌 상태 유지
- [ ] B-2: 다운로드 HTML에 `<script>` 포함
- [ ] B-3: catalog에 details-block 등록. _load_catalog_map()에서 정상 로드
- [ ] B-4: KEI_PROMPT에 images[] 스키마 추가. Kei API로 전달
- [ ] B-5: KEI_PROMPT에 tables[] 스키마 추가. Kei API로 전달
- [ ] B-4/B-5: fallback system_prompt에 role, info_structure, images[], tables[] 동기화
- [ ] B-4/B-5: manual_classify에 images:[], tables:[] 빈 배열 추가
- [ ] B-8: _fallback_layout()에서 "topic-header" 사용. char_guide 없음
- [ ] B-8: fallback 경로에서 topic-header 렌더링 정상 동작
---
## 수정 이력
| 날짜 | 내용 |
|------|------|
| 2026-03-25 | 초안. 하드코딩 제거 반영 (B-8 char_guide 제거, B-1 CSS 토큰 강제). fallback 동기화 추가. |
| 2026-03-25 | Phase B 전체 구현 완료. 검증 통과. |
## 구현 완료 확인
| 항목 | 검증 결과 |
|------|----------|
| B-1 | `details-block.html` 존재. `#직접값` 0개 — CSS 변수만 사용. 슬롯명(summary_text, detail_content, label) BLOCK_SLOTS와 정합 |
| B-2 | slide-base.html에 `onbeforeprint`/`onafterprint` JS 포함. `<details>` 없으면 무동작(안전) |
| B-3 | catalog에 `details-block` 등록. `_load_catalog_map()` 정상 로드 (총 46개 블록) |
| B-4 | KEI_PROMPT에 images[] 스키마 + 판단 규칙 추가 |
| B-5 | KEI_PROMPT에 tables[] 스키마 + 판단 규칙 추가 |
| B-4/5 동기화 | fallback system_prompt에 role, info_structure, images[], tables[] 동기화 완료. manual_classify에도 동기화 |
| B-8 | fallback 블록 `topic-header`. char_guide 없음(편집자 자체 판단). BLOCK_SLOTS/defaults/catalog/템플릿 전부 정합 |

View File

@@ -0,0 +1,177 @@
# Phase C: 디자인 원칙 위반 수정 — 실행 상세
> CLAUDE.md 디자인 원칙 위반 사항 수정. 실작업 3개 (C-2는 이미 완료).
> 원칙: 하드코딩 금지. 모든 CSS 값은 디자인 토큰. 회귀 금지.
---
## 실행 순서
```
[독립] C-1 (CLAUDE.md 원칙 완화), C-3 (border-radius), C-4 (box-shadow)
모두 독립 작업. 병렬 가능.
```
---
## C-1: CLAUDE.md 원칙 완화 (gradient 허용 범위 확대)
### 현재 상태
- CLAUDE.md 327행: "HTML/CSS 블록의 배경 그라데이션 금지 (SVG 시각화 블록은 예외)"
- 실제 코드: banner-gradient, quote-question, card-dark-overlay 등에서 linear-gradient 사용
- 사용자 결정 (2026-03-25): banner-gradient의 그라데이션은 디자인의 핵심. 원칙을 완화.
### 작업
CLAUDE.md 327행 문구 변경:
```
현재: "HTML/CSS 블록의 배경 그라데이션 금지 (SVG 시각화 블록은 예외)"
변경: "HTML/CSS 블록의 배경 그라데이션 금지 (SVG 시각화 블록, 디자인 의도가 명확한 블록(배너, 오버레이, 콜아웃 등)은 허용)"
```
### 충돌/회귀
- 코드 변경 없음. 문서만 업데이트.
- 기존 코드(banner-gradient 등)를 원칙에 맞추는 것이므로 회귀 아님.
### 수정 파일
- `CLAUDE.md`
---
## C-2: ~~hover 효과 제거~~ → Phase A-8에서 이미 완료
- compare-3col-badge.html의 `tr:hover` 규칙이 A-8 작업 시 삭제됨
- _legacy/comparison-table.html에만 :hover 남아있으나 _legacy이므로 무관
- **작업 불필요**
---
## C-3: border-radius > 8px → var(--radius) 통일
### 현재 상태
- CLAUDE.md: "둥근 모서리 과다 사용 금지 (border-radius 최대 8px)"
- 디자인 토큰: `--radius: 6px` (tokens.css:38)
- 9개 파일에서 10~25px 사용 중 (위반)
- compare-pill-pair (60px, 55px)는 Phase E-2 SVG 전환 시 해소 → 지금 보류
### 작업
9개 파일의 위반 값을 모두 `var(--radius)` 로 변경. px 직접값 0개.
| 파일 | 셀렉터 | 현재 | 변경 |
|------|--------|------|------|
| compare-3col-badge.html:68 | `.th-badge` | 25px | var(--radius) |
| card-dark-overlay.html:33 | `.cd-card` | 10px | var(--radius) |
| card-text-grid.html:46 | `.card-category` | 12px | var(--radius) |
| card-compare-3col.html:38 | `.cc-card` | 10px | var(--radius) |
| card-stat-number.html:31 | `.st-card` | 10px | var(--radius) |
| quote-question.html:18 | `.block-quote-q` | 12px | var(--radius) |
| quote-big-mark.html:21 | `.block-quote-big` | 10px | var(--radius) |
| callout-warning.html:21 | `.block-callout-warn` | 12px | var(--radius) |
| callout-solution.html:22 | `.block-callout-sol` | 12px | var(--radius) |
### 하드코딩 점검
- 모든 값이 `var(--radius)` → 디자인 토큰 사용 ✅
- px 직접값 0개 ✅
- th-badge도 예외 없이 `var(--radius)` — badge가 덜 둥글지만 원칙 우선
### 충돌/회귀
- 충돌: 없음. CSS 값만 변경. overflow:hidden 있는 .cd-card에서도 radius 줄여도 무관
- 회귀: 없음. CLAUDE.md 원칙 준수 방향
- 디자인 영향: 10~12px → 6px은 시각적 차이 미미. 25px → 6px은 badge가 약간 덜 둥글어짐.
### 수정 파일
- 9개 HTML 파일 (각 1줄씩)
---
## C-4: circle-gradient box-shadow 2레벨 → 1레벨
### 현재 상태
- circle-gradient.html:31: `box-shadow: 0 0 30px rgba(0, 106, 255, 0.25), 0 0 60px rgba(0, 106, 255, 0.1);`
- CLAUDE.md: "그림자(box-shadow) 최소화 (1개 레벨만)"
### 작업
두 번째 shadow(60px, 0.1) 제거:
```css
/* 현재 */
box-shadow: 0 0 30px rgba(0, 106, 255, 0.25), 0 0 60px rgba(0, 106, 255, 0.1);
/* 변경 */
box-shadow: 0 0 30px rgba(0, 106, 255, 0.25);
```
### 하드코딩 점검
- rgba 색상값은 그라데이션 원의 글로우 색상. 디자인 토큰에 글로우용 변수는 없음.
- Phase E-1(SVG 전환) 시 CSS box-shadow가 SVG filter로 대체되면 이 값 자체가 없어짐.
- 현 단계에서는 기존 색상값 유지가 적절.
### 충돌/회귀
- 충돌: 없음. 시각적 변화만 (글로우 범위 축소)
- 회귀: 없음
### 수정 파일
- `templates/blocks/visuals/circle-gradient.html`
---
## 수정 파일 총괄
| 파일 | 항목 | 변경 성격 |
|------|------|----------|
| `CLAUDE.md` | C-1 | 원칙 문구 완화 (1줄) |
| `templates/blocks/tables/compare-3col-badge.html` | C-3 | border-radius 25px → var(--radius) |
| `templates/blocks/cards/card-dark-overlay.html` | C-3 | border-radius 10px → var(--radius) |
| `templates/blocks/cards/card-text-grid.html` | C-3 | border-radius 12px → var(--radius) |
| `templates/blocks/cards/card-compare-3col.html` | C-3 | border-radius 10px → var(--radius) |
| `templates/blocks/cards/card-stat-number.html` | C-3 | border-radius 10px → var(--radius) |
| `templates/blocks/emphasis/quote-question.html` | C-3 | border-radius 12px → var(--radius) |
| `templates/blocks/emphasis/quote-big-mark.html` | C-3 | border-radius 10px → var(--radius) |
| `templates/blocks/emphasis/callout-warning.html` | C-3 | border-radius 12px → var(--radius) |
| `templates/blocks/emphasis/callout-solution.html` | C-3 | border-radius 12px → var(--radius) |
| `templates/blocks/visuals/circle-gradient.html` | C-4 | box-shadow 2레벨 → 1레벨 |
---
## 검증 체크리스트
- [ ] C-1: CLAUDE.md에 gradient 허용 범위 확대 문구 반영
- [ ] C-3: 9개 파일에서 border-radius가 모두 var(--radius) 또는 8px 이하
- [ ] C-3: px 직접값 10px 이상이 templates/blocks/ (비 _legacy)에 없음
- [ ] C-4: circle-gradient.html에 box-shadow가 1개만
---
## 수정 이력
| 날짜 | 내용 |
|------|------|
| 2026-03-25 | 초안. th-badge도 var(--radius) 통일 (px 직접값 0개). |
| 2026-03-25 | Phase C 전체 구현 완료. 검증 통과. |
## 구현 완료 확인
| 항목 | 검증 결과 |
|------|----------|
| C-1 | CLAUDE.md에 "디자인 의도가 명확한 블록은 허용" 문구 반영 |
| C-2 | Phase A-8에서 이미 완료 (작업 없음) |
| C-3 | 9개 파일 border-radius → `var(--radius)` 변경. 전수 검증 결과 위반 0건 (compare-pill-pair만 Phase E-2 대기) |
| C-4 | circle-gradient.html box-shadow 2레벨 → 1레벨. 쉼표 0개 확인 |
### C-1 구현 결과
- CLAUDE.md 327행: "HTML/CSS 블록의 배경 그라데이션 금지" → "디자인 의도가 명확한 블록(배너, 오버레이, 콜아웃 등)은 허용"으로 완화
### C-3 구현 결과 (9개 파일)
- compare-3col-badge.html `.th-badge`: 25px → `var(--radius)`
- card-dark-overlay.html `.cd-card`: 10px → `var(--radius)`
- card-text-grid.html `.card-category`: 12px → `var(--radius)`
- card-compare-3col.html `.cc-card`: 10px → `var(--radius)`
- card-stat-number.html `.st-card`: 10px → `var(--radius)`
- quote-question.html `.block-quote-q`: 12px → `var(--radius)`
- quote-big-mark.html `.block-quote-big`: 10px → `var(--radius)`
- callout-warning.html `.block-callout-warn`: 12px → `var(--radius)`
- callout-solution.html `.block-callout-sol`: 12px → `var(--radius)`
- px 직접값 > 8px: **0건** (compare-pill-pair는 Phase E-2 대기)
### C-4 구현 결과
- circle-gradient.html:31 — `box-shadow: 0 0 30px rgba(0,106,255,0.25), 0 0 60px rgba(0,106,255,0.1)``box-shadow: 0 0 30px rgba(0,106,255,0.25)` (두 번째 제거)

View File

@@ -0,0 +1,354 @@
# Phase D: 이미지 처리 — 실행 상세
> MDX 콘텐츠의 이미지 참조(`![alt](/assets/images/DX1.png)`)를 감지하고,
> 로컬 이미지 파일의 크기를 측정하여 배치 판단에 활용.
> 서버가 localhost에서 돌므로 로컬 파일 접근 가능.
> 원칙: 하드코딩 금지. 모든 판단은 AI 사고. 회귀 금지.
---
## 실행 순서
```
D-0 (이미지 경로 입력 UI + API) ← 선행 필수
→ D-1 (Pillow 유틸리티 + 이미지 추출)
→ D-2 + D-3 (비율 기반 배치 — 프롬프트에 정보 전달)
→ D-4 (텍스트 포함 도표 축소 방지)
→ D-5 (HTML에 이미지 삽입 — base64 또는 절대 경로)
```
---
## D-0: 이미지 경로 입력 UI + API 파라미터 (선행 작업)
### 현재 상태
- static/index.html:176~179 — `fetch('/api/generate', { body: JSON.stringify({ content }) })` → 텍스트만 전송
- src/main.py:35~36 — `SlideRequest` 모델에 `content: str`
- src/pipeline.py:27~29 — `generate_slide(content, manual_layout)` → base_path 없음
### 작업
**1) static/index.html — generate() 함수 수정**
- 텍스트에서 `![...](...)` 패턴 감지 (정규식)
- 발견 시: "이미지가 포함된 콘텐츠입니다. 이미지 파일이 있는 프로젝트 폴더 경로를 입력해주세요" 팝업 (prompt)
- 미발견 시: base_path 없이 기존처럼 전송
- API 요청 body: `{ content, base_path }` (base_path는 선택)
```javascript
// 이미지 참조 감지
const imagePattern = /!\[.*?\]\((.*?)\)/g;
const hasImages = imagePattern.test(content);
let basePath = '';
if (hasImages) {
basePath = prompt(
'이미지가 포함된 콘텐츠입니다.\n' +
'이미지 파일이 있는 프로젝트 폴더 경로를 입력해주세요.\n' +
'예: D:\\ad-hoc\\kei\\content'
) || '';
}
// API 전송
body: JSON.stringify({ content, base_path: basePath })
```
**2) src/main.py — SlideRequest 모델 확장**
```python
class SlideRequest(BaseModel):
content: str
base_path: str = "" # 이미지 기준 폴더 (선택)
```
**3) src/main.py — generate 엔드포인트에서 base_path 전달**
```python
async for event in generate_slide(req.content, base_path=req.base_path):
```
**4) src/pipeline.py — generate_slide() 시그니처 확장**
```python
async def generate_slide(
content: str,
manual_layout: dict[str, Any] | None = None,
base_path: str = "",
) -> AsyncIterator[dict[str, str]]:
```
### 하드코딩 점검
- 없음. 사용자가 경로를 직접 입력. 코드에 경로 고정값 없음 ✅
### 충돌/회귀
- SlideRequest에 base_path 추가: 기본값 `""` → 기존 요청(base_path 없는)과 호환 ✅
- generate_slide() 시그니처에 base_path 추가: 기본값 `""` → 기존 호출과 호환 ✅
- index.html generate() 함수: 이미지 없으면 기존과 동일 동작 ✅
### 수정 파일
- `static/index.html` — generate() 함수
- `src/main.py` — SlideRequest + generate 엔드포인트
- `src/pipeline.py` — generate_slide() 시그니처
---
## D-1: Pillow 이미지 크기 읽기 유틸리티
### 현재 상태
- Pillow import/사용 전무. pyproject.toml에도 없음. src/utils/ 디렉토리 없음.
### 작업
**1) pyproject.toml에 Pillow 추가**
```toml
dependencies = [
...
"Pillow>=10.0",
]
```
**2) src/image_utils.py 신규 제작**
```python
"""이미지 크기 측정 유틸리티."""
from pathlib import Path
from PIL import Image
def get_image_sizes(content: str, base_path: str) -> list[dict]:
"""콘텐츠에서 이미지 참조를 추출하고 로컬 파일 크기를 측정한다.
Args:
content: MDX/텍스트 콘텐츠
base_path: 이미지 파일 기준 폴더 경로
Returns:
[{"path": "/assets/images/DX1.png", "width": 800, "height": 600,
"ratio": 1.33, "orientation": "landscape"}]
"""
import re
if not base_path:
return []
base = Path(base_path)
images = []
for match in re.finditer(r'!\[.*?\]\((.*?)\)', content):
rel_path = match.group(1).strip()
# 상대 경로 해석
abs_path = base / rel_path.lstrip('/')
if abs_path.exists() and abs_path.suffix.lower() in ('.png', '.jpg', '.jpeg', '.gif', '.webp'):
try:
with Image.open(abs_path) as img:
w, h = img.size # 헤더만 읽음
ratio = w / h if h > 0 else 1.0
orientation = "landscape" if ratio > 1.2 else ("portrait" if ratio < 0.8 else "square")
images.append({
"path": rel_path,
"width": w,
"height": h,
"ratio": round(ratio, 2),
"orientation": orientation,
})
except Exception:
images.append({"path": rel_path, "width": 0, "height": 0, "ratio": 0, "orientation": "unknown"})
else:
images.append({"path": rel_path, "width": 0, "height": 0, "ratio": 0, "orientation": "not_found"})
return images
```
### 하드코딩 점검
- ratio 기준 1.2, 0.8: CLAUDE.md 원문 "가로형(ratio > 1.2)", "세로형(ratio < 0.8)" — 문서 기준값이므로 하드코딩 아님 ✅
- 이미지 확장자 목록: 웹 표준 이미지 포맷. 변할 일 없음 ✅
### 충돌/회귀
- 신규 파일 추가만. 기존 코드 변경 없음 ✅
- Pillow는 `Image.open()` 시 헤더만 읽음 (전체 디코딩 안 함) → 성능 안전 ✅
### 수정 파일
- `pyproject.toml` — Pillow 의존성 추가
- 신규 `src/image_utils.py`
---
## D-2 + D-3: 비율 기반 배치 판단
### 현재 상태
- 비율 기반 배치 판단 코드 없음
- B-4에서 1단계 images[] 필드 추가했지만, 실제 크기 정보는 없음
### 작업
pipeline.py에서 D-1 유틸리티 호출 → 이미지 크기/비율 정보를 2단계 Step B와 4단계 Sonnet에 전달
```python
# pipeline.py generate_slide() 내, 2단계 전에:
from src.image_utils import get_image_sizes
image_sizes = get_image_sizes(content, base_path)
if image_sizes:
# analysis에 이미지 크기 정보 추가
analysis["image_sizes"] = image_sizes
```
design_director.py `create_layout_concept()`에서 user_prompt에 이미지 정보 포함:
```python
# 이미지 크기 정보가 있으면 프롬프트에 포함
if analysis.get("image_sizes"):
image_info = "\n".join(
f"- {img['path']}: {img['width']}×{img['height']}px, {img['orientation']}"
for img in analysis["image_sizes"]
)
user_prompt += f"\n\n## 이미지 크기 정보\n{image_info}\n"
```
→ Sonnet(팀장)이 이 정보를 보고 "가로형 → 전체 너비", "세로형 → 텍스트 옆" 등을 판단.
### 하드코딩 점검
- 비율 판단: AI(팀장)가 orientation 정보를 보고 결정 ✅
- 코드는 크기 정보를 전달만. 배치 결정은 AI ✅
### 충돌/회귀
- pipeline.py: 기존 흐름 사이에 이미지 측정 삽입. base_path 없으면 빈 리스트 → 영향 없음 ✅
- design_director.py: user_prompt에 텍스트 추가만. 이미지 없으면 추가 안 함 ✅
- analysis에 image_sizes 추가: 기존 코드는 `.get()` 패턴 → 안전 ✅
### 수정 파일
- `src/pipeline.py` — generate_slide()에서 get_image_sizes() 호출
- `src/design_director.py` — create_layout_concept()에서 이미지 정보 프롬프트 포함
---
## D-4: 텍스트 포함 도표 → 과도한 축소 방지
### 현재 상태
- B-4에서 images[].has_text 필드 추가 (1단계 Kei가 판단)
- 이 정보를 기반으로 "축소하지 마라" 가이드 없음
### 작업
D-2/D-3의 이미지 정보 전달 시, has_text 정보도 함께 포함:
```python
# pipeline.py에서 1단계 analysis의 images[]와 D-1의 image_sizes를 결합
for img_size in image_sizes:
# 1단계에서 판단한 has_text 정보 매칭
for kei_img in analysis.get("images", []):
if kei_img.get("description", "") in img_size.get("path", ""):
img_size["has_text"] = kei_img.get("has_text", False)
```
→ Sonnet 프롬프트에 "has_text: true인 이미지는 텍스트가 포함된 도표이므로 과도하게 축소하지 마라" 가이드
### 하드코딩 점검
- 축소 여부: AI가 판단 ✅
- has_text: 1단계 Kei가 판단 ✅
### 충돌/회귀
- 기존 데이터에 필드 추가만. 없으면 무시됨 ✅
### 수정 파일
- `src/pipeline.py`
---
## D-5: 슬라이드 HTML에 이미지 삽입
### 현재 상태
- 이미지 블록(image-row, image-side-text 등)은 `{{ img.src }}` 슬롯에 경로를 넣지만
- 다운로드 HTML에서 상대 경로는 깨짐 (로컬 파일이므로)
### 작업
렌더링 완료 후, HTML의 이미지 src를 base64 data URI로 변환:
```python
# renderer.py 또는 pipeline.py에서 최종 HTML 후처리
import base64, re
def embed_images(html: str, base_path: str) -> str:
"""HTML의 이미지 src를 base64 data URI로 변환."""
if not base_path:
return html
base = Path(base_path)
def replace_src(match):
src = match.group(1)
abs_path = base / src.lstrip('/')
if abs_path.exists():
mime = 'image/png' if abs_path.suffix == '.png' else 'image/jpeg'
data = base64.b64encode(abs_path.read_bytes()).decode()
return f'src="data:{mime};base64,{data}"'
return match.group(0)
return re.sub(r'src="(/[^"]+\.(?:png|jpg|jpeg|gif|webp))"', replace_src, html)
```
### 하드코딩 점검
- MIME 타입: 확장자 기반 표준 매핑. 하드코딩 아님 ✅
- 이미지 확장자: D-1과 동일 목록 ✅
### 충돌/회귀
- 최종 HTML 후처리. 이미지 없으면 정규식 매칭 없음 → 기존과 동일 ✅
- base_path 없으면 함수 즉시 반환 → 안전 ✅
### 수정 파일
- `src/image_utils.py` (embed_images 함수 추가) 또는 `src/pipeline.py`
---
## 수정 파일 총괄
| 파일 | 항목 | 변경 성격 |
|------|------|----------|
| `static/index.html` | D-0 | generate() 함수에 이미지 감지 + 경로 입력 팝업 |
| `src/main.py` | D-0 | SlideRequest에 base_path 추가 + 엔드포인트 전달 |
| `src/pipeline.py` | D-0, D-2~D-4 | generate_slide() 시그니처 + 이미지 크기 측정 + 프롬프트 전달 |
| `pyproject.toml` | D-1 | Pillow 의존성 추가 |
| 신규 `src/image_utils.py` | D-1, D-5 | get_image_sizes() + embed_images() |
| `src/design_director.py` | D-2, D-3 | user_prompt에 이미지 크기/비율 정보 포함 |
---
## 검증 체크리스트
- [ ] D-0: 이미지 없는 텍스트 → 팝업 안 뜸. 기존과 동일 동작
- [ ] D-0: 이미지 있는 MDX → 팝업 뜨고 경로 입력 → API에 base_path 전달
- [ ] D-0: 팝업에서 취소 → base_path="" → 이미지 처리 스킵 (에러 없음)
- [ ] D-1: Pillow로 이미지 크기 측정. 파일 없으면 width=0, orientation="not_found"
- [ ] D-2/D-3: Sonnet 프롬프트에 이미지 크기/orientation 정보 포함
- [ ] D-4: has_text=true 이미지에 대해 "축소 금지" 가이드 전달
- [ ] D-5: 다운로드 HTML에서 이미지가 base64로 삽입되어 보임
- [ ] 이미지 없는 기존 콘텐츠: 전체 파이프라인 기존과 동일하게 동작 (회귀 없음)
---
## 수정 이력
| 날짜 | 내용 |
|------|------|
| 2026-03-25 | 초안. D-0(선행 UI) 추가. D-5(HTML 이미지 삽입) 추가. 6개 항목. |
| 2026-03-25 | Phase D 전체 구현 완료. 검증 통과. |
## 구현 완료 확인
| 항목 | 검증 결과 |
|------|----------|
| D-0 | SlideRequest에 base_path="" 기본값 추가. generate_slide()에 base_path 파라미터 추가. index.html에 이미지 감지+팝업. 기존 요청(base_path 없는) 호환 ✅ |
| D-1 | pyproject.toml에 Pillow>=10.0 추가. src/image_utils.py 신규 (get_image_sizes + embed_images). Pillow 10.4.0 설치 확인. base_path 없으면 빈 리스트 반환(안전) |
| D-2+D-3 | pipeline.py에서 get_image_sizes() 호출 → analysis["image_sizes"] 저장. design_director.py user_prompt에 이미지 크기/orientation/배치 가이드 포함. 이미지 없으면 추가 안 함(기존 동일) |
| D-4 | design_director.py에서 has_text=true 이미지에 "(텍스트 포함 도표 — 과도한 축소 금지)" 가이드 자동 추가 |
| D-5 | pipeline.py에서 최종 HTML 반환 전 embed_images(html, base_path) 호출. base_path 없으면 원본 그대로 반환(안전) |
### D-0 구현 결과
- `src/main.py`: SlideRequest에 `base_path: str = ""` 추가. generate 엔드포인트에서 `base_path=req.base_path` 전달.
- `src/pipeline.py`: generate_slide() 시그니처에 `base_path: str = ""` 추가.
- `static/index.html`: generate() 함수에서 `![...](...)` 패턴 감지 → prompt()로 경로 입력 → API에 `{ content, base_path }` 전송. 취소 시 `base_path=""` → 이미지 처리 스킵.
### D-1 구현 결과
- `pyproject.toml`: `"Pillow>=10.0"` 의존성 추가.
- `src/image_utils.py` 신규:
- `get_image_sizes(content, base_path)`: MDX에서 `![alt](path)` 추출 → base_path + 상대경로 해석 → Pillow `Image.open().size` → {path, width, height, ratio, orientation} 반환
- `embed_images(html, base_path)`: HTML의 `src="/...png"``src="data:image/png;base64,..."` 변환
- 파일 미발견/에러 시 orientation="not_found"/"error" → 에러 없이 계속 진행
### D-2+D-3+D-4 구현 결과
- `src/pipeline.py`: 1단계 완료 직후 `get_image_sizes()` 호출 → `analysis["image_sizes"]` 저장
- `src/design_director.py`: user_prompt에 이미지 정보 섹션 추가 — 각 이미지의 크기/orientation + "가로형 → 전체 너비", "세로형 → 텍스트 옆", "텍스트 포함 도표 → 축소 금지" 가이드
- has_text 정보는 1단계 Kei의 images[].has_text와 D-1 크기 정보가 결합됨
### D-5 구현 결과
- `src/pipeline.py`: yield result 직전에 `embed_images(html, base_path)` 호출
- base_path 없으면 원본 HTML 그대로 반환 (기존과 동일)

View File

@@ -0,0 +1,309 @@
# Design Agent — 개선 계획
CLAUDE.md 요구사항 전수검토 결과 발견된 미구현/부분구현/위반 사항 33개의 개선 계획.
2026-03-25 기준 코드 감사 결과에 기반.
---
## Phase A: 슬라이드 품질 핵심 (8개)
> "프레임에 내용이 안 보인다"의 직접 원인. 최우선.
> **실행 상세:** [IMPROVEMENT-PHASE-A.md](IMPROVEMENT-PHASE-A.md)
### A-1: 4단계 Sonnet 디자인 조정 추가
- **현재:** Jinja2 렌더링만 수행. 텍스트 길이에 맞는 디자인 조정 없음.
- **CLAUDE.md:** "디자인 실무자 (Sonnet + Jinja2 + CSS) — 편집자가 정리한 텍스트에 맞게 폰트/여백/박스 조정"
- **작업:** pipeline.py 4단계에서 render_slide() 호출 전, Sonnet이 블록별 텍스트 길이를 보고 CSS 조정값(font-size, padding, gap 등)을 결정하는 호출 추가
- **파일:** pipeline.py, 새 함수 또는 renderer.py 확장
- **의존성:** 없음
### A-2: 5단계 HTML을 프롬프트에 전달
- **현재:** `_review_balance(html, ...)` 시그니처에 html 있지만 프롬프트에 미포함. 데이터 길이만 전달.
- **CLAUDE.md:** "1차 조립 결과의 전체 균형 확인"
- **작업:** _review_balance() 프롬프트에 HTML 구조 요약 또는 전문 포함
- **파일:** pipeline.py `_review_balance()`
- **의존성:** 없음
### A-3: 5단계 shrink action 구현
- **현재:** `_apply_adjustments()`에서 `action in ("expand", "rewrite")` 조건만 처리. shrink 무시.
- **작업:** shrink 시 char_guide 값을 0.7배로 축소하는 분기 추가
- **파일:** pipeline.py `_apply_adjustments()`
- **의존성:** 없음
### A-4: 5단계 rewrite action 구현
- **현재:** rewrite가 expand와 같은 조건에 들어가지만 실제 동작 없음 (no-op).
- **작업:** rewrite 시 해당 블록의 data를 초기화하고 fill_content()로 재편집
- **파일:** pipeline.py `_apply_adjustments()`
- **의존성:** 없음
### A-5: overflow:hidden vs "텍스트 자르지 않는다" 원칙 해소
- **현재:** base.css에 `.slide { overflow: hidden }` + `.slide > div { overflow: hidden }`. 텍스트 넘치면 잘림.
- **CLAUDE.md:** "텍스트를 자르지 않는다 (디자인이 텍스트에 맞춘다)"
- **작업:** A-1(Sonnet 디자인 조정)으로 넘침을 사전 방지. `.slide > div`의 overflow를 재검토. 최소한 텍스트 블록은 overflow: visible 또는 auto 허용.
- **파일:** static/base.css
- **의존성:** A-1 완료 후 정책 결정
### A-6: object-fit: cover → contain 수정
- **현재:** image-row-2col.html, image-grid-2x2.html에서 `object-fit: cover` 사용 → 이미지 crop 발생
- **CLAUDE.md:** "이미지를 crop하지 않는다", "object-fit: contain"
- **작업:** cover → contain으로 변경
- **파일:** templates/blocks/media/image-row-2col.html, templates/blocks/media/image-grid-2x2.html
- **의존성:** 없음
### A-7: table-layout: fixed 적용
- **현재:** compare-3col-badge.html에 table-layout 미지정
- **CLAUDE.md:** "table-layout: fixed"
- **작업:** 테이블 CSS에 table-layout: fixed 추가
- **파일:** templates/blocks/tables/compare-3col-badge.html
- **의존성:** 없음
### A-8: container query 폰트 스케일링
- **현재:** 표 셀 폰트 크기 고정
- **CLAUDE.md:** "container query 폰트 스케일링"
- **작업:** @container 규칙으로 표 크기에 따른 폰트 자동 축소
- **파일:** templates/blocks/tables/compare-3col-badge.html
- **의존성:** A-7
---
## Phase B: 누락 기능 구현 (8개)
> **실행 상세:** [IMPROVEMENT-PHASE-B.md](IMPROVEMENT-PHASE-B.md)
### B-1: details-block 템플릿 제작
- **현재:** BLOCK_SLOTS에 정의만 있고 HTML 템플릿 파일 없음. 렌더링 불가.
- **CLAUDE.md:** "HTML 네이티브 `<details>/<summary>` 사용"
- **작업:** `<details>/<summary>` 기반 접기/펼치기 블록 템플릿 제작
- **파일:** 신규 templates/blocks/emphasis/details-block.html
- **의존성:** 없음
### B-2: 인쇄 시 details 자동 펼침 JS
- **현재:** 미구현
- **CLAUDE.md:** "인쇄 시 JavaScript 6줄로 자동 펼침"
- **작업:** slide-base.html에 `window.onbeforeprint` 핸들러 추가
- **파일:** templates/slide-base.html
- **의존성:** B-1
### B-3: catalog에 details-block 등록
- **현재:** catalog.yaml에 미등록 → 팀장이 선택 불가
- **작업:** id, when, not_for, slots, height_cost 정의하여 등록
- **파일:** templates/catalog.yaml
- **의존성:** B-1
### B-4: 1단계 이미지 상세 판단 필드
- **현재:** `content_type: "image"` 한 줄만. 개수/소속/핵심여부/텍스트포함 없음.
- **CLAUDE.md:** "몇 개인지, 어떤 꼭지 소속인지, 핵심/보조인지, 텍스트 포함 이미지인지"
- **작업:** KEI_PROMPT 출력 형식에 images[] 배열 추가 (count, topic_id, role, has_text)
- **파일:** src/kei_client.py KEI_PROMPT
- **의존성:** 없음
### B-5: 1단계 표 상세 판단 필드
- **현재:** `content_type: "table"` 한 줄만. 행/열 규모 없음.
- **CLAUDE.md:** "행/열 규모, 전체 표시 가능 여부"
- **작업:** KEI_PROMPT 출력 형식에 tables[] 배열 추가 (rows, cols, fits_single_page)
- **파일:** src/kei_client.py KEI_PROMPT
- **의존성:** 없음
### B-6: ~~catalog에 quote-left-border 등록 여부~~ → 제외 확정
- **결정 (2026-03-25):** 등록 안 함. 구 블록 제거 방향 유지. 신규 블록(quote-question)만 사용.
- **상태:** 해결됨 (작업 불필요)
### B-7: ~~catalog에 comparison-2col 등록 여부~~ → 제외 확정
- **결정 (2026-03-25):** 등록 안 함. 구 블록 제거 방향 유지. 신규 블록(compare-box, comparison-table)만 사용.
- **상태:** 해결됨 (작업 불필요)
### B-8: fallback_layout에서 card-grid → 신규 블록 교체
- **현재:** `_fallback_layout()`에서 삭제된 `"card-grid"` 타입 사용 (design_director.py:438)
- **작업:** card-image 또는 topic-header 등 신규 블록으로 교체
- **파일:** src/design_director.py `_fallback_layout()`
- **의존성:** 없음
---
## Phase C: 디자인 원칙 위반 수정 (4개)
> **실행 상세:** [IMPROVEMENT-PHASE-C.md](IMPROVEMENT-PHASE-C.md)
### C-1: ~~HTML/CSS 블록 배경 그라데이션 제거~~ → CLAUDE.md 원칙 완화
- **결정 (2026-03-25):** banner-gradient의 그라데이션은 디자인의 핵심. 코드 수정 대신 CLAUDE.md 원칙을 완화.
- **작업:** CLAUDE.md의 "HTML/CSS 블록의 배경 그라데이션 금지" → "디자인 의도가 명확한 블록(배너, 오버레이 등)은 허용" 으로 업데이트
- **파일:** CLAUDE.md
- **상태:** CLAUDE.md 업데이트만 필요
### C-2: hover 효과 제거
- **현재:** compare-3col-badge.html에 `tr:hover` 배경색 변경
- **CLAUDE.md:** "호버 효과 금지"
- **작업:** :hover 규칙 삭제
- **파일:** templates/blocks/tables/compare-3col-badge.html
- **의존성:** 없음
### C-3: border-radius > 8px 수정
- **현재:** quote-question(12px), compare-pill-pair(60px), card-dark-overlay(10px), card-text-grid(12px), compare-3col-badge(25px)
- **CLAUDE.md:** "둥근 모서리 과다 사용 금지 (border-radius 최대 8px)"
- **작업:** 모두 var(--radius)(6px) 또는 최대 8px로 조정
- **파일:** 5개 html 파일
- **의존성:** 없음. 단, compare-pill-pair(60px)는 "pill" 모양이 디자인 의도 — 이 블록은 SVG 전환(E-2) 시 해결될 수 있음
### C-4: circle-gradient box-shadow 2레벨 → 1레벨
- **현재:** 2개 box-shadow (0 0 30px + 0 0 60px)
- **CLAUDE.md:** "그림자 최소화 (1개 레벨만)"
- **작업:** shadow 1개로 축소. 또는 SVG 전환(E-1) 시 filter로 대체
- **파일:** templates/blocks/visuals/circle-gradient.html
- **의존성:** 없음
---
## Phase D: 이미지 처리 — Pillow 도입 (6개)
> **실행 상세:** [IMPROVEMENT-PHASE-D.md](IMPROVEMENT-PHASE-D.md)
> MDX 콘텐츠에 `![alt](/assets/images/DX1.png)` 같은 이미지 참조가 포함됨.
> 이미지 파일은 로컬 디스크에 존재 (MDX 프로젝트 폴더 기준 상대 경로).
> 서버가 localhost에서 돌므로 로컬 파일 접근 가능.
### D-0: 이미지 경로 입력 UI + API 파라미터 (선행 작업)
- **현재:** 프론트엔드에서 텍스트만 전송. 이미지 기준 경로 전달 방법 없음.
- **필요 이유:** 이미지 상대 경로(`/assets/images/DX1.png`)를 절대 경로로 해석하려면 base_path 필요.
- **작업:**
- 프론트엔드(static/index.html): 텍스트에서 `![...](...)` 패턴 감지 → 발견 시 "이미지 폴더 위치" 입력 팝업 표시
- API(src/main.py): `/api/generate` 엔드포인트에 `base_path` 선택 파라미터 추가
- 파이프라인(src/pipeline.py): `generate_slide()``base_path` 전달
- **파일:** static/index.html, src/main.py, src/pipeline.py
- **의존성:** 없음
### D-1: Pillow 이미지 크기 읽기 유틸리티
- **현재:** Pillow import/사용 전무. pyproject.toml에도 없음. src/utils/ 디렉토리 없음.
- **CLAUDE.md:** "Pillow Image.open().size (헤더만 읽음)"
- **작업:**
- pyproject.toml에 `Pillow>=10.0` 추가
- 유틸리티 함수: base_path + 상대 경로 → Pillow로 (width, height) 반환
- 콘텐츠 텍스트에서 `![alt](path)` 패턴 추출 → 각 이미지 크기 측정
- **파일:** pyproject.toml, 신규 src/image_utils.py
- **의존성:** D-0 (base_path 전달 체계)
### D-2: 가로형 이미지(ratio > 1.2) → 전체 너비 배치
- **현재:** 비율 기반 배치 판단 없음
- **작업:** 파이프라인에서 이미지 크기/비율 정보를 2단계 Step B와 4단계 Sonnet에 전달. 팀장/실무자가 배치 판단.
- **파일:** src/pipeline.py 또는 src/design_director.py
- **의존성:** D-1
### D-3: 세로형 이미지(ratio < 0.8) → 텍스트 옆 배치
- **현재:** 미구현
- **작업:** D-2와 함께 구현. 비율 정보를 프롬프트에 포함하면 AI가 배치 판단.
- **파일:** src/design_director.py
- **의존성:** D-1
### D-4: 텍스트 포함 도표 → 과도한 축소 방지
- **현재:** 미구현
- **작업:** B-4에서 추가한 images[].has_text 정보와 D-1의 크기 정보를 결합. has_text=true이면 "이 이미지는 축소하지 마라" 프롬프트 가이드.
- **파일:** src/design_director.py
- **의존성:** D-1, B-4 (이미지 상세 판단)
### D-5: 슬라이드 HTML에 이미지 경로 삽입
- **현재:** 이미지 블록(image-row, image-side-text 등)은 src 슬롯에 URL/경로를 넣지만, 실제 이미지 경로가 연결 안 됨.
- **작업:** 렌더링된 HTML에서 이미지 상대 경로를 절대 경로 또는 data URI(base64)로 변환하여 다운로드 HTML에서도 이미지 표시.
- **파일:** src/renderer.py 또는 src/pipeline.py
- **의존성:** D-0, D-1
---
## Phase E: visuals 블록 SVG 전환 (3개) — Phase 2 이후 진행
> **Phase 2 이후로 연기.**
> 다른 Claude가 Phase 2에서 `svg_calculator.py`(좌표 계산 모듈) + `renderer.py`에 `_preprocess_svg_data()` + `venn-diagram.html` 동적 템플릿 작업 중.
> Phase E는 이 인프라 위에서 나머지 3개 블록을 SVG로 전환하는 작업이므로, Phase 2 완료 후 진행해야 함.
>
> **활용 방식:** `svg_calculator.py`에 각 블록용 좌표 함수 추가 + `_preprocess_svg_data()`에 블록 등록.
>
> **P2-D(shrink/rewrite)는 Phase A(A-3/A-4)에서 이미 구현 완료.** 다른 Claude에게 중복 방지 알림 필요.
### E-1: circle-gradient → SVG
- **현재:** CSS border-radius + linear-gradient
- **작업:** SVG `<circle>` + `<radialGradient>` + `<text>`로 재제작. `svg_calculator.py`에 함수 추가.
- **파일:** templates/blocks/visuals/circle-gradient.html, src/svg_calculator.py
- **의존성:** Phase 2 P2-B 완료
### E-2: compare-pill-pair → SVG
- **현재:** HTML div + CSS border-radius: 60px
- **작업:** SVG `<rect rx="30">` + `<text>`로 재제작. C-3 border-radius 위반도 해소.
- **파일:** templates/blocks/visuals/compare-pill-pair.html
- **의존성:** Phase 2 P2-B 완료
### E-3: process-horizontal → SVG
- **현재:** HTML/CSS flexbox + 가상 화살표
- **작업:** SVG `<circle>` + `<line>` + `<polygon>` + `<text>`로 재제작. `svg_calculator.py`에 함수 추가.
- **파일:** templates/blocks/visuals/process-horizontal.html, src/svg_calculator.py
- **의존성:** Phase 2 P2-B 완료
---
## Phase F: 향후 — Phase 2 이후 (6개)
### F-1: Step A를 AI 선택으로 전환
- **현재:** 규칙 기반 4줄 코드 (CLAUDE.md에는 "규칙 기반"으로 명시)
- **대화에서 요청됨:** AI가 프리셋을 선택하도록 변경
- **작업:** select_preset()을 Sonnet 호출로 전환. CLAUDE.md 업데이트.
- **의존성:** CLAUDE.md 원칙 변경 합의
### F-2: Gemini API 배경 생성
- **현재:** 미구현
- **CLAUDE.md:** "실사 배경: Gemini API (배경 텍스처 전용)"
- **작업:** section-title-with-bg 등의 배경 이미지를 Gemini로 생성
- **의존성:** Gemini API 키
### F-3: FAISS 블록 검색
- **현재:** 미구현 (PLAN.md DA-20)
- **CLAUDE.md:** "변형 40개 이상부터 FAISS 도입 검토"
- **작업:** 블록 HTML 구조/용도 임베딩 → FAISS 인덱스 → 검색
- **의존성:** DA-19 (변형 40개+)
### F-4: venn-diagram N개 자동 배치 (cos/sin)
- **현재:** Phase 1 — 3개 고정 SVG
- **CLAUDE.md:** "Phase 2: N개 자동 배치 (360/N 간격, cos/sin)"
- **작업:** renderer에서 items 개수에 따라 좌표 계산 후 템플릿에 전달
- **의존성:** 없음
### F-5: Figma REST API 연동
- **현재:** 수동 에셋만 (docs/figma-assets/)
- **작업:** Figma API로 에셋 자동 추출
- **의존성:** Figma API 키
### F-6: .astro (Starlight) 출력
- **현재:** HTML 다운로드만
- **작업:** HTML → .astro 변환 출력 옵션
- **의존성:** Starlight 연동 설계
---
## Phase별 의존 관계
```
Phase A (슬라이드 품질)
├── A-1~A-4: 독립 작업 가능
├── A-5: A-1 완료 후
├── A-6, A-7: 독립
└── A-8: A-7 완료 후
Phase B (누락 기능)
├── B-1~B-5, B-8: 독립
├── B-2, B-3: B-1 완료 후
└── B-6, B-7: 사용자 결정 대기
Phase C (디자인 원칙) → 독립. A/B와 병렬 가능.
└── C-1: 사용자 결정 대기 (원칙 변경 vs 코드 수정)
Phase D (Pillow) → D-1 선행, 나머지 순차
└── D-4: B-4 완료 후
Phase E (SVG 전환) → 독립. A/B/C와 병렬 가능.
Phase F (향후) → Phase A~E 완료 후.
└── F-1: CLAUDE.md 합의 후
```
---
## 수정 이력
| 날짜 | 내용 |
|------|------|
| 2026-03-25 | 초안 작성. CLAUDE.md 전수검토 기반 33개 항목 도출. |

373
04. design_agent/PLAN.md Normal file
View File

@@ -0,0 +1,373 @@
# Design Agent — 실행 계획
## Phase 1: 기반 구축
### DA-1: 프로젝트 셋업
- **파일:** pyproject.toml, .env, .gitignore
- **내용:** Python 환경, 의존성 정의, 환경 변수
- **의존성:** 없음
- **완료 기준:** `pip install -e .` 성공
### DA-2: FastAPI 서버 기본 구조
- **파일:** src/main.py, src/config.py
- **내용:** FastAPI 앱, CORS, health endpoint, 설정 관리
- **의존성:** DA-1
- **완료 기준:** `uvicorn src.main:app --reload` 정상 시작, `/api/health` 200 반환
### DA-3: 디자인 토큰 + 기본 CSS
- **파일:** static/tokens.css, static/base.css
- **내용:** CLAUDE.md에 정의된 디자인 토큰을 CSS 변수로 구현, Pretendard 폰트 설정, 16:9 슬라이드 컨테이너
- **의존성:** 없음
- **완료 기준:** 빈 슬라이드가 16:9 비율로 렌더링, Pretendard 폰트 적용 확인
---
## Phase 2: 블록 템플릿 제작
### DA-4: 블록 템플릿 — 비교 (comparison)
- **파일:** templates/blocks/comparison.html
- **내용:** 2단 병렬 레이아웃, Jinja2 슬롯 ({{left_title}}, {{left_content}}, {{right_title}}, {{right_content}})
- **의존성:** DA-3
- **완료 기준:** 더미 데이터로 렌더링 시 2단 비교 표시, 디자인 토큰 적용
### DA-5: 블록 템플릿 — 카드 그리드 (card-grid)
- **파일:** templates/blocks/card-grid.html
- **내용:** 2~4열 카드 배열, Jinja2 슬롯 ({{cards[n].icon}}, {{cards[n].title}}, {{cards[n].description}})
- **의존성:** DA-3
- **완료 기준:** 3개 카드 렌더링, 카드 수에 따라 자동 배열
### DA-6: 블록 템플릿 — 관계도 (relationship)
- **파일:** templates/blocks/relationship.html
- **내용:** 벤 다이어그램 (CSS 원형), Jinja2 슬롯 ({{center}}, {{items[n]}})
- **의존성:** DA-3
- **완료 기준:** 3원 벤 다이어그램 렌더링, 라벨 표시
### DA-7: 블록 템플릿 — 프로세스 (process)
- **파일:** templates/blocks/process.html
- **내용:** 가로 단계 흐름, Jinja2 슬롯 ({{steps[n].number}}, {{steps[n].title}}, {{steps[n].description}})
- **의존성:** DA-3
- **완료 기준:** 4단계 프로세스 렌더링, 연결선 표시
### DA-8: 블록 템플릿 — 강조 인용 (quote-block)
- **파일:** templates/blocks/quote-block.html
- **내용:** 배경색 + 좌측 라인 + 인용 텍스트, Jinja2 슬롯 ({{quote_text}}, {{source}})
- **의존성:** DA-3
- **완료 기준:** 인용 블록 렌더링, 강조 스타일 적용
### DA-9: 블록 템플릿 — 결론 바 (conclusion-bar)
- **파일:** templates/blocks/conclusion-bar.html
- **내용:** 하단 전체 폭 강조 영역, Jinja2 슬롯 ({{conclusion_text}})
- **의존성:** DA-3
- **완료 기준:** 결론 바 렌더링, 강조 색상 적용
### DA-10: 블록 템플릿 — 비교 테이블 (comparison-table)
- **파일:** templates/blocks/comparison-table.html
- **내용:** 다항목 비교 테이블, Jinja2 슬롯 ({{headers}}, {{rows}})
- **의존성:** DA-3
- **완료 기준:** 5행 3열 테이블 렌더링
### DA-11: 슬라이드 조합 렌더러
- **파일:** src/renderer.py, templates/slide-base.html
- **내용:** Jinja2로 블록 조합 → HTML 생성. grid-template-areas로 블록 배치. 다중 페이지 지원.
- **다중 페이지:** `.slide` div 여러 개 + `page-break-after: always` (인쇄 시 페이지 분리)
- **의존성:** DA-4 ~ DA-10
- **완료 기준:** JSON 블록 배치 명세 → 완성 HTML 출력 (1페이지 또는 다중 페이지)
---
## Phase 3: AI 파이프라인 연결
### DA-12: 1단계 — Kei 실장 (꼭지 추출 + 분석)
- **파일:** src/kei_client.py
- **내용:** 본문에서 핵심 꼭지 추출 + 다단계 분석
- 꼭지 추출: 본문에서 2~5개 핵심 파트 식별 (1페이지 적정: 5개)
- 페이지 분리: 5개 초과 + 레이어 동등 → 2페이지 (의미 기반 분할 2/3, 3/4 등)
- 5개 + 내용 많음 → 세부 내용은 자세히보기 대상
- 레이어 수준: 각 꼭지가 도입/핵심/보조/결론 중 어디인지
- 강조 판단: 어떤 꼭지를 시각적으로 눈에 띄게 할 것인가
- 배치 방향: 세로로 긴 꼭지, 가로로 나열할 꼭지 판단
- 이미지 판단: 몇 개, 어떤 꼭지 소속, 핵심/보조, 텍스트 포함 여부
- 표 판단: 행/열 규모, 전체 표시 가능 여부
- 상세 콘텐츠 판단: 자세히보기 대상 식별
- **기술:** Anthropic API (Sonnet)
- **의존성:** DA-2
- **완료 기준:** 꼭지 목록 + 레이어 + 강조 + 배치 + 이미지/표/상세 판단 JSON
### DA-13a: 2단계 Step A — 레이아웃 프리셋 선택 (규칙 기반)
- **파일:** src/design_director.py (`select_preset()` 함수)
- **내용:** 실장의 role 분석을 보고 레이아웃 프리셋을 자동 선택
- reference 꼭지 있음 → `sidebar-right` (65:35)
- 모든 flow가 대등 비교 → `two-column` (50:50)
- 고강조 1개 + 나머지 보조 → `hero-detail`
- 나머지 → `single-column`
- **기술:** Python 규칙 코드 (LLM 불필요)
- **의존성:** DA-12
- **완료 기준:** 프리셋 이름 + CSS grid 반환
### DA-13b: 2단계 Step B — 프리셋 내 블록 매핑 (Sonnet)
- **파일:** src/design_director.py
- **내용:** 선택된 프리셋의 zone에 꼭지를 배정 + 블록 타입 선택 + 글자 수 가이드
- 프롬프트에 선택된 프리셋의 CSS grid가 포함됨
- flow 꼭지 → body/main zone
- reference 꼭지 → sidebar zone
- detail_target → popup 연결
- catalog에서 블록 타입 선택
- 이미지/표 배치 판단
- **기술:** Anthropic API (Sonnet) + Pillow
- **의존성:** DA-13a
- **완료 기준:** zone별 블록 배정 + 글자 수 가이드 JSON
### DA-13c: 3단계 — Kei 텍스트 편집자 (텍스트 정리)
- **파일:** src/content_editor.py
- **내용:** 팀장의 글자 수 가이드 참고하되 내용 의미 우선
- 전체 컨텍스트와 핵심 용어 유지
- 세련된 표현으로 편집 (의미 > 글자 수)
- 출처 보존, 개조식, 날조 금지
- 표 내용 편집 (핵심 행/열 선택, 요약)
- 자세히보기 대상: 요약 버전 + 상세 버전 둘 다 작성
- **기술:** Anthropic API (Sonnet)
- **의존성:** DA-13b
- **완료 기준:** 슬롯별 텍스트 JSON. 핵심 용어 보존. 자세히보기 포함.
### DA-14: 4단계 — 디자인 실무자 (디자인 조정 + HTML 조립) + 5단계 재검토
- **파일:** src/pipeline.py, src/renderer.py
- **내용:**
- 4단계 (AI + 코드): 편집자 텍스트에 맞게 디자인 조정 (폰트/여백/박스 — 텍스트 자르지 않음)
+ Jinja2 HTML 조립, 이미지 object-fit:contain, 표 container query, `<details>` 접기, 인쇄 펼침 JS
- 5단계 (AI): 팀장이 전체 균형 재검토 → 채움 비율, 블록 균형, 이미지/표 크기 점검 → 2차 조정
- **기술:** Anthropic API Sonnet (디자인 조정 + 재검토) + Jinja2/CSS (조립)
- **의존성:** DA-11, DA-12, DA-13a, DA-13b, DA-13c
- **완료 기준:** 텍스트 입력 → 균형 잡힌 슬라이드 HTML (이미지/표/자세히보기 포함, 재검토 완료)
---
## Phase 4: UI + 출력
### DA-15: 프론트엔드 — 콘텐츠 입력 + 미리보기
- **파일:** static/index.html (별도 HTML 파일), main.py (FileResponse로 서빙)
- **내용:** 텍스트 입력 영역 + iframe 미리보기 + HTML 다운로드 버튼
- **기술:** FileResponse (FastAPI 내장), fetch API + 수동 SSE 파싱
- **의존성:** DA-14
- **완료 기준:** 텍스트 붙여넣기 → 슬라이드 미리보기 표시 + HTML 다운로드
- **주의:** HTML/JS를 Python 문자열에 넣지 않는다 (이스케이프 충돌 방지)
---
## 버그 수정
### BF-2: 블록 내용 비어있음 (렌더러 Jinja2 include 문제)
- **파일:** src/renderer.py, templates/slide-base.html
- **내용:** `include` → 블록별 개별 `render()` 후 HTML 삽입
- **기술:** Jinja2 `get_template().render()` (내장)
- **의존성:** 없음 (기존 코드 수정만)
- **완료 기준:** 콘텐츠 입력 → 슬라이드에 텍스트가 표시됨
### BF-3: 한글 깨짐 (다운로드 파일)
- **파일:** static/index.html
- **내용:** download() Blob에 UTF-8 BOM 추가
- **기술:** JavaScript `'\uFEFF'` 1줄
- **의존성:** 없음
- **완료 기준:** 다운로드한 HTML 파일에서 한글 정상 표시
### BF-4: body 블록 겹침 (같은 area에 여러 div)
- **파일:** src/renderer.py
- **내용:** 같은 area의 블록을 하나의 div로 그룹핑 (OrderedDict + flex-column)
- **기술:** Python OrderedDict (내장)
- **의존성:** BF-5 (area명 통일 선행)
- **완료 기준:** body에 4개 블록이 세로로 쌓여서 보임 (겹침 없음)
- **상태:** 코드 수정 완료, md 반영 중
### BF-5: 제목 안 보임 (area명 불일치)
- **파일:** src/design_director.py (LAYOUT_PRESETS)
- **내용:** 프리셋 4개의 area명 `title``header`로 통일 (slide-base.html이 `header` 사용)
- **기술:** 문자열 교체
- **의존성:** 없음
- **완료 기준:** 슬라이드에 제목이 표시됨
- **상태:** sidebar-right 프리셋 수정 완료, 나머지 3개 확인 필요
### BF-6: sidebar 카드 3열 찢어짐
- **파일:** src/design_director.py (STEP_B_PROMPT)
- **내용:** 팀장 Step B 프롬프트에 "sidebar zone은 전체 너비의 35%로 좁다. card-grid 배치 시 카드 열 수를 공간에 맞게 판단하라" 추가
- **기술:** 프롬프트 엔지니어링
- **의존성:** 없음
- **완료 기준:** sidebar의 카드가 1열 세로로 배치됨
- **상태:** 미수정
### BF-7: body 블록 텍스트 비어있음 (content_editor 매칭 오류)
- **파일:** src/content_editor.py
- **내용:** 같은 area에 여러 블록이 있을 때 첫 번째만 매칭되는 문제. `break` 제거 + area+topic_id로 정확 매칭. 편집자 프롬프트 출력 형식에 `topic_id` 추가.
- **기술:** Python 조건문 수정
- **의존성:** 없음
- **완료 기준:** body의 모든 블록에 텍스트가 채워짐
- **상태:** 미수정
### BF-8: 컨테이너 예산 기반 블록 배치 (프레임 넘침 방지)
- **파일:** src/design_director.py, templates/catalog.yaml, static/base.css, templates/blocks/visuals/*.html
- **내용:**
- 근본 원인: 팀장이 콘텐츠 중심으로 블록 선택 → 높이 고려 없이 body에 4개 블록 쌓음 → 720px 초과 → overflow:hidden으로 잘림
- 해결: 컨테이너(zone) 크기 예산을 먼저 확인하고, 예산 안에서 블록을 선택하는 사고 순서로 전환
- LAYOUT_PRESETS: zone별 budget_px + width_pct 추가
- STEP_B_PROMPT: 4단계 사고 순서 (컨테이너 확인→배정→블록 선택+높이 계산→검증)
- catalog.yaml: 블록별 height_cost 추가 (compact ~70px / medium ~150px / large ~200px / xlarge ~400px)
- base.css: area 컨테이너에 overflow:hidden + min-height:0 안전망
- 시각화 블록 CSS: flex-shrink + responsive SVG
- **기술:** 프롬프트 엔지니어링 + CSS 안전망
- **의존성:** 없음
- **완료 기준:** body 영역 블록이 720px 프레임 안에 모두 보임. 넘침 없음.
- **한계:** 프롬프트만으로는 Sonnet이 grid를 무시하는 문제를 방지할 수 없음 → BF-9 필요
### BF-9: grid와 Sonnet의 역할 분리 (설계 수정)
- **파일:** src/design_director.py
- **내용:**
- 설계 오류: Step B에서 Sonnet에게 grid 값을 출력하라고 요구 → Sonnet이 자기만의 grid를 생성해버림
- 근본 원인: grid는 코드(Step A)가 결정하는 것인데 Sonnet에게 에코를 요구한 것 자체가 잘못
- 해결:
- Step B 프롬프트에서 grid 출력 요구 제거. Sonnet은 `blocks` 배열만 출력
- `create_layout_concept()`에서 grid 값은 프리셋에서 직접 가져옴 (Sonnet 출력 무시)
- Sonnet이 출력한 area명이 프리셋 zone에 없으면 가장 가까운 zone으로 코드 매핑
- 원칙: **코드가 결정한 것은 코드가 유지한다. Sonnet은 콘텐츠 판단만.**
- **기술:** Python 코드 수정 + 프롬프트 출력 형식 변경
- **의존성:** BF-8 (프리셋에 zone 정보 필요)
- **완료 기준:** Sonnet 출력에 grid 값이 없음. 최종 HTML의 grid는 항상 프리셋과 100% 일치.
### BF-10: _CATALOG_MAP 캐시 갱신 문제
- **파일:** src/renderer.py
- **내용:**
- 현상: catalog.yaml을 수정해도 서버 재시작 전까지 구 매핑 유지 → _legacy 블록 로드
- 근본 원인: _CATALOG_MAP이 모듈 레벨 global로 한 번만 로드됨
- 해결: reload 옵션 (--reload 시 자동 갱신) 또는 매 요청마다 파일 mtime 확인 후 reload
- **기술:** Python pathlib stat() (내장)
- **의존성:** 없음
- **완료 기준:** catalog.yaml 수정 → 서버 재시작 없이 새 매핑 적용
### DA-16: 통합 테스트
- **파일:** tests/test_pipeline.py, tests/test_renderer.py
- **내용:** 전체 파이프라인 테스트 + 블록 렌더링 테스트
- **의존성:** BF-2, BF-3, BF-4, BF-5, BF-6, BF-7
- **완료 기준:** 테스트 전체 통과
---
---
## Phase 5: 블록 라이브러리 확장
### DA-17: Figma 에셋 추출 + 블록 템플릿 제작
- **상태:** done
- **산출물:**
- Figma 스크린샷 16장 (`docs/figma-screenshots/`)
- Figma 에셋 15개+ (`docs/figma-assets/`)
- 신규 블록 템플릿 6종 (`templates/blocks/` 카테고리별)
- 디자인 분석 보고서 (`docs/figma-analysis/DESIGN-ANALYSIS.md`)
- 블록 인덱스 (`templates/blocks/INDEX.md`)
- **완료 기준:** 신규 블록 6개 독립 렌더링 테스트 통과, Figma 톤 반영
### DA-18: 블록 라이브러리 카테고리 재편
- **상태:** done
- **내용:** 플랫 구조 → 6개 카테고리 폴더 (headers, cards, tables, visuals, emphasis, media)
- **완료 기준:** INDEX.md에 전체 구조 정리, 기존 루트 파일 정리
### DA-19: 변형 확장
- **상태:** done (46개 달성)
- **산출물:** 46개 블록 (6 카테고리), catalog.yaml 46개, BLOCK_SLOTS 46개 동기화
- **완료 기준:** ✅ 달성
---
## Phase 2: 파이프라인 고도화 (상세: docs/PHASE2-PLAN.md, PHASE2-PROCESS.md)
### P2-A: FAISS 블록 검색 인덱스
- **파일:** `src/block_search.py` (신규), `scripts/build_block_index.py` (신규), `src/design_director.py` (수정)
- **내용:** catalog 전문 → FAISS 검색으로 관련 블록 5~8개만 프롬프트에 전달
- **기술:** BAAI/bge-m3 + faiss-cpu (Kei persona와 동일 패턴)
- **의존성:** 없음
- **완료 기준:** 꼭지별 검색 → 카테고리별 최소 1개 보장 → 프롬프트 토큰 절약
### P2-B: SVG N개 자동 배치
- **파일:** `src/svg_calculator.py` (신규), `src/renderer.py` (수정), `templates/blocks/visuals/venn-diagram.html` (수정)
- **내용:** venn-diagram 3개 고정 → N개(2~7) cos/sin 자동 계산
- **기술:** Python math (내장, 추가 의존성 없음)
- **의존성:** 없음 (2-A와 병렬 가능)
- **완료 기준:** N=2~7 각각 렌더링 테스트 통과. Phase 1 fallback 유지.
### P2-C: Step A Opus+FAISS 고도화
- **파일:** `src/design_director.py` (수정)
- **내용:** 규칙 4줄 → Opus(Kei API)가 FAISS 후보에서 블록 선정 + 배치 결정
- **기술:** Kei API 호출 (Opus). anthropic 직접 호출 **절대 금지**.
- **의존성:** P2-A 완료 필수
- **완료 기준:** Opus가 콘텐츠 보고 블록 후보 선정. Kei API 실패 시 기존 방식 fallback.
### P2-D: 5단계 재검토 강화
- **파일:** `src/pipeline.py` (수정)
- **내용:** shrink/rewrite action 구현 + 재조정 루프 (MAX=2)
- **기술:** Anthropic API (Sonnet) — 기존 패턴
- **의존성:** 없음 (병렬 가능)
- **완료 기준:** expand/shrink/rewrite 3개 action 모두 동작. 루프 2회 제한.
### P2-E: 누락 기능
- **E-1 Pillow:** `src/design_director.py` — 이미지 크기 읽기 → 블록 선택 참고
- **E-2 details-block:** `src/design_director.py` + `src/pipeline.py` — detail_target → details-block 배치
- **의존성:** 없음 (병렬 가능)
- **완료 기준:** 이미지 크기 정보가 팀장에게 전달. details 접기/펼치기 동작.
### 의존 관계
```
P2-A (FAISS) ──────────────────────┐
├→ P2-C (Opus+FAISS)
P2-B (SVG N개) ── 병렬 │
P2-D (재검토) ─── 병렬 │
P2-E (누락기능) ── 병렬 │
```
### DA-21: renderer 카테고리 경로 지원
- **상태:** todo
- **내용:** `renderer.py`의 블록 로드 경로를 `blocks/{type}.html``blocks/{category}/{name}.html`로 변경
- **주의:** 기존 파이프라인과 호환성 유지
- **의존성:** DA-18
- **완료 기준:** 카테고리 경로로 블록 렌더링 정상 동작
### DA-22: catalog.yaml 경로 업데이트
- **상태:** todo
- **내용:** template 경로를 새 폴더 구조로 반영
- **의존성:** DA-21
- **완료 기준:** catalog.yaml의 모든 template 경로가 실제 파일과 일치
---
## 의존 관계
```
Phase 1~2 (기반+블록):
DA-1 → DA-2, DA-3 → DA-4~DA-10 → DA-11(렌더러)
Phase 3 (AI 파이프라인):
DA-12(실장) → DA-13(팀장) → DA-13b(편집자) → DA-14(조립+재검토)
Phase 4 (UI):
DA-14 → DA-15(프론트엔드) → DA-16(테스트)
Phase 5 (블록 라이브러리):
DA-17(Figma추출) → DA-18(카테고리재편) → DA-19(변형확장) → DA-20(FAISS)
DA-18 → DA-21(renderer경로) → DA-22(catalog경로)
```
- Phase 1~2, 5: AI 없이 진행 가능
- Phase 3: Anthropic API 필요 (5단계 파이프라인)
- Phase 5는 Phase 3와 **병렬** 진행 가능
---
## 기술 스택
| 역할 | 도구 | 비고 |
|------|------|------|
| 서버 | FastAPI + uvicorn | 포트 8001 |
| 템플릿 엔진 | Jinja2 | 카테고리별 블록 조합 |
| 렌더링 | CSS Grid + 디자인 토큰 | 슬라이드 + 웹 |
| 한국어 폰트 | Pretendard Variable | word-break: keep-all |
| AI (5단계) | Anthropic API (Sonnet) | 실장→팀장→편집자→실무자→재검토 |
| 이미지 생성 | Gemini API | 복합 시각화 배경 (레이어 방식) |
| Figma | Figma REST API + Framelink MCP | 에셋 추출 |
| 변형 검색 | FAISS (향후) | 블록 40개+ 시 |
| 테스트 | pytest | 렌더링 + 파이프라인 |

View File

@@ -0,0 +1,237 @@
# Design Agent — 진행 상황
## 현재 상태 요약
| 상태 | 개수 |
|------|------|
| done | 23 |
| in-progress | 0 |
| todo | 0 |
| bug-fix | 7 (BF-4~10) |
| blocked | 0 |
| **전체** | **30** |
**Phase 2 완료 (2026-03-25):** P2-A~E 전체 done.
---
## Phase 1: 기반 구축
| 태스크 | 상태 | 담당 | 시작 | 완료 | 메모 |
|--------|------|------|------|------|------|
| DA-1: 프로젝트 셋업 | done | - | - | - | pyproject.toml, .env |
| DA-2: FastAPI 서버 | done | - | - | - | DA-1 이후 |
| DA-3: 디자인 토큰 + 기본 CSS | done | - | - | - | 독립 작업 가능 |
## Phase 2: 블록 템플릿 제작
| 태스크 | 상태 | 담당 | 시작 | 완료 | 메모 |
|--------|------|------|------|------|------|
| DA-4: 비교 블록 | done | - | - | - | DA-3 이후 |
| DA-5: 카드 그리드 | done | - | - | - | DA-3 이후 |
| DA-6: 관계도 | done | - | - | - | DA-3 이후 |
| DA-7: 프로세스 | done | - | - | - | DA-3 이후 |
| DA-8: 강조 인용 | done | - | - | - | DA-3 이후 |
| DA-9: 결론 바 | done | - | - | - | DA-3 이후 |
| DA-10: 비교 테이블 | done | - | - | - | DA-3 이후 |
| DA-11: 슬라이드 조합 렌더러 | done | - | - | - | DA-4~10 이후 |
## Phase 3: AI 파이프라인 연결
| 태스크 | 상태 | 담당 | 시작 | 완료 | 메모 |
|--------|------|------|------|------|------|
| DA-12: 1단계 Kei 실장 (꼭지+정보구조+role) | done | - | - | - | Kei API 연동. info_structure + role(flow/reference) |
| DA-13a: 2단계A 프리셋 선택 (규칙 기반) | todo | - | - | - | reference→sidebar-right, 비교→two-column 등 자동 |
| DA-13b: 2단계B 블록 매핑 (Sonnet) | todo | - | - | - | 프리셋 CSS 포함 프롬프트. zone별 블록 배정 |
| DA-13c: 3단계 텍스트 편집자 (Kei 역할) | todo | - | - | - | 의미 우선 편집 + 표 편집 + 자세히보기(요약+상세) |
| DA-14: 4단계 실무자 + 5단계 재검토 | todo | - | - | - | 디자인 조정 + HTML 조립 + 팀장 균형 재검토 |
## Phase 4: UI + 출력
| 태스크 | 상태 | 담당 | 시작 | 완료 | 메모 |
|--------|------|------|------|------|------|
| DA-15: 프론트엔드 | done | - | - | - | DA-14 이후. HTML 다운로드만 |
| DA-16: 통합 테스트 | done | - | - | - | DA-15 이후 |
---
## 버그 수정 이력
### BF-1: 프론트엔드 SSE 파싱 실패 [발견: DA-15 이후]
- **현상:** 서버는 정상 응답하지만 브라우저에서 결과 미표시. "시작 중..." 고정.
- **원인:** main.py Python 문자열 안에 HTML/JS를 넣어서 `\n`이 실제 줄바꿈으로 변환 → JS `split('\n\n')` 깨짐. 또한 Windows SSE가 `\r\n\r\n`(CRLF)로 구분.
- **해결:** static/index.html 별도 파일로 분리. FileResponse로 서빙. SSE split을 `/\r?\n\r?\n/` 정규식으로 변경.
- **기술:** FileResponse (FastAPI 내장), 추가 의존성 0
- **충돌 검토:** API 경로와 충돌 없음. 기존 코드 변경 없음. Kei persona 무관.
- **상태:** done
### BF-2: 블록 내용이 비어있음 (Jinja2 include 변수 전달 실패) [발견: BF-1 이후]
- **현상:** 슬라이드 HTML은 생성되지만 모든 블록 텍스트가 비어있음. 레이아웃 구조만 있고 내용 없음.
- **원인:** renderer.py에서 Jinja2 `include`로 블록 템플릿을 삽입하는데, `include`는 블록별 변수를 개별 전달하지 못함. Sonnet이 채운 data가 템플릿에 도달 안 함.
- **해결:** `include` 대신 각 블록 템플릿을 `env.get_template().render(**data)`로 개별 렌더링 후 완성된 HTML을 삽입. `render_standalone_block()`이 이미 이 방식으로 동작 중 → 통일.
- **기술:** Jinja2 `get_template().render()` (내장), 추가 의존성 0
- **수정 파일:** renderer.py, templates/slide-base.html
- **충돌 검토:** 블록 템플릿 7개 변경 없음. pipeline.py 호출 시그니처 동일. Kei persona 무관.
- **상태:** done
### BF-3: 한글 깨짐 (다운로드 HTML 파일) [발견: BF-1 이후]
- **현상:** 다운로드한 HTML 파일에서 한글이 `ê±´ì¤ì°ì` 같은 깨진 문자로 표시.
- **원인:** Blob 다운로드 시 UTF-8 BOM 미포함. 일부 에디터/브라우저가 인코딩 자동 감지 실패.
- **해결:** download() 함수에서 Blob 생성 시 UTF-8 BOM(`'\uFEFF'`) 접두사 추가.
- **기술:** JavaScript BOM 1줄, 추가 의존성 0
- **수정 파일:** static/index.html
- **충돌 검토:** 미리보기(iframe)에 영향 없음. SSE 파싱에 영향 없음.
- **상태:** done
### BF-4: body 블록 겹침 [발견: 프리셋 도입 후]
- **현상:** body area에 4개 블록이 겹쳐서 하나만 보임
- **원인:** renderer가 같은 area에 별도 div 생성 → CSS Grid 겹침
- **해결:** OrderedDict로 같은 area 그룹핑 → 하나의 div에 flex-column
- **기술:** Python OrderedDict (내장)
- **수정 파일:** renderer.py
- **상태:** 코드 수정 완료, 테스트 필요
### BF-5: 제목 안 보임 [발견: 프리셋 도입 후]
- **현상:** 슬라이드 제목이 표시 안 됨
- **원인:** 프리셋 area명 `title` vs slide-base.html `header` 불일치
- **해결:** 프리셋 4개에서 `title``header` 교체
- **기술:** 문자열 교체
- **수정 파일:** design_director.py LAYOUT_PRESETS
- **상태:** sidebar-right 수정 완료, 나머지 3개 확인 필요
### BF-6: sidebar 카드 3열 찢어짐 [발견: sidebar-right 테스트]
- **현상:** sidebar 35% 너비에 카드 3열 → 각 카드 폭 극히 좁아 찢어짐
- **원인:** 팀장이 sidebar 공간 고려 없이 배치
- **해결:** Step B 프롬프트에 sidebar 공간 안내 추가
- **기술:** 프롬프트 엔지니어링
- **수정 파일:** design_director.py STEP_B_PROMPT
- **상태:** 미수정
### BF-7: body 블록 텍스트 비어있음 [발견: 편집자 출력 확인]
- **현상:** body의 4개 블록 중 1개만 텍스트 있고 3개 비어있음
- **원인:** content_editor 매칭에서 같은 area 첫 번째만 매칭 (break)
- **해결:** area + topic_id로 정확 매칭. 편집자 프롬프트에 topic_id 출력 추가
- **기술:** Python 조건문 수정
- **수정 파일:** content_editor.py
- **상태:** 미수정
### BF-8: 컨테이너 예산 기반 블록 배치 [발견: 파이프라인 실행 후 프레임 넘침]
- **현상:** body에 4개 블록(quote+card+venn+comparison) 쌓아서 총 ~810px → 490px 예산 초과 → 잘림
- **원인:** 팀장 프롬프트가 콘텐츠 중심 블록 선택 (높이 제약 없음). 큰 SVG(380px)를 다른 블록과 함께 배치
- **해결:**
- LAYOUT_PRESETS: zone별 budget_px + width_pct 추가
- STEP_B_PROMPT: "컨테이너 예산 확인 → 배정 → 블록+높이 계산 → 검증" 4단계 사고
- catalog.yaml: 블록별 height_cost (compact/medium/large/xlarge) + 높이 참고표
- base.css: area div에 overflow:hidden + min-height:0 안전망
- 시각화 블록: flex-shrink + responsive SVG
- **수정 파일:** design_director.py, catalog.yaml, base.css, venn-diagram.html, circle-gradient.html
- **상태:** done (2026-03-25)
- **한계:** 프롬프트만으로는 Sonnet이 grid를 무시하는 문제를 방지 불가 → BF-9 필요
- **충돌 해소 (2026-03-25):** 다른 에이전트가 구 블록(quote-block, card-grid, comparison)을 BLOCK_SLOTS/defaults에서 의도적 제거. BF-8에서 catalog에 복원했던 것을 다시 제거하여 정합성 확보. 구 블록 → 신규 블록 대체 방향 확정.
### BF-9: grid와 Sonnet의 역할 분리 [발견: 파이프라인 실행 결과 분석]
- **현상:** Sonnet이 프리셋 grid 대신 자기만의 5행 all-auto grid 생성. zone명도 불일치(main, definitions 등)
- **원인:** 설계 오류 — Sonnet에게 grid 값을 출력하라고 요구한 것 자체가 잘못. grid는 코드(Step A)가 결정, Sonnet이 건드릴 대상 아님
- **해결:**
- Step B 프롬프트: grid 출력 요구 제거, blocks 배열만 출력하도록 변경
- create_layout_concept(): grid 값은 프리셋에서 직접 가져옴 (Sonnet 출력 무시)
- Sonnet이 출력한 area명이 프리셋 zone에 없으면 코드에서 자동 매핑
- **원칙:** 코드가 결정한 것은 코드가 유지한다. Sonnet은 콘텐츠 판단만.
- **수정 파일:** design_director.py (STEP_B_PROMPT + create_layout_concept)
- **상태:** done (2026-03-25)
### BF-10: _CATALOG_MAP 캐시 갱신 문제 [발견: 파이프라인 실행 결과 분석]
- **현상:** relationship 블록이 _legacy CSS 원형으로 렌더링됨 (SVG premium이 아님). catalog.yaml 매핑이 적용 안 됨.
- **원인:** _CATALOG_MAP이 모듈 레벨 global로 한 번만 로드. 서버가 구 catalog를 캐시.
- **해결:** 파일 mtime 확인 후 자동 reload, 또는 매 렌더링 시 강제 reload
- **기술:** Python pathlib stat()
- **수정 파일:** renderer.py
- **상태:** done (2026-03-25)
## Phase 5: 블록 라이브러리 확장
| 태스크 | 상태 | 담당 | 시작 | 완료 | 메모 |
|--------|------|------|------|------|------|
| DA-17: Figma 에셋 추출 + 블록 템플릿 | done | - | 2026-03-25 | 2026-03-25 | 스크린샷 16장, 에셋 15개+, 신규 블록 6종 |
| DA-18: 카테고리 폴더 재편 | done | - | 2026-03-25 | 2026-03-25 | 6개 카테고리 + INDEX.md |
| DA-19: 변형 확장 | done | - | 2026-03-25 | 2026-03-25 | 46개 달성. catalog/BLOCK_SLOTS/INDEX 전체 동기화 완료 |
## Phase 2: 파이프라인 고도화
| 태스크 | 상태 | 담당 | 시작 | 완료 | 메모 |
|--------|------|------|------|------|------|
| P2-A: FAISS 블록 검색 | done | - | 2026-03-25 | 2026-03-25 | bge-m3 1024d, 46벡터. block_search.py 신규. director 연동 완료 |
| P2-B: SVG N개 자동 배치 | done | - | 2026-03-25 | 2026-03-25 | svg_calculator.py 신규. N=2~7 테스트 통과. Phase 1 fallback 유지 |
| P2-C: Step A Opus+FAISS | done | - | 2026-03-25 | 2026-03-25 | _opus_block_recommendation(). Kei API 경유. Anthropic 직접 0회 |
| P2-D: 5단계 재검토 강화 | done | - | 2026-03-25 | 2026-03-25 | MAX_REVIEW_ROUNDS=2. expand/shrink/rewrite 3개 action. 다른쪽 구현+루프 추가 |
| P2-E-1: Pillow 이미지 크기 | done | - | 2026-03-25 | 2026-03-25 | 다른쪽에서 image_utils.py 구현 완료 |
| P2-E-2: details-block 연결 | done | - | 2026-03-25 | 2026-03-25 | "생략"→details-block 배치. fallback에도 반영 |
| DA-21: renderer 카테고리 경로 지원 | todo | - | - | - | DA-18 이후 |
| DA-22: catalog.yaml 경로 업데이트 | todo | - | - | - | DA-21 이후 |
---
## 블로킹 이슈
없음
---
## DA-17 상세 기록
### Figma 추출 결과
- **파일:** 바론컨설턴트 홈페이지_기획팀공유 (uw7Z2hZGv9k6ygwrgYaAnF)
- **접근:** Figma REST API (유료 계정 토큰)
- **스크린샷:** 16장 (메인 3 + 자세히보기 13)
- **에셋:** bg_header, card_img x3, compare_box x2, dx_bim_table, circle_label, mountain_viz, image_grid x2 등
- **노드 분석:** 2-1_01 (건설산업), 2-1_02 (BIM) depth=4 상세 구조
### 신규 블록 템플릿 6종
| 블록 | 카테고리 | 검증 결과 |
|------|---------|---------|
| section-title-with-bg | headers/ | ✅ 렌더링 OK |
| topic-left-right | headers/ | ✅ 렌더링 OK, 사용자 확인 |
| compare-pill-pair | visuals/ | ✅ 색상 2차 수정 후 OK |
| circle-gradient | visuals/ | ✅ 사용자 확인 OK |
| card-image-3col | cards/ | ✅ 사용자 확인 OK |
| image-row-2col | media/ | ✅ 렌더링 OK |
### 기존 블록 수정
| 블록 | 수정 내용 |
|------|---------|
| comparison-table → compare-3col-badge | Figma 톤으로 재디자인 (중앙 VS 배지, 좌우 중앙정렬) |
| conclusion-bar → conclusion-accent-bar | Figma 톤으로 재디자인 (좌측 파란 라인 + 밝은 배경) |
| compare-box → compare-pill-pair | Figma 톤으로 재디자인 (하늘색 둥근 테두리 + 시안 텍스트) |
### 시각화 방식 검증 이력
1. **CSS 원형 벤 다이어그램** → 실패 (클로드스러운 플랫 디자인, 20점)
2. **AntV infographic-cli** → 제한적 (일부 SSR 타임아웃, 관계도 용도 안 맞음)
3. **AI 이미지(Gemini) + HTML 텍스트 오버레이** → 실패 (이미지 내 원 위치가 매번 달라 텍스트 위치 맞출 수 없음)
4. **SVG premium (radialGradient + filter + 수학적 좌표 계산)****성공! 최종 확정**
- 텍스트가 SVG 안에 있어 위치 100% 정확
- 그라데이션/글로우/하이라이트로 Figma 수준 품질
- N개 원소 자동 배치 (360/N 간격, cos/sin)
### Phase 1 완료 요약 (2026-03-25)
- 블록 라이브러리: 6개 카테고리, 18개 블록 변형
- 시각화 방식: SVG premium 확정
- Figma 에셋: 스크린샷 16장, 에셋 15개+
- 블록 검증: 독립 렌더링 테스트 전체 통과
- 노하우: 텍스트=HTML/CSS, 시각화=SVG, 실사=이미지, AI이미지=배경전용
---
## 완료된 준비 사항
| 항목 | 파일 | 상태 |
|------|------|------|
| 프로젝트 규칙 | CLAUDE.md | 완료 (블록 라이브러리 구조 반영) |
| 실행 계획 | PLAN.md | 완료 (Phase 5 추가) |
| 진행 추적 | PROGRESS.md | 완료 (이 파일) |
| 기술 조사 | docs/RESEARCH.md | 완료 |
| Figma 분석 | docs/figma-analysis/DESIGN-ANALYSIS.md | 완료 |
| Figma 추출 계획 | docs/FIGMA-COMPONENT-EXTRACTION-PLAN.md | 완료 |
| 블록 라이브러리 | templates/blocks/ (6개 카테고리) | 구축 완료, 변형 확장 중 |
| 블록 인덱스 | templates/blocks/INDEX.md | 완료 |
| 블록 카탈로그 | templates/catalog.yaml | 완료 (경로 업데이트 필요) |
| MCP 설정 | .mcp.json (Framelink Figma MCP) | 완료 |

281
04. design_agent/README.md Normal file
View File

@@ -0,0 +1,281 @@
# Kei Design Agent
콘텐츠를 시각적으로 구조화된 슬라이드 HTML로 변환하는 독립 에이전트.
## 개요
텍스트/MDX 콘텐츠를 입력하면, AI가 정보 구조를 파악하고 적합한 레이아웃과 블록을 선택하여 깔끔한 1페이지(또는 다중 페이지) 슬라이드를 생성합니다.
## 아키텍처 (5단계 파이프라인)
```
텍스트 입력 (+ 이미지 폴더 경로)
[1] Kei 실장 (Kei API → Opus) — 정보 구조 파악 + 꼭지 추출
- 본문 흐름(flow) vs 참조 정보(reference) 분리
- 각 꼭지의 레이어/강조/배치 방향 판단
- 이미지 판단 (개수/소속/핵심·보조/텍스트 포함 여부)
- 표 판단 (행/열 규모, 1페이지 표시 가능 여부)
- fallback: Anthropic Sonnet 직접 호출
[2] 디자인 팀장 — 3-Step
Step A-1: 레이아웃 프리셋 자동 선택 (규칙 기반)
- sidebar-right / two-column / hero-detail / single-column
Step A-2: Opus(Kei API) 블록 추천 (FAISS 검색 결과 기반)
- 도메인 지식 + 콘텐츠 성격 기반 블록 추천
- fallback: 추천 없이 Step B로
Step B: 프리셋 안에서 블록 매핑 + 글자 수 가이드 (Sonnet)
- Opus 추천 참고하되 최종 선택은 팀장 판단
- 컨테이너 예산(zone별 높이 px) 기반 블록 선택
- grid는 코드가 프리셋에서 강제 (Sonnet은 blocks만 출력)
[3] Kei 텍스트 편집자 (Kei API) — 도메인 전문가로서 텍스트 정리
- 글자 수 가이드 참고, 내용 의미 우선
- 출처 보존, 개조식, 날조 금지
- fallback: Anthropic Sonnet 직접 호출
[4] 디자인 실무자 (Sonnet + Jinja2 + CSS Grid) — 디자인 조정 + HTML 조립
- Sonnet이 텍스트 양에 맞게 CSS 변수 override 결정 (폰트/여백/간격)
- Jinja2로 블록 템플릿 렌더링 + CSS 변수 cascade로 자동 적용
- SVG 시각화 블록: 좌표 사전 계산 (svg_calculator.py)
- 이미지 base64 인라인 삽입 (다운로드 HTML에서도 표시)
[5] 디자인 팀장 (Sonnet) — 전체 재검토 (최대 2회 루프)
- HTML 전문 기반 균형 점검
- expand/shrink/rewrite 조정 (AI가 target_ratio 결정)
- 조정 후 재렌더링 → 재검토
미리보기 + HTML 다운로드
```
## 블록 라이브러리 (46개 + _legacy 13개)
```
templates/blocks/
├── INDEX.md 전체 인덱스
├── headers/ (5개) 타이틀, 꼭지 헤더
│ ├── section-title-with-bg.html 배경 이미지 + 영문/한글
│ ├── section-header-bar.html 파란 배경 바 + 제목
│ ├── topic-left-right.html 좌:제목 + 우:설명
│ ├── topic-center.html 중앙 정렬 제목
│ └── topic-numbered.html 번호 + 제목 + 설명
├── cards/ (10개) 카드 계열
│ ├── card-text-grid.html 텍스트 카드 2~4열
│ ├── card-image-3col.html 이미지 카드 3열
│ ├── card-dark-overlay.html 다크 오버레이 카드
│ ├── card-tag-image.html 태그 + 이미지 카드
│ ├── card-icon-desc.html 아이콘 + 설명 카드
│ ├── card-compare-3col.html 비교 카드 3열
│ ├── card-step-vertical.html 세로 단계 카드
│ ├── card-image-round.html 원형 이미지 카드
│ ├── card-stat-number.html 큰 숫자 KPI 카드
│ └── card-numbered.html 번호 리스트 카드
├── tables/ (3개) 비교 테이블
│ ├── compare-3col-badge.html A|VS배지|B 3단 비교
│ ├── compare-2col-split.html 좌우 분할 비교
│ └── table-simple-striped.html 줄무늬 일반 테이블
├── visuals/ (10개) 다이어그램, 관계도 (SVG)
│ ├── venn-diagram.html 벤 다이어그램 (N개 동적)
│ ├── circle-gradient.html 그라데이션 원 + 텍스트
│ ├── compare-pill-pair.html 둥근 박스 2개 + VS
│ ├── process-horizontal.html 가로 단계 흐름
│ ├── flow-arrow-horizontal.html 가로 화살표 흐름
│ ├── keyword-circle-row.html 키워드 원형 나열
│ ├── layer-diagram.html 레이어 다이어그램
│ ├── timeline-vertical.html 세로 타임라인
│ ├── timeline-horizontal.html 가로 타임라인
│ └── pyramid-hierarchy.html 피라미드 계층
├── emphasis/ (13개) 강조, 인용, 결론
│ ├── quote-left-border.html 좌측 라인 인용
│ ├── quote-big-mark.html 큰 따옴표 인용
│ ├── quote-question.html 질문형 강조
│ ├── conclusion-accent-bar.html 좌측 라인 결론
│ ├── comparison-2col.html 2단 비교
│ ├── banner-gradient.html 그라데이션 배너
│ ├── dark-bullet-list.html 다크 배경 불릿 리스트
│ ├── highlight-strip.html 하이라이트 스트립
│ ├── callout-solution.html 솔루션 콜아웃
│ ├── callout-warning.html 경고 콜아웃
│ ├── tab-label-row.html 탭 라벨 행
│ ├── divider-text.html 텍스트 구분선
│ └── details-block.html 자세히보기 (접기/펼치기)
├── media/ (5개) 이미지/미디어
│ ├── image-row-2col.html 이미지 2장 나란히
│ ├── image-grid-2x2.html 이미지 2x2 그리드
│ ├── image-side-text.html 이미지 + 텍스트
│ ├── image-full-caption.html 전체 너비 이미지 + 캡션
│ └── image-before-after.html Before/After 비교
└── _legacy/ (13개) 이전 버전 (fallback)
```
## FAISS 블록 검색
46개 블록 전체를 프롬프트에 넣는 대신, FAISS로 꼭지별 관련 블록만 검색하여 전달합니다.
```
꼭지 "A vs B 비교" → FAISS 검색 → comparison-2col, compare-pill-pair, compare-2col-split
꼭지 "연도별 로드맵" → FAISS 검색 → timeline-vertical, timeline-horizontal, card-step-vertical
```
- 임베딩 모델: BAAI/bge-m3 (1024차원, 한국어 최적화)
- 인덱스 빌드: `python scripts/build_block_index.py`
- fallback: 인덱스 없으면 catalog.yaml 전문 전달 (기존 방식)
## 레이아웃 프리셋
| 프리셋 | 조건 | CSS Grid | zone 예산 |
|--------|------|----------|----------|
| `sidebar-right` | reference 꼭지 있음 | 65:35 좌우 분할 | body 490px, sidebar 490px |
| `two-column` | 대등한 비교 | 50:50 균등 | left 490px, right 490px |
| `hero-detail` | 고강조 1개 + 보조 | hero 영역 + detail | hero 310px, detail 155px |
| `single-column` | 순차적 flow만 | 1열 | body 490px |
grid는 코드(Step A)가 결정. Sonnet은 blocks만 출력. grid 변경 불가.
## 기술 스택
| 역할 | 도구 |
|------|------|
| 서버 | FastAPI + uvicorn (포트 8001) |
| AI (1단계 실장) | Kei API (Opus) → fallback: Sonnet |
| AI (2단계 A-2) | Kei API (Opus) — 블록 추천 |
| AI (2단계 B) | Anthropic API (Sonnet) — 블록 매핑 |
| AI (3단계 편집자) | Kei API → fallback: Sonnet |
| AI (4단계 실무자) | Anthropic API (Sonnet) — CSS 조정 |
| AI (5단계 재검토) | Anthropic API (Sonnet) — 균형 점검 |
| 블록 검색 | FAISS + bge-m3 (46개 블록 인덱스) |
| 템플릿 | Jinja2 (카테고리별 블록 조합) |
| 렌더링 | CSS Grid + 디자인 토큰 (16:9, 1280×720) |
| SVG 시각화 | svg_calculator.py (cos/sin 좌표 계산, N개 동적) |
| 이미지 처리 | Pillow (크기 측정) + base64 인라인 |
| 폰트 | Pretendard Variable (한국어) |
## 설치 및 실행
### 설치
```bash
cd design_agent
python -m venv .venv
.venv/Scripts/activate # Windows
pip install -e .
```
### FAISS 인덱스 빌드
```bash
python scripts/build_block_index.py
```
### 환경 변수
`.env` 파일:
```env
ANTHROPIC_API_KEY=sk-ant-...
KEI_API_URL=http://localhost:8000
LOG_LEVEL=DEBUG
```
### 실행
```bash
# 터미널 1: Kei 백엔드 (Opus 실장 + 편집자 역할)
cd D:\ad-hoc\kei\persona_agent
uvicorn backend.main:app --reload --host 127.0.0.1 --port 8000
# 터미널 2: Design Agent
cd D:\ad-hoc\kei\design_agent
uvicorn src.main:app --reload --host 127.0.0.1 --port 8001
```
접속: http://localhost:8001
## 프로젝트 구조
```
design_agent/
├── CLAUDE.md 프로젝트 규칙 + 5단계 프로세스
├── PLAN.md 태스크 계획
├── PROGRESS.md 진행 상황
├── IMPROVEMENT.md 개선 계획 (Phase A~F)
├── IMPROVEMENT-PHASE-{A~D}.md 각 Phase 실행 상세
├── README.md 이 파일
├── pyproject.toml
├── .env API 키
├── src/ 파이프라인 코드
│ ├── main.py FastAPI 서버 (포트 8001)
│ ├── config.py 설정 (pydantic-settings)
│ ├── kei_client.py 1단계: Kei API → 꼭지 추출
│ ├── design_director.py 2단계: 프리셋 선택 + Opus 추천 + 블록 매핑
│ ├── content_editor.py 3단계: Kei API → 텍스트 정리
│ ├── pipeline.py 5단계 파이프라인 (디자인 조정 + 재검토 루프)
│ ├── renderer.py 4단계: HTML 조립 (SVG 전처리 + CSS 변수 override)
│ ├── block_search.py FAISS 블록 검색 모듈
│ ├── svg_calculator.py SVG 좌표 계산 (cos/sin N개 배치)
│ └── image_utils.py 이미지 크기 측정 + base64 삽입
├── scripts/
│ └── build_block_index.py FAISS 인덱스 빌드 스크립트
├── templates/
│ ├── slide-base.html 슬라이드 베이스 (다중 페이지 + 인쇄 JS)
│ ├── catalog.yaml 블록 카탈로그 (46개, height_cost 포함)
│ └── blocks/ 블록 라이브러리 (6 카테고리, 46개)
│ ├── INDEX.md 전체 인덱스
│ ├── headers/ (5) 타이틀, 꼭지 헤더
│ ├── cards/ (10) 카드 계열
│ ├── tables/ (3) 비교 테이블
│ ├── visuals/ (10) 다이어그램, 관계도 (SVG)
│ ├── emphasis/ (13) 강조, 인용, 결론, 자세히보기
│ ├── media/ (5) 이미지/미디어
│ └── _legacy/ (13) 이전 버전 (fallback)
├── static/
│ ├── index.html 프론트엔드 (이미지 경로 입력 팝업 포함)
│ ├── tokens.css 디자인 토큰
│ └── base.css 기본 슬라이드 스타일
├── data/ 로컬 데이터 (gitignored)
│ ├── block_index.faiss FAISS 벡터 인덱스
│ └── block_metadata.json 인덱스 메타데이터
├── docs/
│ ├── RESEARCH.md 기술 조사
│ ├── PHASE2-PLAN.md Phase 2 계획
│ ├── PHASE2-PROCESS.md Phase 2 실행 프로세스
│ ├── PHASE2-TECH-REVIEW.md Phase 2 기술 검토
│ ├── figma-screenshots/ Figma 스크린샷 (16장)
│ ├── figma-assets/ Figma 에셋
│ ├── figma-analysis/ 노드 구조 분석
│ └── block-tests/ 블록 테스트 HTML
└── tests/
```
## 핵심 원칙
- **모든 판단은 AI 사고. 하드코딩 없음**
- 텍스트가 기준. 디자인이 텍스트에 맞춤 (텍스트를 자르지 않음)
- 이미지 원본 그대로, 크기만 조절 (object-fit: contain)
- 컨테이너 예산(zone별 높이 px) 안에서 블록 배치
- grid는 코드가 결정. Sonnet은 blocks만 판단
- Kei API 1차 → Sonnet fallback (1단계, 3단계)
- Kei Persona Agent 코드를 수정하지 않음
## Kei Persona와의 관계
```
Kei Persona (본체) — localhost:5173/8000
├ 대화/생성/피드백/실행 모드
├ Opus + RAG (bge-m3 + FAISS)
└ 독립적으로 동작
Design Agent (이 프로젝트) — localhost:8001
├ 슬라이드 생성 전용
├ Kei API로 실장(1단계) + 편집자(3단계) + 블록 추천(2단계 A-2) 호출
├ FAISS 블록 검색 (bge-m3, Kei와 동일 모델)
└ 독립적으로 동작 (Kei 없이도 Sonnet fallback)
```
두 프로젝트는 완전히 독립. 코드 공유 없음. API 연동만.

View File

@@ -0,0 +1,105 @@
# 45개 블록 BLOCK_SLOTS — design_director.py에 반영 필요
# 다른 쪽 작업 완료 후 교체
BLOCK_SLOTS = {
# headers/
"section-title-with-bg": {"required": ["title_ko"], "optional": ["title_en", "breadcrumb", "bg_image"]},
"section-header-bar": {"required": ["title"], "optional": ["subtitle"]},
"topic-left-right": {"required": ["title", "description"], "optional": []},
"topic-center": {"required": ["title"], "optional": ["subtitle", "description"]},
"topic-numbered": {"required": ["number", "title"], "optional": ["description", "color"]},
# cards/
"card-image-3col": {"required": ["cards"], "optional": []},
"card-text-grid": {"required": ["cards"], "optional": []},
"card-dark-overlay": {"required": ["cards"], "optional": []},
"card-tag-image": {"required": ["cards"], "optional": []},
"card-icon-desc": {"required": ["cards"], "optional": []},
"card-compare-3col": {"required": ["cards"], "optional": []},
"card-step-vertical": {"required": ["steps"], "optional": []},
"card-image-round": {"required": ["cards"], "optional": []},
"card-stat-number": {"required": ["stats"], "optional": []},
"card-numbered": {"required": ["items"], "optional": []},
# tables/
"compare-3col-badge": {"required": ["headers", "rows"], "optional": []},
"compare-2col-split": {"required": ["left_title", "right_title", "rows"], "optional": []},
"table-simple-striped": {"required": ["headers", "rows"], "optional": []},
# visuals/
"venn-diagram": {"required": ["center_label", "items"], "optional": ["center_sub", "description"]},
"circle-gradient": {"required": ["label"], "optional": ["sub_label"]},
"compare-pill-pair": {"required": ["left_label", "right_label"], "optional": ["left_sub", "right_sub"]},
"process-horizontal": {"required": ["steps"], "optional": []},
"flow-arrow-horizontal": {"required": ["steps"], "optional": []},
"keyword-circle-row": {"required": ["keywords"], "optional": []},
"layer-diagram": {"required": ["layers"], "optional": ["title"]},
"timeline-vertical": {"required": ["events"], "optional": []},
"timeline-horizontal": {"required": ["events"], "optional": []},
"pyramid-hierarchy": {"required": ["levels"], "optional": []},
# emphasis/
"quote-left-border": {"required": ["quote_text"], "optional": ["source"]},
"quote-big-mark": {"required": ["quote_text"], "optional": ["source"]},
"quote-question": {"required": ["question"], "optional": ["description"]},
"conclusion-accent-bar": {"required": ["conclusion_text"], "optional": ["label"]},
"comparison-2col": {"required": ["left_title", "left_content", "right_title", "right_content"], "optional": ["left_subtitle", "right_subtitle"]},
"banner-gradient": {"required": ["text"], "optional": ["sub_text"]},
"dark-bullet-list": {"required": ["bullets"], "optional": ["title"]},
"highlight-strip": {"required": ["segments"], "optional": []},
"callout-solution": {"required": ["title", "description"], "optional": ["icon", "source"]},
"callout-warning": {"required": ["title", "description"], "optional": ["icon"]},
"tab-label-row": {"required": ["tabs"], "optional": []},
"divider-text": {"required": ["text"], "optional": []},
# media/
"image-row-2col": {"required": ["images"], "optional": []},
"image-grid-2x2": {"required": ["images"], "optional": []},
"image-side-text": {"required": ["image_src"], "optional": ["image_alt", "title", "description", "bullets"]},
"image-full-caption": {"required": ["src"], "optional": ["alt", "caption"]},
"image-before-after": {"required": ["before_src", "after_src"], "optional": ["before_label", "after_label", "caption"]},
}
# _apply_defaults 용
BLOCK_DEFAULTS = {
"section-title-with-bg": {"title_ko": "(제목)"},
"section-header-bar": {"title": "(섹션)"},
"topic-left-right": {"title": "(소제목)", "description": ""},
"topic-center": {"title": "(제목)"},
"topic-numbered": {"number": "1", "title": "(단계)"},
"card-image-3col": {"cards": []},
"card-text-grid": {"cards": []},
"card-dark-overlay": {"cards": []},
"card-tag-image": {"cards": []},
"card-icon-desc": {"cards": []},
"card-compare-3col": {"cards": []},
"card-step-vertical": {"steps": []},
"card-image-round": {"cards": []},
"card-stat-number": {"stats": []},
"card-numbered": {"items": []},
"compare-3col-badge": {"headers": [], "rows": []},
"compare-2col-split": {"left_title": "A", "right_title": "B", "rows": []},
"table-simple-striped": {"headers": [], "rows": []},
"venn-diagram": {"center_label": "관계도", "items": [], "center_sub": "", "description": ""},
"circle-gradient": {"label": "(라벨)"},
"compare-pill-pair": {"left_label": "A", "right_label": "B"},
"process-horizontal": {"steps": []},
"flow-arrow-horizontal": {"steps": []},
"keyword-circle-row": {"keywords": []},
"layer-diagram": {"layers": []},
"timeline-vertical": {"events": []},
"timeline-horizontal": {"events": []},
"pyramid-hierarchy": {"levels": []},
"quote-left-border": {"quote_text": "(인용)"},
"quote-big-mark": {"quote_text": "(인용)"},
"quote-question": {"question": "(질문)"},
"conclusion-accent-bar": {"conclusion_text": "(결론)"},
"comparison-2col": {"left_title": "A", "left_content": "-", "right_title": "B", "right_content": "-"},
"banner-gradient": {"text": "(배너)"},
"dark-bullet-list": {"bullets": []},
"highlight-strip": {"segments": []},
"callout-solution": {"title": "(솔루션)", "description": ""},
"callout-warning": {"title": "(경고)", "description": ""},
"tab-label-row": {"tabs": []},
"divider-text": {"text": "구분"},
"image-row-2col": {"images": []},
"image-grid-2x2": {"images": []},
"image-side-text": {"image_src": ""},
"image-full-caption": {"src": ""},
"image-before-after": {"before_src": "", "after_src": ""},
}

View File

@@ -0,0 +1,582 @@
# Figma → 컴포넌트 추출 + 카탈로그 구축 계획
## 목적
Figma 디자인(바론컨설턴트 홈페이지 기획팀 공유)에서 재사용 가능한 **콘텐츠 블록**을 추출하고, 디자인 팀장(Sonnet)이 선택할 수 있는 카탈로그로 체계화한다.
**핵심 원칙: 블록은 모드 독립적이다.**
- 블록 자체는 "슬라이드 전용"이 아니라 **HTML/CSS 콘텐츠 블록**
- 슬라이드 모드(100vh, overflow:hidden)와 웹/스크롤 모드(auto, overflow:visible)는 **컨테이너(base 템플릿)**가 결정
- 블록은 높이를 고정하지 않음 → 어떤 컨테이너에도 들어갈 수 있음
- 현재는 `slide-base.html`(슬라이드)에 집중하되, 향후 `page-base.html`(웹) 추가 가능
```
블록 (카드, 표, 인용 등) — 모드와 무관, 재사용 가능
slide-base.html → height:100vh, overflow:hidden (슬라이드 모드)
page-base.html → height:auto, overflow:visible (웹/스크롤 모드, 향후)
```
---
## 현재 상태
### 보유 자산
| 항목 | 상태 | 위치 |
|------|------|------|
| 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 버튼 바** | ❌ | ✅ (자세히보기 버튼) | **필요 시** |
| **이미지 블록** | ❌ | ✅ (도표, 참고자료) | **신규** (3변형: full/side/thumb) |
| **자세히보기 블록** | ❌ | ✅ (상세 콘텐츠 접기/펼치기) | **신규** (`<details>/<summary>`) |
---
## 작업 계획
### Phase A: Figma 분석 + 패턴 추출
#### A-1: Figma 전체 섹션 이미지 렌더링
- **작업:** 각 섹션/프레임을 이미지로 렌더링하여 시각적으로 패턴 식별
- **방법:**
- **1차:** Framelink MCP `get_figma_data` — CSS-ready 데이터로 노드 구조 + 스타일 동시 추출
- **2차:** Figma 공식 MCP `get_screenshot` — 시각 참고용 스크린샷
- **fallback:** Figma REST API `/v1/images/{file_key}?ids={node_ids}` (MCP 미설치 시)
- **산출물:** `docs/figma-screenshots/` 폴더에 PNG 저장
- **완료 기준:** 모든 자세히보기 프레임(8개)의 스크린샷 확보
#### A-2: Figma 노드 구조 심층 분석
- **작업:** 각 프레임의 상세 스타일 + 레이아웃 정보 추출
- **방법:**
- **1차:** Framelink MCP `get_figma_data` (nodeId 지정, depth 조절)
- 자동 CSS 변환: 색상→hex/rgba, 레이아웃→flex 용어, 그림자→box-shadow, 그라데이션→linear-gradient()
- 스타일 중복 제거 (글로벌 변수로 추출)
- 토큰 효율적 (raw API 대비 1/5 크기)
- **2차:** Figma 공식 MCP `get_variable_defs` — 디자인 토큰/변수 추출
- **fallback:** Figma REST API `/v1/files/{key}/nodes?ids={ids}&depth=5` (MCP 미설치 시)
- **추출 정보:**
- TEXT 노드: fontFamily, fontSize, fontWeight, lineHeight, letterSpacing, color, 텍스트 내용
- FRAME/GROUP: auto-layout (direction, gap, padding, alignItems, justifyContent), constraints
- RECTANGLE: fills (solid/gradient/image), strokes, cornerRadius, effects
- INSTANCE: componentId (재사용 컴포넌트 식별)
- **Figma → CSS 매핑 (Framelink MCP가 자동 처리, REST API 시 수동):**
- `layoutMode: "VERTICAL"``flex-direction: column`
- `primaryAxisAlignItems: "CENTER"``justify-content: center`
- `itemSpacing: 20``gap: 20px`
- `paddingLeft/Right/Top/Bottom``padding`
- `fills[].color {r,g,b,a}``rgba()` 또는 `#hex`
- `fills[].type: "GRADIENT_LINEAR"``linear-gradient(...)`
- `cornerRadius``border-radius`
- `strokes + strokeWeight``border`
- `effects[].type: "DROP_SHADOW"``box-shadow`
- `fontSize``font-size` (px 단위)
- `lineHeightPercentFontSize: 170``line-height: 1.7`
- **산출물:** `docs/figma-analysis/` 폴더에 구조 문서
- **주의:** Figma API rate limit 심함 — depth 깊은 요청은 30분 차단 가능. 얕게 요청 후 필요한 노드만 상세 조회
#### A-3: 디자인 패턴 분류 + 명명
- **작업:** 추출된 시각 요소를 재사용 가능한 블록 단위로 분류
- **기준:**
- 2회 이상 반복되는 패턴 → 블록 후보
- 슬롯(교체 가능한 위치)이 명확한 것 → 우선 순위 높음
- 콘텐츠 유형과 매칭되는 것 → 우선 순위 높음
- **산출물:** 패턴 목록 + 각 패턴의 Figma 원본 노드 ID
### Phase B: HTML/CSS 컴포넌트 제작
#### B-1: 신규 블록 템플릿 제작 (8~10종)
- **파일:** `templates/blocks/{name}/` 폴더별 정리 (변형별 파일 + preview.png)
- **제작 순서 (우선순위):**
1. `section-title/default.html` — 공통 헤더 (모든 슬라이드에서 사용)
2. `example-card/2col.html` — 사례 카드 (출처+불릿, 정책 문서 인용)
3. `image-block/full.html`, `side.html`, `thumb.html` — 이미지 블록 3변형
- full: 전체 너비 (핵심 도표, 가로형)
- side: 텍스트 옆 (보조 이미지, 세로형)
- thumb: 썸네일 (참고 문서 표지)
- CSS: `object-fit: contain` (비율 유지, 잘리지 않음)
- 원본 이미지 그대로 사용, 크기만 조절 (crop 안 함)
4. `image-gallery/2col.html`, `3col.html`, `2x2.html` — 이미지 갤러리
5. `timeline/vertical.html`, `horizontal.html` — 타임라인 (연혁/로드맵)
6. `big-number/3col.html`, `4col.html` — 핵심 지표 (큰 숫자 + 보조 텍스트)
7. `icon-list/vertical.html`, `grid.html` — 아이콘 리스트 (기능 나열)
8. `details-block/default.html` — 자세히보기 (`<details>/<summary>`)
- 슬라이드 표면: 요약만 표시
- 펼치면: 전체 상세 내용
- 인쇄 시: `beforeprint` 이벤트로 자동 펼침
- **규칙:**
- 디자인 토큰(`var(--color-*)`) 사용 (하드코딩 색상 금지)
- Jinja2 슬롯 (`{{ variable }}`) 형식
- `<style>` 태그를 블록 HTML 안에 포함 (자체 완결)
- Figma 원본과 시각적으로 유사하되 1:1 복제 아님 (디자인 토큰 기준)
- **높이를 고정하지 않음** (auto 또는 비율 사용) — 모드 독립적
- 각 블록에 사람용 주석 포함 (용도, 적합/부적합, Figma 원본 위치)
#### B-1a: Figma 웹 → 블록 변환 규칙
Figma는 웹사이트 디자인(920px 너비, 세로 스크롤)이므로, 블록으로 변환할 때 아래 규칙을 적용한다:
| Figma 원본 | 블록 변환 | 이유 |
|------------|----------|------|
| 높이: 고정 px (예: 200px) | `height: auto` 또는 비율(%) | 720px 안에 들어가야 하고, 웹 모드에서도 동작해야 함 |
| 너비: 고정 px (예: 920px) | `fr` 또는 `%` (grid 컬럼에 맞춤) | 컨테이너 너비에 적응해야 함 |
| 색상: Figma 값 (예: #1565c0) | `var(--color-accent)` 등 디자인 토큰 | 토큰 체계 통일. 필요 시 `tokens.css`에 토큰 추가 |
| 폰트: Figma 값 (예: Noto Sans CJK KR 24px) | `var(--font-subtitle)` 등 디자인 토큰 | Pretendard 기준. 크기 비율만 참고 |
| 간격: Figma 값 (예: padding 20px, gap 80px) | `var(--spacing-inner)`, `var(--spacing-block)` 등 | 토큰 체계. 비율 참고 |
| 그라데이션 배경 | **제거** | CLAUDE.md 디자인 규칙: 그라데이션 금지 |
| 호버/클릭 효과 | **제거** | CLAUDE.md 디자인 규칙: 호버 금지 |
| 애니메이션/트랜지션 | **제거** | CLAUDE.md 디자인 규칙: 애니메이션 금지 |
| 원형 이미지 마스크 | **제거** (필요 시 별도 변형으로) | 장식 요소는 기본에서 제외 |
| 텍스트 위치 | `{{ variable }}` Jinja2 슬롯으로 표시 | 교체 가능한 부분 |
**원칙:** Figma에서 가져오는 것은 **정보 구조(카드 배치, 타임라인 흐름, 비교 레이아웃)**이지, 장식 요소가 아니다.
#### 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는 Noto Sans CJK KR 사용, 토큰은 Pretendard Variable | Pretendard 유지. Figma 폰트 크기 비율만 참고 |
| **여백 불일치** | Figma 920px 프레임 vs 슬라이드 1280px | 비율 기반으로 변환. 고정 px 대신 토큰 사용 |
| **웹 vs 슬라이드** | Figma는 웹사이트(세로 스크롤, 920x1231~2208px), 슬라이드는 고정(1280x720px) | 높이 고정하지 않음(auto). 컨테이너가 모드를 결정 |
**원칙:** Figma 디자인을 1:1 복제하지 않는다. 패턴(정보 구조)만 추출하고, 스타일은 디자인 토큰으로 통일한다.
**디자인 토큰 매핑 테이블 (Figma 추출 시 작성):**
Figma에서 추출한 색상/폰트/간격을 기존 `tokens.css`와 매핑하고, 누락된 토큰이 있으면 추가한다.
| Figma 속성 | 추출값 (예시) | 기존 토큰 매핑 | 신규 토큰 필요? |
|------------|-------------|--------------|--------------|
| 배경색 (SOLID fill) | `#f8fafc` | `--color-bg-subtle` | - |
| 포인트 색상 | `#1565c0` | `--color-accent` (현재 `#2563eb`) | 검토 필요 |
| 텍스트 색상 | `#1e293b` | `--color-primary` | - |
| 보조 텍스트 | `#64748b` | `--color-neutral` | - |
| 경고/강조 | `#dc2626` | `--color-danger` | - |
| 본문 폰트 크기 | `16px` | `--font-body` (현재 `0.95rem ≈ 15.2px`) | 비율 확인 |
| 제목 폰트 크기 | `24px` | `--font-subtitle` (현재 `1.25rem = 20px`) | 비율 확인 |
| 블록 간 간격 | `20px` | `--spacing-block` | - |
| 내부 패딩 | `16px` | `--spacing-inner` | - |
| 카드 상단 액센트 | `3px solid` | `--accent-border` | - |
이 매핑 테이블은 Phase A-2에서 실제 Figma 값을 추출한 후 정확한 값으로 채운다. 기존 토큰과 차이가 크면 토큰을 추가하되, 기존 토큰의 값을 변경하지 않는다 (기존 7개 블록이 깨질 수 있음).
### 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 REST API는 CSS를 직접 제공하지 않음. 스타일 속성만 제공 | **Framelink MCP 사용 시 자동 CSS 변환.** REST API만 사용 시 수동 변환 필요 |
| **이미지 에셋** | 벡터(VECTOR, ELLIPSE)는 PNG로 렌더링 가능하나 CSS 재현 필요 | **Framelink MCP `download_figma_images`로 일괄 export.** 단순 도형은 CSS, 복잡한 것은 PNG |
| **INSTANCE 참조** | Figma 컴포넌트(Instance)의 master 확인 필요 | `GET /v1/files/{key}/components`로 마스터 컴포넌트 조회 |
| **Rate Limit** | REST API depth 깊은 요청 시 30분 차단 가능 | **Framelink MCP가 토큰 효율적 (raw API 대비 1/5).** 얕게 요청 후 필요한 노드만 상세 조회. 결과 캐싱 |
| **디자인 토큰 접근** | REST API로는 Figma 변수/토큰 추출 불가 (별도 scope 필요) | **Figma 공식 MCP `get_variable_defs` 사용.** 또는 노드 속성에서 수동 추출 |
| **의미적 HTML 추론 불가** | "버튼"은 FRAME+TEXT, "카드"는 FRAME+FRAME — 태그 구분 없음 | 노드 이름(naming convention)으로 추론. 예: `section_title`, `example-card` |
#### MCP 도구 설정
**Framelink MCP (권장, CSS-ready 추출):**
```json
// Claude Code MCP 설정
{
"mcpServers": {
"Framelink MCP for Figma": {
"command": "cmd",
"args": ["/c", "npx", "-y", "figma-developer-mcp", "--figma-api-key=YOUR-KEY", "--stdio"]
}
}
}
```
- `get_figma_data`: 노드 스타일을 CSS-ready YAML로 변환 (색상→hex, 레이아웃→flex, 그림자→box-shadow)
- `download_figma_images`: 이미지 일괄 다운로드 (크롭, 중복 제거 자동)
**Figma 공식 MCP (디자인 토큰 + 스크린샷):**
```
claude mcp add --transport http figma https://mcp.figma.com/mcp
```
- `get_variable_defs`: 디자인 토큰(색상, 간격, 타이포) 추출
- `get_screenshot`: 시각 참고용 스크린샷
- 주의: Free/Starter 플랜 = 월 6회 호출 제한
### 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/
│ ├── comparison/ # 기존 (폴더 구조로 재편)
│ │ ├── default.html
│ │ └── preview.png
│ ├── card-grid/ # 기존
│ │ ├── default.html
│ │ ├── icon.html # B-2: 변형
│ │ └── preview.png
│ ├── relationship/ # 기존
│ ├── process/ # 기존
│ ├── quote-block/ # 기존
│ │ ├── default.html
│ │ ├── decorated.html # B-2: 변형
│ │ └── preview.png
│ ├── conclusion-bar/ # 기존
│ ├── comparison-table/ # 기존
│ ├── section-title/ # B-1: 신규
│ │ ├── default.html
│ │ └── preview.png
│ ├── example-card/ # B-1: 신규
│ │ ├── single.html
│ │ ├── 2col.html
│ │ ├── 3col.html
│ │ └── preview.png
│ ├── image-block/ # B-1: 신규 (3변형)
│ │ ├── full.html # 전체 너비 (핵심 도표)
│ │ ├── side.html # 텍스트 옆 (보조 이미지)
│ │ ├── thumb.html # 썸네일 (참고 문서)
│ │ └── preview.png
│ ├── image-gallery/ # B-1: 신규
│ │ ├── 2col.html
│ │ ├── 3col.html
│ │ ├── 2x2.html
│ │ └── preview.png
│ ├── timeline/ # B-1: 신규
│ │ ├── vertical.html
│ │ ├── horizontal.html
│ │ └── preview.png
│ ├── big-number/ # B-1: 신규
│ │ ├── 3col.html
│ │ ├── 4col.html
│ │ └── preview.png
│ ├── icon-list/ # B-1: 신규
│ │ ├── vertical.html
│ │ ├── grid.html
│ │ └── preview.png
│ └── details-block/ # B-1: 신규
│ ├── default.html
│ └── preview.png
├── slide-base.html # B-3: 업데이트 (슬라이드 모드)
└── page-base.html # 향후: 웹/스크롤 모드
samples/ # 완성 슬라이드 레시피
├── dx-bim-comparison/
│ ├── slide.html # 완성 HTML
│ ├── preview.png # 스크린샷
│ └── meta.yaml # 사용된 블록 조합 + Figma 원본 참조
└── ...
src/
├── design_director.py # C-2: catalog.yaml 연동
└── renderer.py # C-3: 신규 블록 지원
```
---
## PROGRESS.md 연동
이 계획의 태스크들은 PROGRESS.md에 아래와 같이 등록한다:
### Phase F (Figma 컴포넌트 추출) — PROGRESS.md 등록 항목
| 태스크 | 상태 | 의존성 | 메모 |
|--------|------|--------|------|
| F-A1: Figma 스크린샷 확보 | todo | MCP 설치 후 | Framelink MCP 또는 REST API |
| F-A2: 노드 구조 심층 분석 | todo | F-A1 | CSS-ready 데이터 + 토큰 매핑 테이블 |
| F-A3: 패턴 분류 + 명명 | todo | F-A1, F-A2 | 블록 후보 목록 확정 |
| F-B1: 신규 블록 템플릿 제작 (8~10종) | todo | F-A3 | 폴더 구조, 변환 규칙 적용 |
| F-B1a: Figma 웹→블록 변환 규칙 검증 | todo | F-B1 | 토큰 매핑, 높이 auto 확인 |
| F-B2: 기존 블록 변형 추가 | todo | F-B1 | CSS만 변형, 슬롯 유지 |
| F-B3: slide-base.html 업데이트 | todo | F-B1 | 기존 7개 블록 깨지지 않는지 검증 |
| F-C1: catalog.yaml 생성 | todo | F-B1 | when/not_for/slots/char_limits |
| F-C2: 팀장 프롬프트 연결 | todo | F-C1 | catalog.yaml → 시스템 프롬프트 |
| F-C3: renderer.py 업데이트 | todo | F-C1 | 신규 블록 로드 + BLOCK_SLOTS 동기화 |
| F-T1: 기존 테스트 재실행 | todo | F-B3, F-C3 | DA-16 기존 케이스 전체 통과 확인 |
**사전 세팅 (Figma 작업 시작 전 필요):**
| 세팅 | 상태 | 설명 |
|------|------|------|
| Framelink MCP 설치 | todo | `npx figma-developer-mcp --figma-api-key=...` |
| Figma 공식 MCP 설치 | todo | `claude mcp add --transport http figma https://mcp.figma.com/mcp` |
| 기존 블록 폴더 구조 재편 | todo | 플랫 파일 → 블록별 폴더 (`templates/blocks/{name}/`) |
| preview.png 생성 방법 확보 | todo | HTML을 브라우저로 열어 스크린샷 (수동 또는 Playwright) |
---
## 금지 사항
1. Figma 디자인을 1:1 복제하지 않는다 (정보 구조만 추출, 장식은 제거, 스타일은 토큰 기준)
2. 기존 7개 블록 템플릿을 수정하지 않는다 (신규/변형은 별도 파일)
3. 한 번에 모든 블록을 만들지 않는다 (A-3 분류 결과를 보고 우선순위 재조정)
4. catalog.yaml 없이 블록을 추가하지 않는다 (카탈로그 미등록 = 디자인 팀장이 모름)
5. Kei Persona Agent 코드를 수정하지 않는다
6. 블록의 높이를 고정 px로 하드코딩하지 않는다 (모드 독립적이어야 함)
7. 기존 `tokens.css`의 값을 변경하지 않는다 (신규 토큰 추가는 가능, 기존 값 변경은 7개 블록 깨짐 위험)
8. 이미지를 crop하지 않는다 (원본 그대로, 크기만 조절)
9. 그라데이션, 호버, 애니메이션 등 Figma 장식 요소를 가져오지 않는다

View File

@@ -0,0 +1,245 @@
# Phase 2 계획 — 파이프라인 고도화 + 검색 + 시각화 자동화
## Phase 1 완료 현황 (2026-03-25)
| 항목 | 상태 | 수량 |
|------|------|------|
| 블록 라이브러리 | ✅ | 46개 (6 카테고리) |
| catalog.yaml | ✅ | 46개 등록, when/not_for/slots/height_cost |
| BLOCK_SLOTS | ✅ | 46개 동기화 |
| _apply_defaults | ✅ | 46개 동기화 |
| SVG premium | ✅ | venn-diagram 검증 (3개 고정) |
| Figma 에셋 | ✅ | 스크린샷 16장, 에셋 15개+ |
| 5단계 파이프라인 | ✅ | 코드 동작 (BF-4~10 수정 완료/진행중) |
| Kei API 연동 | ✅ | 1단계(실장) + 3단계(편집자) |
| catalog→renderer 매핑 | ✅ | mtime 캐시 (BF-10) |
| grid 역할 분리 | ✅ | BF-9 (코드가 grid, Sonnet은 blocks만) |
---
## Phase 2 목표
**파이프라인을 실제 사용 가능한 수준으로 고도화한다.**
Phase 1은 "기반 구축 + 블록 라이브러리"였고,
Phase 2는 "AI가 블록을 정확히 선택 + 고품질 결과물 생성"이다.
---
## Phase 2-A: FAISS 블록 검색 인덱스
### 목적
디자인 팀장(Step B)이 콘텐츠를 보고 46개 블록 중 적합한 것을 **검색**으로 찾는다.
현재는 catalog.yaml 전문이 프롬프트에 들어가는데, 46개가 넘으면 토큰 낭비 + 선택 정확도 저하.
### 구현
```
1. 각 블록의 (id + visual + when + not_for)를 임베딩
2. FAISS 인덱스 구축 (46개 벡터)
3. 콘텐츠 꼭지 분석 결과를 쿼리 → 상위 5~8개 후보 반환
4. 팀장 프롬프트에 후보 블록만 포함 (전체 46개 대신)
```
### 효과
- 프롬프트 토큰 절약 (46개 전문 → 5~8개만)
- 선택 정확도 향상 (관련 블록만 보여주니까)
- 블록 100개+까지 확장 가능
### 파일
- `src/block_search.py` (신규)
- `data/block_index.faiss` (생성)
- `src/design_director.py` (catalog 전문 → 검색 결과로 교체)
### 의존성
- sentence-transformers 또는 Anthropic embeddings
- FAISS (faiss-cpu)
---
## Phase 2-B: SVG N개 자동 배치
### 목적
현재 venn-diagram은 3개 원 고정. 콘텐츠에 따라 2~7개 원이 필요할 수 있음.
수학적 계산(cos/sin)으로 N개를 자동 배치한다.
### 구현
```
1. renderer.py에 SVG 좌표 계산 함수 추가
- calc_circle_positions(n, center, radius) → [(cx, cy), ...]
- 360/N 간격, 12시 방향부터 시계방향
2. venn-diagram.html 템플릿을 동적으로 변경
- items[]에 사전 계산된 cx, cy가 포함
- 원 크기도 N에 따라 자동 조정 (N=3: r=120, N=5: r=80, N=7: r=60)
3. Gemini 참고 디자인 흐름 (선택적)
- SVG 초안 → Gemini에게 "이 구조로 고급 디자인" 요청
- 생성 이미지의 색감/그라데이션을 참고하여 SVG tokens 업데이트
- 최종은 항상 SVG (AI 이미지는 참고만)
```
### 파일
- `src/svg_calculator.py` (신규)
- `templates/blocks/visuals/venn-diagram.html` (동적 좌표 지원)
- `src/renderer.py` (SVG 계산 호출 추가)
### 검증 완료 사항
- 3/4/5개 수학적 계산: ✅ (Phase 1에서 테스트)
- SVG premium 디자인 (radialGradient+filter): ✅
- AI 이미지 방식 폐기: ✅ (텍스트 위치 불일치로 사용 불가)
---
## Phase 2-C: 2단계 Step A 고도화 (Opus + FAISS)
### 목적
현재 Step A는 규칙 4줄(프리셋 4개 중 선택)인데,
원래 의도는 **Opus가 FAISS로 적합한 구조/블록을 검색해서 배치와 크기까지 정하는 것**.
### 구현
```
현재 (Phase 1):
Step A: 규칙 기반 프리셋 선택 (코드)
Step B: Sonnet이 블록 매핑
Phase 2:
Step A: Opus + FAISS로 구조/블록 검색 + 배치/크기 결정
Step B: Sonnet이 Step A 결과를 grid에 매칭 + 글자수 가이드
```
### 세부
1. Opus가 콘텐츠를 보고 "이 콘텐츠에는 비교형+정의형+관계도가 적합" 판단
2. FAISS에서 각 유형에 맞는 블록 후보 검색
3. 후보 중 배치/크기를 결정 (좌 65% 비교표, 우 35% 정의 카드, 하단 관계도)
4. Step B(Sonnet)에게 이 배치 구조 + 후보 블록을 전달
### 의존성
- Phase 2-A (FAISS 인덱스) 완료 필요
- Kei API (Opus) 안정 동작
### 파일
- `src/design_director.py` (Step A 전면 재작성)
- `src/block_search.py` (Phase 2-A에서 생성)
---
## Phase 2-D: 5단계 재검토 강화
### 현재 문제
```
IMPROVEMENT 분석에서 발견된 문제:
- HTML을 프롬프트에 실제 전달하지 않음 (블록 데이터 양만 전달)
- shrink action이 no-op
- rewrite action이 no-op
- 조정 후 재편집이 정확하지 않음
```
### 구현
```
1. HTML 코드 요약을 프롬프트에 전달
- 전체 HTML은 너무 크니까, 블록별 텍스트 길이 + 구조 요약
2. shrink action 구현
- char_guide를 줄여서 재편집 유도
3. rewrite action 구현
- 특정 블록의 텍스트를 완전히 재작성
4. 조정 횟수 제한
- 무한 루프 방지: 최대 2회 재조정
```
### 파일
- `src/pipeline.py` (_review_balance, _apply_adjustments 개선)
---
## Phase 2-E: 누락 기능 구현
### E-1: Pillow 이미지 크기 읽기
```
콘텐츠에 이미지가 포함될 때:
- Pillow로 원본 크기 확인
- 가로/세로 비율에 따라 블록 선택 (image-full vs image-side-text)
- 팀장에게 이미지 크기 정보 전달
```
- 파일: `src/design_director.py` (Step B에 이미지 정보 추가)
### E-2: `<details>/<summary>` 완성
```
현재 details-block.html은 있지만 파이프라인에서 활용 안 됨:
- 실장이 detail_target으로 판단한 꼭지를 details-block으로 연결
- 편집자가 summary + detail 두 버전 작성
- 인쇄 시 자동 펼침 JavaScript
```
- 파일: `src/pipeline.py`, `templates/blocks/emphasis/details-block.html`
### E-3: 디자인 실무자 AI 조정
```
현재 4단계는 순수 코드(Jinja2)만.
CLAUDE.md에는 "텍스트에 맞게 폰트/여백/박스 조정"이라고 되어있음.
- Sonnet이 렌더링된 HTML을 보고 CSS 미세 조정 제안
- 또는 CSS 변수를 동적으로 조정하는 코드
```
- 파일: `src/renderer.py` 또는 `src/pipeline.py`
- 우선순위: 낮음 (다른 것이 더 급함)
---
## ~~Phase 2-F: 출력 확장~~ (design_agent 범위 밖)
**글벗에서 처리:**
- design_agent는 HTML 생성까지만 담당
- .astro 변환 → 글벗이 design_agent API 호출 후 HTML → .astro 래핑
- 글벗 연동 → 글벗 쪽에서 design_agent의 `/api/generate` 호출
---
## 작업 순서 (의존 관계)
```
Phase 2-A (FAISS) ─────────────────────┐
├→ Phase 2-C (Step A: Opus+FAISS)
Phase 2-B (SVG N개) ── 독립, 병렬 가능 │
Phase 2-D (5단계 강화) ── 독립, 병렬 가능 │
Phase 2-E (누락 기능) ── 독립, 병렬 가능 │
Phase 2-F (출력 확장) ─── 글벗에서 처리 (design_agent 범위 밖)
```
### 추천 실행 순서
```
1순위: Phase 2-A (FAISS) — 블록 선택 정확도의 핵심
2순위: Phase 2-B (SVG N개) — 시각화 자동화
3순위: Phase 2-D (5단계 강화) — 결과물 품질
4순위: Phase 2-E (누락 기능) — 완성도
5순위: Phase 2-C (Step A: Opus) — Phase 2-A 완료 필요
※ Phase 2-F (출력 확장) — 글벗에서 처리, design_agent 범위 밖
```
---
## 기술 스택 추가
| 역할 | 도구 | Phase |
|------|------|-------|
| 블록 검색 | FAISS + sentence-transformers | 2-A |
| SVG 계산 | Python math (cos/sin) | 2-B |
| Step A | Opus via Kei API + FAISS | 2-C |
| 이미지 크기 | Pillow | 2-E |
| .astro 출력 | Jinja2 + StarlightPage 템플릿 | 2-F |
---
## 성공 기준
```
Phase 2 완료 시:
✅ 텍스트 원고 입력 → 85점 슬라이드 HTML 자동 생성
✅ 블록 선택이 콘텐츠에 정확히 매칭 (FAISS 검색)
✅ 관계도/다이어그램 N개 자동 배치 (SVG)
✅ 재검토 루프가 실질적으로 동작
✅ Starlight에서 바로 볼 수 있는 .astro 출력
```

View File

@@ -0,0 +1,321 @@
# Phase 2 실행 프로세스
## 절대 규칙 (모든 작업에 적용)
```
🔴 단발성/하드코딩 금지 — 모든 구현은 N개, M종류에 범용 동작
🔴 회귀 금지 — Phase 1 확정 구조(catalog 매핑, grid 분리, Kei API 우선) 되돌리지 않음
🔴 Opus→Sonnet 대체 금지 — Kei API가 기본, Sonnet은 fallback만
🔴 "일단 돌아가게" 금지 — 설계대로 구현하거나 설계를 먼저 변경
```
---
## Phase 1 완료 자산
| 항목 | 수량/상태 |
|------|----------|
| 블록 라이브러리 | 46개 (6 카테고리) |
| catalog.yaml | 46개 (when/not_for/slots/height_cost) |
| BLOCK_SLOTS + _apply_defaults | 46개 동기화 |
| SVG premium | venn-diagram 3개 고정 검증 |
| 5단계 파이프라인 | 동작 (BF-4~10 수정) |
| Kei API 연동 | 1단계(실장) + 3단계(편집자) |
| grid 역할 분리 | BF-9 (코드가 grid, Sonnet은 blocks만) |
| catalog→renderer 매핑 | mtime 캐시 (BF-10) |
---
## 실행 순서
```
Phase 2-A (FAISS 블록 검색)
Phase 2-B (SVG N개 자동 배치) ← 2-A와 병렬 가능
Phase 2-D (5단계 재검토 강화) ← 2-A/2-B와 병렬 가능
Phase 2-E (누락 기능: Pillow, details-block)
Phase 2-C (Step A: Opus + FAISS) ← 2-A 완료 필수
```
---
## Phase 2-A: FAISS 블록 검색 인덱스
### 목적
팀장(Step B) 프롬프트에 46개 catalog 전문 대신, FAISS 검색으로 **관련 블록 5~8개만** 전달.
### 수정 파일
| 파일 | 변경 | 신규/수정 |
|------|------|---------|
| `src/block_search.py` | FAISS 인덱스 구축 + 검색 함수 | **신규** |
| `src/design_director.py` | `_load_catalog()` → 검색 결과로 교체 | 수정 (line 294) |
| `data/block_index.faiss` | 인덱스 파일 | **신규** |
| `data/block_metadata.json` | id→블록 매핑 | **신규** |
| `pyproject.toml` | faiss-cpu, sentence-transformers 의존성 | 수정 |
### 기술 상세
```
임베딩 모델: BAAI/bge-m3 (1024차원)
→ Kei persona에서 검증됨 (retriever.py line 49)
→ 한국어 최적화
인덱스 방식: faiss.IndexFlatIP (Inner Product = 코사인 유사도)
→ Kei와 동일 패턴 (retriever.py line 88)
검색 입력: 꼭지별 title + summary + layer + role
검색 출력: 상위 8개 블록 (id + visual + when + not_for + slots)
fallback: FAISS 인덱스 없거나 검색 실패 시 → catalog.yaml 전문 (기존 방식)
```
### 프로세스
```
1. scripts/build_block_index.py 실행 (1회성)
→ catalog.yaml 읽기
→ 각 블록의 (name + visual + when) 임베딩
→ data/block_index.faiss + data/block_metadata.json 생성
2. src/block_search.py
→ 서버 시작 시 인덱스 로드
→ search_blocks(query, top_k=8) → 관련 블록 목록 반환
3. src/design_director.py 수정
→ _load_catalog() 대신 search_blocks() 호출
→ 꼭지별 검색 → 카테고리별 최소 1개 보장 → 프롬프트에 삽입
```
### 충돌 검토
```
design_director.py _load_catalog(): 문자열 반환 → 문자열 반환 (인터페이스 동일) ✅
renderer.py _load_catalog_map(): 별도 함수, 영향 없음 ✅
content_editor.py: BLOCK_SLOTS만 참조, 영향 없음 ✅
pipeline.py: create_layout_concept() 인터페이스 동일 ✅
```
### 점검
- [ ] FAISS 실패 시 catalog 전문 fallback 동작하는가?
- [ ] 검색 결과에 카테고리별 최소 1개 보장되는가?
- [ ] 블록 60개로 늘어나도 인덱스 재구축만으로 동작하는가?
---
## Phase 2-B: SVG N개 자동 배치
### 목적
venn-diagram의 원 3개 고정 → N개(2~7) 자동 배치. cos/sin 수학적 계산.
### 수정 파일
| 파일 | 변경 | 신규/수정 |
|------|------|---------|
| `src/svg_calculator.py` | 좌표 계산 함수 | **신규** |
| `src/renderer.py` | venn-diagram 렌더링 전 좌표 전처리 | 수정 (render_multi_page 내) |
| `templates/blocks/visuals/venn-diagram.html` | 하드코딩 좌표 → 동적 `{{ item.cx }}` | 수정 |
### 기술 상세
```
src/svg_calculator.py:
calc_circle_positions(n, center_x, center_y, radius) → [{cx, cy}, ...]
→ angle = (2π × i / n) - π/2 (12시부터 시계방향)
→ 의존성: Python math (내장)
calc_circle_radius(n) → int
→ n≤3: 120, n≤5: 80, n≤7: 60
→ 하드코딩 아님: base_radius / (1 + (n-3)*0.15) 공식
calc_outer_circle(n) → int
→ 큰 원 반지름도 N에 따라 조정
renderer.py 수정:
render_multi_page() 안에서 block_type == "venn-diagram" 일 때:
1. items = block_data.get("items", [])
2. positions = calc_circle_positions(len(items))
3. for i, item in enumerate(items): item["cx"] = positions[i]["cx"]
4. 나머지는 Jinja2가 처리
venn-diagram.html 수정:
현재: cx="265" (하드코딩)
변경: cx="{{ item.cx }}" (동적)
fallback: items에 cx가 없으면 기존 3개 고정 좌표 사용
```
### 충돌 검토
```
renderer.py: render_multi_page()에 if 분기 추가 — 기존 흐름에 영향 없음 ✅
(venn-diagram 아닌 블록은 그대로 통과)
venn-diagram.html: Phase 1 고정 SVG → 동적으로 변경
→ fallback(cx 없으면 기존 좌표) 필수 ✅
pipeline.py: 변경 없음 ✅
content_editor.py: items[].cx는 renderer에서 추가, 편집자는 모름 ✅
```
### 점검
- [ ] N=2, 3, 4, 5, 6, 7 각각 렌더링 테스트
- [ ] items에 cx/cy가 없을 때 Phase 1 고정 SVG로 fallback
- [ ] 원끼리 겹침 없이 배치되는가? (N=7 특히)
- [ ] 큰 원 안에 모든 작은 원이 들어가는가?
---
## Phase 2-D: 5단계 재검토 강화
### 목적
_review_balance가 실질적으로 동작하도록 강화. shrink/rewrite 구현.
### 수정 파일
| 파일 | 변경 | 신규/수정 |
|------|------|---------|
| `src/pipeline.py` | _review_balance 프롬프트 + _apply_adjustments 3개 action | 수정 |
### 기술 상세
```
_review_balance 개선:
현재: 블록별 데이터 양(글자수)만 전달
변경: 블록별 (area + type + 데이터 양 + height_cost) 전달
+ 전체 zone 예산 대비 사용량
_apply_adjustments 개선:
현재: expand만 동작 (char_guide * 1.5)
변경:
expand: char_guide * 1.5 (현재와 동일)
shrink: char_guide * 0.7 (신규)
rewrite: block["data"] 제거 → fill_content 재호출 시 재작성 (신규)
재조정 루프:
MAX_ADJUSTMENTS = 2 (상수, 하드코딩 아닌 설정값)
for attempt in range(MAX_ADJUSTMENTS): ...
```
### 충돌 검토
```
pipeline.py 내부 함수만 수정 ✅
fill_content 재호출: Kei API 우선 (Phase 1에서 수정됨) ✅
renderer.py: 변경 없음 ✅
```
### 점검
- [ ] expand/shrink/rewrite 3개 action 모두 동작하는가?
- [ ] MAX_ADJUSTMENTS 초과 시 루프 종료되는가?
- [ ] fill_content 재호출이 Kei API를 거치는가? (Sonnet 직접 아닌지)
- [ ] rewrite 후 _apply_defaults로 빈 데이터가 처리되는가?
---
## Phase 2-E: 누락 기능
### E-1: Pillow 이미지 크기
| 파일 | 변경 |
|------|------|
| `src/design_director.py` | create_layout_concept() 내 이미지 크기 확인 |
```
수정 위치: topics 순회할 때 content_type=="image" 확인
→ Pillow Image.open().size로 width, height 읽기
→ topic에 image_width, image_height, image_ratio 추가
→ Step B 프롬프트에 이미지 크기 정보 포함
→ 팀장이 가로형→image-full, 세로형→image-side-text 판단 가능
fallback: 이미지 파일 없으면 → 기본 비율 1.5 (가로형 가정)
⚠️ 이것은 하드코딩이 아닌 "정보 부재 시 안전한 기본값"
```
### E-2: details-block 연결
| 파일 | 변경 |
|------|------|
| `src/design_director.py` | detail_target 꼭지를 "생략" → "details-block 배치"로 |
| `src/content_editor.py` | detail_target 꼭지에 summary + detail 두 버전 작성 |
```
현재: design_director.py에서 detail_target 꼭지를 "생략 (미구현)"으로 처리
변경: detail_target 꼭지를 details-block으로 body/sidebar에 배치
→ 편집자가 summary(3줄) + detail(전체) 작성
→ renderer가 <details>/<summary>로 조립
```
### 점검
- [ ] 이미지 없는 콘텐츠에서 Pillow 에러 안 나는가?
- [ ] detail_target 꼭지가 details-block으로 렌더링되는가?
- [ ] <details> 접기/펼치기가 브라우저에서 동작하는가?
- [ ] 인쇄 시 자동 펼침 JavaScript가 동작하는가?
---
## Phase 2-C: Step A Opus+FAISS
### 목적
규칙 4줄 → Opus가 FAISS 검색으로 구조/블록 선정 + 배치/크기 결정.
### 수정 파일
| 파일 | 변경 | 신규/수정 |
|------|------|---------|
| `src/design_director.py` | select_preset() 유지 + _opus_block_selection() 추가 | 수정 |
### 기술 상세
```
현재 흐름:
Step A: select_preset() → 규칙 기반 (코드)
Step B: Sonnet → 블록 매핑
Phase 2 흐름:
Step A-1: select_preset() → 프리셋 선택 (유지, 안정적)
Step A-2: _opus_block_selection() → Kei API(Opus)로 블록 후보 선정
입력: 꼭지 분석 + FAISS 검색 결과
출력: 각 꼭지에 추천 블록 + 배치 방향 + 크기 가이드
Step B: Sonnet → Opus 추천 기반으로 최종 매핑 + 글자수 가이드
핵심: Opus 호출은 반드시 Kei API 경유
→ kei_client.py의 _call_kei_api() 패턴 재사용
→ anthropic.AsyncAnthropic 직접 호출 절대 금지
```
### 의존성
```
Phase 2-A 완료 필수 (FAISS 인덱스 + search_blocks 함수)
Kei API(localhost:8000) 안정 동작 필요
```
### 충돌 검토
```
select_preset(): 유지 (삭제하지 않음) ✅
create_layout_concept(): Step A-2 결과를 Step B에 전달하는 구조 추가
→ 기존 인터페이스(return {"title": ..., "pages": [...]}) 동일 ✅
pipeline.py: create_layout_concept() 호출 방식 동일 ✅
```
### 점검
- [ ] Opus 호출이 Kei API를 거치는가? (`grep "AsyncAnthropic" → fallback만`)
- [ ] Kei API 실패 시 현재 방식(규칙+Sonnet)으로 fallback
- [ ] FAISS 검색 결과가 Opus에게 전달되는가?
- [ ] select_preset()이 삭제되지 않았는가? (안정적 규칙은 유지)
---
## 산출물 체크리스트
### 코드 파일
```
신규:
src/block_search.py ← 2-A
src/svg_calculator.py ← 2-B
scripts/build_block_index.py ← 2-A
data/block_index.faiss ← 2-A
data/block_metadata.json ← 2-A
수정:
src/design_director.py ← 2-A, 2-C, 2-E
src/renderer.py ← 2-B
src/pipeline.py ← 2-D, 2-E
templates/blocks/visuals/venn-diagram.html ← 2-B
pyproject.toml ← 2-A (의존성)
```
### 문서
```
docs/PHASE2-PLAN.md ← 완료
docs/PHASE2-PROCESS.md ← 이 파일
docs/PHASE2-TECH-REVIEW.md ← 완료
PLAN.md ← Phase 2 태스크 추가 필요
PROGRESS.md ← Phase 2 진행 상황 추적
```

View File

@@ -0,0 +1,396 @@
# Phase 2 기술 검토 보고서
각 항목별로 **정확한 구현 방법, 기존 코드 충돌 여부, 회귀 위험, 대충 처리 위험**을 검토한다.
---
## Phase 2-A: FAISS 블록 검색
### 현재 코드 상태
```
design_director.py line 184~188: _load_catalog()
→ catalog.yaml 전문을 문자열로 읽어서 프롬프트에 통째로 넣음
→ 46개 블록 전체 설명 = 약 8,000~10,000 토큰
design_director.py line 294: catalog_text = _load_catalog()
design_director.py line 322: catalog=catalog_text # 프롬프트에 삽입
```
### 정확한 구현 방법
**1. 임베딩 모델 선택**
```
Kei persona가 사용하는 모델: BAAI/bge-m3 (1024차원)
위치: D:\ad-hoc\kei\persona_agent\backend\llm\retriever.py line 49
design_agent에서도 동일 모델 사용:
→ 한국어 지원 ✅
→ Kei에서 검증됨 ✅
→ 1024차원으로 46개 벡터 = 약 184KB (가벼움)
```
**2. 인덱스 구축 (1회성, 오프라인)**
```python
# src/block_search.py (신규 파일)
import faiss
import yaml
from sentence_transformers import SentenceTransformer
def build_block_index():
# 1. catalog.yaml 로드
with open("templates/catalog.yaml") as f:
catalog = yaml.safe_load(f)
# 2. 각 블록의 검색용 텍스트 생성
texts = []
ids = []
for block in catalog["blocks"]:
text = f"{block['name']}. {block['visual']}. {block['when']}"
texts.append(text)
ids.append(block["id"])
# 3. 임베딩
model = SentenceTransformer("BAAI/bge-m3")
embeddings = model.encode(texts, normalize_embeddings=True)
# 4. FAISS 인덱스 생성
dim = embeddings.shape[1]
index = faiss.IndexFlatIP(dim) # Inner Product (코사인 유사도)
index.add(embeddings)
# 5. 저장
faiss.write_index(index, "data/block_index.faiss")
# ids 매핑도 저장
```
**3. 검색 (런타임, 매 요청)**
```python
def search_blocks(query: str, top_k: int = 8) -> list[dict]:
"""콘텐츠 꼭지 설명으로 적합한 블록 검색"""
embedding = model.encode([query], normalize_embeddings=True)
scores, indices = index.search(embedding, top_k)
return [catalog_blocks[i] for i in indices[0]]
```
**4. design_director.py 수정 지점**
```
현재 line 294: catalog_text = _load_catalog() # 전문
변경: catalog_text = search_blocks(topics_summary, top_k=8) # 관련 8개만
```
### 충돌 검토
| 파일 | 영향 | 충돌? |
|------|------|-------|
| design_director.py | _load_catalog() 반환값이 문자열 → 문자열(검색결과) | ❌ (인터페이스 동일) |
| pipeline.py | 호출하지 않음 | ❌ |
| renderer.py | _load_catalog_map()은 별도 함수 (경로 매핑용) | ❌ (다른 함수) |
| content_editor.py | BLOCK_SLOTS만 참조 | ❌ |
### 회귀 위험
- _load_catalog()를 교체하므로, 검색이 실패하면 catalog 전문을 fallback으로 넘겨야 함
- FAISS 인덱스 파일이 없으면 기존 방식(전문)으로 동작해야 함
### 대충 처리 위험
- ⚠️ "검색 결과 8개만 넣으면 되지" → 검색 품질이 낮으면 적합한 블록이 빠질 수 있음
- 대응: 검색 결과 + 카테고리별 최소 1개 보장 (8개 중 카테고리 커버 확인)
---
## Phase 2-B: SVG N개 자동 배치
### 현재 코드 상태
```
templates/blocks/visuals/venn-diagram.html:
→ 3개 원 좌표가 하드코딩 (cx="265" cy="300", cx="370" cy="230", cx="365" cy="355")
→ items[0], items[1], items[2]로 직접 인덱싱
renderer.py:
→ render_standalone_block()에서 block_data를 Jinja2에 **kwargs로 전달
→ 별도 전처리 없음
```
### 정확한 구현 방법
**1. 좌표 계산 함수 (신규)**
```python
# src/svg_calculator.py (신규 파일)
import math
def calc_circle_positions(
n: int,
center_x: float = 300,
center_y: float = 300,
radius: float = 120,
) -> list[dict]:
"""N개 원소를 원형으로 배치. 12시부터 시계방향."""
positions = []
for i in range(n):
angle = (2 * math.pi * i / n) - math.pi / 2
positions.append({
"cx": round(center_x + radius * math.cos(angle), 1),
"cy": round(center_y + radius * math.sin(angle), 1),
})
return positions
def calc_circle_radius(n: int, base_radius: int = 120) -> int:
"""N에 따라 작은 원 크기 자동 조정."""
if n <= 3: return base_radius
if n <= 5: return int(base_radius * 0.7)
return int(base_radius * 0.5)
```
**2. renderer.py 수정 지점**
```python
# render_multi_page() 또는 render_slide() 안에서:
if block_type in ("venn-diagram", "relationship"):
items = block_data.get("items", [])
if items:
from src.svg_calculator import calc_circle_positions, calc_circle_radius
positions = calc_circle_positions(len(items))
small_r = calc_circle_radius(len(items))
for i, item in enumerate(items):
item["cx"] = positions[i]["cx"]
item["cy"] = positions[i]["cy"]
item["r"] = small_r
```
**3. venn-diagram.html 수정**
```
현재: cx="265" (하드코딩)
변경: cx="{{ items[0].cx }}" (동적)
+ items 개수에 따라 for 루프로 생성
+ 큰 원 크기도 N에 따라 조정
```
### 충돌 검토
| 파일 | 영향 | 충돌? |
|------|------|-------|
| renderer.py | block_data 전처리 추가 | ⚠️ 주의: 기존 render 흐름에 if 분기 추가 |
| venn-diagram.html | 하드코딩 → 동적 좌표 | ⚠️ Phase 1 고정 SVG가 깨짐 → fallback 필요 |
| pipeline.py | 변경 없음 | ❌ |
| content_editor.py | items[].cx/cy는 편집자가 생성하지 않음 | ❌ (renderer에서 추가) |
### 회귀 위험
- **venn-diagram.html 변경 시 Phase 1 고정 SVG가 깨질 수 있음**
- 대응: items에 cx/cy가 없으면 기존 하드코딩 좌표 사용 (fallback)
### 대충 처리 위험
- ⚠️ 원 크기 자동 조정을 대충 하면 7개 원이 겹침
- 대응: N별 최적 반지름/큰원 크기 테이블 사전 정의
---
## Phase 2-C: Step A Opus+FAISS
### 현재 코드 상태
```
design_director.py line 145~178: select_preset()
→ 규칙 4줄: reference→sidebar, 대등비교→two-column, 고강조→hero, 나머지→single
→ LLM 호출 없음, 코드만
의도: Opus가 FAISS로 적합한 구조/블록 검색 + 배치/크기 결정
```
### 정확한 구현 방법
**1단계: select_preset()은 유지 (규칙 기반 프리셋은 안정적)**
**2단계: Opus가 블록 후보를 검색+선정하는 함수 추가**
```python
# design_director.py에 추가
async def _opus_block_selection(
content: str,
analysis: dict,
block_candidates: list[dict], # FAISS 검색 결과
) -> list[dict]:
"""Opus가 FAISS 후보에서 최종 블록을 선정하고 배치를 결정."""
# Kei API를 통해 Opus 호출
kei_url = settings.kei_api_url
prompt = f"""
콘텐츠 분석 결과와 블록 후보를 보고,
각 꼭지에 가장 적합한 블록을 선택하고 배치를 결정해줘.
후보 블록: {block_candidates}
꼭지: {analysis['topics']}
"""
# Kei API 호출 (실장과 동일 패턴)
...
```
### 충돌 검토
| 파일 | 영향 | 충돌? |
|------|------|-------|
| design_director.py | select_preset() 유지 + _opus_block_selection() 추가 | ❌ (추가만) |
| kei_client.py | Kei API 호출 패턴 재사용 | ❌ (참조만) |
| pipeline.py | create_layout_concept() 인터페이스 동일 | ❌ |
### 회귀 위험
- ⚠️ Opus가 Kei API를 통해 호출되어야 하는데, **Sonnet을 직접 호출하면 안 됨**
- 대응: _call_kei_api() 패턴 그대로 복제. Anthropic 직접 호출 금지.
- ⚠️ Kei API 실패 시 fallback = 현재 규칙 기반 방식 (select_preset + Sonnet Step B)
### 대충 처리 위험
- ⚠️ "Opus 대신 Sonnet 직접 호출" → **절대 금지**. 3단계에서 이미 이 실수 했음.
- ⚠️ FAISS 없이 catalog 전문 넣기 → Phase 2-A가 선행 안 되면 의미 없음
- 대응: Phase 2-A 완료 후에만 시작
---
## Phase 2-D: 5단계 재검토 강화
### 현재 코드 상태
```
pipeline.py line 102~161: _review_balance()
→ Sonnet에게 블록별 데이터 양(글자수)만 전달
→ HTML 자체는 전달하지 않음
→ shrink/rewrite action이 실질적으로 no-op
pipeline.py line 164~193: _apply_adjustments()
→ expand만 동작 (char_guide * 1.5)
→ shrink: 조건 매칭 안 됨 (expand만 if 처리)
→ rewrite: 아예 동작 없음
```
### 정확한 구현 방법
**1. _review_balance 프롬프트 개선**
```python
# 현재: 블록별 데이터 양만
# 변경: 블록별 텍스트 길이 + 블록 타입 + zone + height_cost
block_summary = []
for block in blocks:
data_len = len(json.dumps(block.get("data", {}), ensure_ascii=False))
block_summary.append(
f" {block['area']}/{block['type']}: "
f"데이터 {data_len}자, height_cost={block.get('height_cost', '?')}"
)
```
**2. shrink/rewrite 구현**
```python
# _apply_adjustments 수정
for adj in adjustments:
action = adj.get("action", "")
if action == "expand":
# 현재 동작: char_guide * 1.5
...
elif action == "shrink":
# 신규: char_guide * 0.7
for key in block.get("char_guide", {}):
block["char_guide"][key] = int(block["char_guide"][key] * 0.7)
elif action == "rewrite":
# 신규: data를 비우고 재편집 유도
block.pop("data", None)
```
**3. 재조정 횟수 제한**
```python
MAX_ADJUSTMENTS = 2
for attempt in range(MAX_ADJUSTMENTS):
review = await _review_balance(...)
if not review or not review.get("needs_adjustment"):
break
layout_concept = await _apply_adjustments(...)
html = render_slide(layout_concept)
```
### 충돌 검토
| 파일 | 영향 | 충돌? |
|------|------|-------|
| pipeline.py | _review_balance, _apply_adjustments 수정 | ❌ (내부 함수만) |
| content_editor.py | fill_content() 재호출됨 | ⚠️ data가 비워진 블록 → _apply_defaults로 fallback |
| renderer.py | 변경 없음 | ❌ |
### 회귀 위험
- ⚠️ 재조정 루프가 무한 반복되면 API 비용 폭증
- 대응: MAX_ADJUSTMENTS = 2로 하드 제한
- ⚠️ fill_content 재호출 시 Kei API가 아닌 Sonnet으로 빠질 수 있음
- 대응: fill_content는 이미 Kei API 1순위로 수정됨 ✅
---
## Phase 2-E: 누락 기능
### E-1: Pillow 이미지 크기
**수정 지점:** design_director.py create_layout_concept() 내부
```python
# 콘텐츠에 이미지 경로가 있으면 크기 확인
from PIL import Image
for topic in analysis.get("topics", []):
if topic.get("content_type") == "image":
img_path = topic.get("image_path")
if img_path and Path(img_path).exists():
w, h = Image.open(img_path).size
topic["image_width"] = w
topic["image_height"] = h
topic["image_ratio"] = w / h # >1.2 가로, <0.8 세로
```
**충돌:** 없음 (analysis dict에 필드 추가만)
**회귀:** 없음 (이미지가 없으면 기존 흐름 그대로)
### E-2: details-block 연결
**수정 지점:** pipeline.py generate_slide() 내부
```python
# 실장이 detail_target=True로 판단한 꼭지를 details-block으로 변환
# 현재 "생략"으로 처리 → details-block으로 연결
```
**충돌:** design_director.py에서 detail_target 꼭지를 "생략"으로 처리 중 → 이것을 "details-block으로 배치"로 변경 필요
**회귀:** detail_target 로직이 변경되므로 기존 테스트 영향
---
## 전체 충돌 매트릭스
```
director editor renderer pipeline kei_client config
2-A FAISS 수정 - - - - -
2-B SVG - - 수정 - - -
2-C Opus 수정 - - - 참조 -
2-D 재검토 - 호출 - 수정 - -
2-E Pillow 수정 - - 수정 - -
```
**동시 수정 파일이 겹치는 경우:**
- design_director.py: 2-A + 2-C + 2-E → **순서대로 진행 (2-A 먼저)**
- pipeline.py: 2-D + 2-E → **독립적 함수라 병렬 가능**
---
## 절대 규칙 (모든 Phase 2 작업에 적용)
### 🔴 절대 금지
1. **단발성/하드코딩 금지** — 특정 상황만 해결하는 if문, 매직넘버, 고정값 절대 금지. 모든 구현은 N개, M종류에 범용으로 동작해야 한다.
2. **회귀 금지** — Phase 1에서 확정한 구조(catalog 매핑, 카테고리 경로, BF-9 grid 분리, Kei API 우선)를 절대 되돌리지 않는다.
3. **Opus 대신 Sonnet 직접 호출 금지** — Kei API가 필요한 곳에 anthropic.AsyncAnthropic 직접 호출로 대체하지 않는다. fallback은 fallback이지 기본 경로가 아니다.
4. **"일단 돌아가게" 금지** — 동작하지만 원래 설계와 다른 구현은 기술 부채다. 설계대로 구현하거나 설계를 먼저 변경한다.
### 자가 점검 질문 (구현 전 반드시 확인)
- [ ] 이 코드가 블록 100개가 되어도 동작하는가?
- [ ] 이 코드가 원소 7개가 되어도 동작하는가?
- [ ] 이 코드에 하드코딩된 값이 있는가? 있다면 설정/계산으로 대체 가능한가?
- [ ] Phase 1에서 확정한 인터페이스(catalog 매핑, grid 프리셋 분리)를 변경하는가?
- [ ] Kei API가 아닌 Sonnet을 직접 호출하는 코드가 있는가? (fallback 제외)
- [ ] 이 수정이 다른 모듈의 기존 동작을 깨뜨리는가?
---
## "대충 처리" 방지 체크리스트
| # | 위험 | 방지책 | 점검 방법 |
|---|------|-------|----------|
| 1 | Opus 대신 Sonnet 직접 호출 | Kei API 패턴만 사용 | `grep "AsyncAnthropic" src/*.py` → fallback 위치만 허용 |
| 2 | FAISS 없이 catalog 전문 유지 | _load_catalog() 교체 | FAISS 실패 시에만 fallback, 기본은 검색 |
| 3 | SVG 좌표를 하드코딩 | calc_circle_positions() 계산 | `grep "cx=\"[0-9]" templates/blocks/visuals/` → 0건이어야 함 |
| 4 | 재검토 루프 무한 반복 | MAX_ADJUSTMENTS = 2 | 코드에 상수 존재 확인 |
| 5 | shrink/rewrite 미구현 | 3개 action 모두 if 분기 | _apply_adjustments에서 action별 동작 확인 |
| 6 | 이미지 크기 하드코딩 | Pillow로 실측 | 고정 비율(예: 1.5) 사용 금지 |
| 7 | details-block "생략" 유지 | detail_target → details-block 배치 | design_director에서 "생략" 문자열 제거 확인 |
| 8 | 특정 블록 수에만 동작 | N개 범용 루프 | `for i in range(n)` 패턴 확인, `items[0]` 직접 인덱싱 금지 |
| 9 | 특정 프리셋에만 동작 | 모든 프리셋에서 테스트 | 4개 프리셋 × 테스트 콘텐츠 조합 |

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/)

Binary file not shown.

After

Width:  |  Height:  |  Size: 936 KiB

View File

@@ -0,0 +1,219 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>블록 테스트: card-image</title>
<style>
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body {
font-family: "Pretendard Variable", "Noto Sans KR", sans-serif;
background: #e8ecf0;
display: flex;
justify-content: center;
padding: 30px;
margin: 0;
}
.block-container {
width: 920px;
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
}
.block-container .block-section-title {
margin: -20px -20px 0;
border-radius: 8px 8px 0 0;
overflow: hidden;
}
</style>
</head>
<body>
<div class="block-container">
<!-- 이미지 카드: 상단 이미지 + 하단 텍스트 (2~4열) -->
<!--
📋 card-image
─────────────────
용도: 단계별 설명, 카테고리별 설명 (이미지가 핵심인 카드)
슬롯: cards[] 배열 (각 카드에 image, title, title_en, items[])
Figma 원본: 2-1_02 > Group 1171281594 (카드 3열)
-->
<div class="block-card-image" style="--ci-count: 3">
<div class="ci-card">
<img class="ci-img" src="figma-assets/card_img_design.png" alt="설계단계">
<div class="ci-body">
<div class="ci-title" style="color: #00aaff">설계단계</div>
<div class="ci-title-en">Design Stage</div>
<div class="ci-divider"></div>
<ul class="ci-list">
<li>고도화된 BIM 구현</li>
<li>최첨단 디지털트윈</li>
<li>시뮬레이션 분석 & 성능평가</li>
<li>지속가능한 인프라개발</li>
</ul>
</div>
</div>
<div class="ci-card">
<img class="ci-img" src="figma-assets/card_img_construction.png" alt="시공 단계">
<div class="ci-body">
<div class="ci-title" style="color: #006aff">시공 단계</div>
<div class="ci-title-en">Construction Stage</div>
<div class="ci-divider"></div>
<ul class="ci-list">
<li>향상된 건설 계획과 공정 순서 관리</li>
<li>Big Room 등 환경을 통한 협업 및 조정</li>
<li>정확한 수량산출서와 비용 산정</li>
</ul>
</div>
</div>
<div class="ci-card">
<img class="ci-img" src="figma-assets/card_img_maintenance.png" alt="유지관리 단계">
<div class="ci-body">
<div class="ci-title" style="color: #004cbe">유지관리 단계</div>
<div class="ci-title-en">Maintenance Stage</div>
<div class="ci-divider"></div>
<ul class="ci-list">
<li>자산 정보 및 데이터 관리</li>
<li>예측 기반 유지보수 및 생애주기 분석</li>
<li>효율적인 시설 운영 및 지속가능한 관리</li>
</ul>
</div>
</div>
</div>
<style>
.block-card-image {
display: grid;
grid-template-columns: repeat(var(--ci-count, 3), 1fr);
gap: 16px;
}
.ci-card {
background: var(--color-bg, #ffffff);
border-radius: var(--radius, 8px);
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
overflow: hidden;
display: flex;
flex-direction: column;
}
.ci-img {
width: 100%;
height: 160px;
object-fit: contain;
background: #f8f9fb;
padding: 10px;
}
.ci-body {
padding: 16px;
flex: 1;
display: flex;
flex-direction: column;
}
.ci-title {
font-size: 14px;
font-weight: var(--weight-bold, 700);
text-decoration: underline;
text-underline-offset: 3px;
margin-bottom: 2px;
}
.ci-title-en {
font-size: 12px;
font-weight: var(--weight-normal, 400);
color: var(--color-text-secondary, #666);
margin-bottom: 10px;
}
.ci-divider {
width: 100%;
height: 1px;
background: #000;
margin-bottom: 10px;
}
.ci-list {
list-style: disc;
padding-left: 18px;
font-size: 13px;
line-height: 1.7;
color: var(--color-text, #000);
flex: 1;
}
.ci-list li {
margin-bottom: 3px;
}
.ci-source {
font-size: 11px;
color: var(--color-text-light, #94a3b8);
font-style: italic;
margin-top: 8px;
border-top: 1px solid var(--color-border, #e2e8f0);
padding-top: 6px;
}
</style>
</div>
</body>
</html>

View File

@@ -0,0 +1,162 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>cards/card-compare-3col</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 3단 비교 카드: 각각 다른 색상 헤더 + 아이콘/이미지 + 불릿 -->
<!--
📋 card-compare-3col
─────────────────
용도: 3개 카테고리 비교 (예: 상용SW / 3rd Party / 전문SW)
슬롯: cards[] (각 카드에 title, subtitle, color, image, bullets[])
Figma 원본: 2-2_03 "상용 / 3rd Party(범용) / 전문·전용 S/W"
-->
<div class="block-compare-3" style="--cc-count: 3">
<div class="cc-card">
<div class="cc-header" style="background: #6b7280">
<div class="cc-title">상용 S/W</div>
<div class="cc-sub">Commercial</div>
</div>
<ul class="cc-list">
<li>Autodesk, Bentley 등</li>
<li>범용 기능 제공</li>
<li>라이선스 비용 높음</li>
</ul>
</div>
<div class="cc-card">
<div class="cc-header" style="background: #2563eb">
<div class="cc-title">3rd Party 범용</div>
<div class="cc-sub">General Purpose</div>
</div>
<ul class="cc-list">
<li>Rhino, Sketchup 등</li>
<li>특정 분야 특화</li>
<li>상대적 저비용</li>
</ul>
</div>
<div class="cc-card">
<div class="cc-header" style="background: #dc2626">
<div class="cc-title">전문·전용 S/W</div>
<div class="cc-sub">Specialized</div>
</div>
<ul class="cc-list">
<li>자체 개발 솔루션</li>
<li>업무 프로세스 최적화</li>
<li>지속적 업그레이드 가능</li>
</ul>
</div>
</div>
<style>
.block-compare-3 {
display: grid;
grid-template-columns: repeat(var(--cc-count, 3), 1fr);
gap: 14px;
}
.cc-card {
border: 1px solid #e2e8f0;
border-radius: 10px;
overflow: hidden;
background: #ffffff;
}
.cc-header {
padding: 12px 16px;
text-align: center;
color: #ffffff;
}
.cc-title {
font-size: 15px;
font-weight: 800;
}
.cc-sub {
font-size: 11px;
opacity: 0.85;
margin-top: 2px;
}
.cc-img {
width: 100%;
height: 120px;
object-fit: contain;
background: #f8fafc;
padding: 8px;
}
.cc-list {
list-style: disc;
padding: 12px 16px 14px 30px;
font-size: 13px;
color: #334155;
line-height: 1.7;
}
.cc-list li {
margin-bottom: 3px;
white-space: pre-line;
}
</style></div></body></html>

View File

@@ -0,0 +1,174 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>cards/card-dark-overlay</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style>
</head>
<body><div class="block-container"><!-- 다크 오버레이 카드: 배경 이미지 + 흰 텍스트 오버레이 -->
<!--
📋 card-dark-overlay
─────────────────
용도: 키워드+짧은 설명을 시각적으로 강조. 이미지 위에 다크 오버레이 + 흰 텍스트.
슬롯: cards[] (각 카드에 image, title, description)
Figma 원본: 2-2_01 > 아이콘 카드 5열 (협업지원, 오류감소, 생산성향상 등)
-->
<div class="block-card-dark" style="--cd-count: 5">
<div class="cd-card">
<img class="cd-bg" src="../figma-assets/2-2_icon_card_1.png" alt="">
<div class="cd-overlay">
<div class="cd-title">협업지원</div>
<div class="cd-desc">팀원간 협업 원활히하여
프로젝트 관리 개선</div>
</div>
</div>
<div class="cd-card">
<img class="cd-bg" src="../figma-assets/2-2_icon_card_2.png" alt="">
<div class="cd-overlay">
<div class="cd-title">오류감소</div>
<div class="cd-desc">설계 오류 최소화
품질 향상</div>
</div>
</div>
<div class="cd-card">
<img class="cd-bg" src="../figma-assets/2-2_icon_card_3.png" alt="">
<div class="cd-overlay">
<div class="cd-title">생산성 향상</div>
<div class="cd-desc">반복적 작업의 자동화
시간과 노력 절약</div>
</div>
</div>
<div class="cd-card">
<img class="cd-bg" src="../figma-assets/2-2_icon_card_4.png" alt="">
<div class="cd-overlay">
<div class="cd-title">비용절감</div>
<div class="cd-desc">설계 변경 최소화
공사비 절감</div>
</div>
</div>
<div class="cd-card">
<img class="cd-bg" src="../figma-assets/2-2_icon_card_5.png" alt="">
<div class="cd-overlay">
<div class="cd-title">데이터 관리</div>
<div class="cd-desc">체계적 정보 관리
생애주기 활용</div>
</div>
</div>
</div>
<style>
.block-card-dark {
display: grid;
grid-template-columns: repeat(var(--cd-count, 3), 1fr);
gap: 12px;
}
.cd-card {
position: relative;
border-radius: 10px;
overflow: hidden;
height: 200px;
}
.cd-bg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
z-index: 1;
}
.cd-bg-default {
background: linear-gradient(135deg, #1e3a5f 0%, #2c5282 100%);
}
.cd-overlay {
position: absolute;
inset: 0;
z-index: 2;
background: linear-gradient(180deg, rgba(0,0,0,0.15) 0%, rgba(0,0,0,0.55) 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 16px;
text-align: center;
color: #ffffff;
}
.cd-title {
font-size: 18px;
font-weight: 800;
line-height: 1.3;
margin-bottom: 6px;
text-shadow: 0 1px 4px rgba(0,0,0,0.3);
}
.cd-desc {
font-size: 12px;
font-weight: 400;
line-height: 1.5;
opacity: 0.9;
text-shadow: 0 1px 3px rgba(0,0,0,0.3);
white-space: pre-line;
}
</style></div></body>
</html>

View File

@@ -0,0 +1,114 @@
<!DOCTYPE html>
<html lang="ko">
<head><meta charset="UTF-8"><title>cards/card-icon-desc</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head>
<body><div class="block-container"><!-- 아이콘 설명 카드: 아이콘 + 제목 + 설명 (2~4열) -->
<!--
📋 card-icon-desc
─────────────────
용도: 기능/특성/장점을 아이콘과 함께 나열. 시각적으로 분류.
슬롯: cards[] (각 카드에 icon, title, description)
Figma 원본: 2-3_01 아이콘 3열 설명
-->
<div class="block-card-icon" style="--ci-count: 3">
<div class="cid-card">
<div class="cid-icon">🔧</div>
<div class="cid-title">기술 기반</div>
<div class="cid-desc">건설 분야별
전문지식과 경험</div>
</div>
<div class="cid-card">
<div class="cid-icon">💻</div>
<div class="cid-title">S/W 역량</div>
<div class="cid-desc">디지털 도구와
Process 통합</div>
</div>
<div class="cid-card">
<div class="cid-icon">🌏</div>
<div class="cid-title">여건 조성</div>
<div class="cid-desc">사회·기업·제도
수용 환경 구축</div>
</div>
</div>
<style>
.block-card-icon {
display: grid;
grid-template-columns: repeat(var(--ci-count, 3), 1fr);
gap: 16px;
}
.cid-card {
text-align: center;
padding: 20px 16px;
background: #f8fafc;
border-radius: 8px;
border: 1px solid #e2e8f0;
}
.cid-icon {
font-size: 2.5rem;
margin-bottom: 10px;
}
.cid-title {
font-size: 15px;
font-weight: 700;
color: #1e293b;
margin-bottom: 6px;
}
.cid-desc {
font-size: 13px;
color: #475569;
line-height: 1.7;
white-space: pre-line;
}
</style></div></body></html>

View File

@@ -0,0 +1,199 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>cards/card-image-3col</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
.block-container .block-section-title { margin: -20px -20px 0; border-radius: 8px 8px 0 0; overflow: hidden; }
</style>
</head>
<body><div class="block-container"><!-- 이미지 카드: 상단 이미지 + 하단 텍스트 (2~4열) -->
<!--
📋 card-image
─────────────────
용도: 단계별 설명, 카테고리별 설명 (이미지가 핵심인 카드)
슬롯: cards[] 배열 (각 카드에 image, title, title_en, items[])
Figma 원본: 2-1_02 > Group 1171281594 (카드 3열)
-->
<div class="block-card-image" style="--ci-count: 3">
<div class="ci-card">
<img class="ci-img" src="../figma-assets/card_img_design.png" alt="설계단계">
<div class="ci-body">
<div class="ci-title" style="color: #00aaff">설계단계</div>
<div class="ci-title-en">Design Stage</div>
<div class="ci-divider"></div>
<ul class="ci-list">
<li>고도화된 BIM 구현</li>
<li>최첨단 디지털트윈</li>
<li>시뮬레이션 분석 & 성능평가</li>
<li>지속가능한 인프라개발</li>
</ul>
</div>
</div>
<div class="ci-card">
<img class="ci-img" src="../figma-assets/card_img_construction.png" alt="시공 단계">
<div class="ci-body">
<div class="ci-title" style="color: #006aff">시공 단계</div>
<div class="ci-title-en">Construction Stage</div>
<div class="ci-divider"></div>
<ul class="ci-list">
<li>향상된 건설 계획과 공정 순서 관리</li>
<li>Big Room 등 환경을 통한 협업 및 조정</li>
<li>정확한 수량산출서와 비용 산정</li>
</ul>
</div>
</div>
<div class="ci-card">
<img class="ci-img" src="../figma-assets/card_img_maintenance.png" alt="유지관리 단계">
<div class="ci-body">
<div class="ci-title" style="color: #004cbe">유지관리 단계</div>
<div class="ci-title-en">Maintenance Stage</div>
<div class="ci-divider"></div>
<ul class="ci-list">
<li>자산 정보 및 데이터 관리</li>
<li>예측 기반 유지보수 및 생애주기 분석</li>
<li>효율적인 시설 운영 및 지속가능한 관리</li>
</ul>
</div>
</div>
</div>
<style>
.block-card-image {
display: grid;
grid-template-columns: repeat(var(--ci-count, 3), 1fr);
gap: 16px;
}
.ci-card {
background: var(--color-bg, #ffffff);
border-radius: var(--radius, 8px);
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
overflow: hidden;
display: flex;
flex-direction: column;
}
.ci-img {
width: 100%;
height: 160px;
object-fit: contain;
background: #f8f9fb;
padding: 10px;
}
.ci-body {
padding: 16px;
flex: 1;
display: flex;
flex-direction: column;
}
.ci-title {
font-size: 14px;
font-weight: var(--weight-bold, 700);
text-decoration: underline;
text-underline-offset: 3px;
margin-bottom: 2px;
}
.ci-title-en {
font-size: 12px;
font-weight: var(--weight-normal, 400);
color: var(--color-text-secondary, #666);
margin-bottom: 10px;
}
.ci-divider {
width: 100%;
height: 1px;
background: #000;
margin-bottom: 10px;
}
.ci-list {
list-style: disc;
padding-left: 18px;
font-size: 13px;
line-height: 1.7;
color: var(--color-text, #000);
flex: 1;
}
.ci-list li {
margin-bottom: 3px;
}
.ci-source {
font-size: 11px;
color: var(--color-text-light, #94a3b8);
font-style: italic;
margin-top: 8px;
border-top: 1px solid var(--color-border, #e2e8f0);
padding-top: 6px;
}
</style></div></body>
</html>

View File

@@ -0,0 +1,133 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>cards/card-image-round</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 원형 이미지 카드: 원형 이미지 + 하단 제목/설명 -->
<!--
📋 card-image-round
─────────────────
용도: 포트폴리오형, 팀 소개, 가치/비전 표현 (원형 이미지가 핵심)
슬롯: cards[] (각 카드에 image, title, description)
Figma 원본: 1장_가치 하단 3열 원형 이미지 + 설명
-->
<div class="block-card-round" style="--cr-count: 3">
<div class="cr-card">
<div class="cr-img-wrap">
<img src="../figma-assets/card_img_design.png" alt="설계 혁신">
</div>
<div class="cr-title">설계 혁신</div>
<div class="cr-desc">3D 모델 기반
통합 설계</div>
</div>
<div class="cr-card">
<div class="cr-img-wrap">
<img src="../figma-assets/card_img_construction.png" alt="시공 효율">
</div>
<div class="cr-title">시공 효율</div>
<div class="cr-desc">디지털 관리
품질 향상</div>
</div>
<div class="cr-card">
<div class="cr-img-wrap">
<img src="../figma-assets/card_img_maintenance.png" alt="유지관리">
</div>
<div class="cr-title">유지관리</div>
<div class="cr-desc">생애주기
자산 관리</div>
</div>
</div>
<style>
.block-card-round {
display: grid;
grid-template-columns: repeat(var(--cr-count, 3), 1fr);
gap: 24px;
text-align: center;
}
.cr-card {
display: flex;
flex-direction: column;
align-items: center;
}
.cr-img-wrap {
width: 140px;
height: 140px;
border-radius: 50%;
overflow: hidden;
border: 3px solid #e2e8f0;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
margin-bottom: 12px;
}
.cr-img-wrap img {
width: 100%;
height: 100%;
object-fit: cover;
}
.cr-title {
font-size: 15px;
font-weight: 700;
color: #1e293b;
margin-bottom: 4px;
}
.cr-desc {
font-size: 13px;
color: #475569;
line-height: 1.6;
white-space: pre-line;
max-width: 200px;
}
</style></div></body></html>

View File

@@ -0,0 +1,136 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>cards/card-numbered</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 번호 카드: 순서 번호 + 제목 + 설명 (세로 나열) -->
<!--
📋 card-numbered
─────────────────
용도: 순서가 있는 항목 나열 (1. 2. 3.), 실행 조건, 요구사항
슬롯: items[] (각 항목에 title, description)
card-icon-desc와 다른 점: 아이콘 대신 순서 번호, 세로 나열
-->
<div class="block-card-num">
<div class="cn-item">
<div class="cn-number" style="background: #2563eb">1</div>
<div class="cn-body">
<div class="cn-title">요구사항 분석</div>
<div class="cn-desc">디지털 전환 목표 수립
사용자 요구사항 수집</div>
</div>
</div>
<div class="cn-item">
<div class="cn-number" style="background: #059669">2</div>
<div class="cn-body">
<div class="cn-title">S/W 개발</div>
<div class="cn-desc">건설산업 디지털화를 위한
솔루션 개발 및 업그레이드</div>
</div>
</div>
<div class="cn-item">
<div class="cn-number" style="background: #7c3aed">3</div>
<div class="cn-body">
<div class="cn-title">System 통합</div>
<div class="cn-desc">기존 시스템 호환성 분석
API 데이터 통합</div>
</div>
</div>
<div class="cn-item">
<div class="cn-number" style="background: #dc2626">4</div>
<div class="cn-body">
<div class="cn-title">교육/피드백</div>
<div class="cn-desc">사용자 교육 프로그램
지속적 모니터링 & 개선</div>
</div>
</div>
</div>
<style>
.block-card-num {
display: flex;
flex-direction: column;
gap: 10px;
}
.cn-item {
display: flex;
gap: 14px;
align-items: flex-start;
padding: 12px 16px;
background: #f8fafc;
border-radius: 8px;
border: 1px solid #e2e8f0;
}
.cn-number {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
font-size: 14px;
font-weight: 800;
flex-shrink: 0;
}
.cn-title {
font-size: 15px;
font-weight: 700;
color: #1e293b;
margin-bottom: 2px;
}
.cn-desc {
font-size: 13px;
color: #475569;
line-height: 1.7;
white-space: pre-line;
}
</style></div></body></html>

View File

@@ -0,0 +1,129 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>cards/card-stat-number</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 통계 숫자 카드: 큰 숫자 + 라벨 + 설명 -->
<!--
📋 card-stat-number
─────────────────
용도: KPI, 성과 수치, 목표 달성률, 비용 절감율 등 핵심 지표 강조
슬롯: stats[] (각 항목에 number, unit, label, description, color)
Figma 참고: 건설 정책 수치 (30% 절감, 40% 감소 등)
-->
<div class="block-stat" style="--st-count: 4">
<div class="st-card">
<div class="st-number" style="color: #2563eb">
30<span class="st-unit">%</span>
</div>
<div class="st-label">공기/공사비 절감</div>
</div>
<div class="st-card">
<div class="st-number" style="color: #059669">
40<span class="st-unit">%</span>
</div>
<div class="st-label">안전사고 감소</div>
</div>
<div class="st-card">
<div class="st-number" style="color: #7c3aed">
220<span class="st-unit">명+</span>
</div>
<div class="st-label">IT+CIVIL ENG 인력</div>
</div>
<div class="st-card">
<div class="st-number" style="color: #dc2626">
80<span class="st-unit">개+</span>
</div>
<div class="st-label">전용 S/W 개발</div>
</div>
</div>
<style>
.block-stat {
display: grid;
grid-template-columns: repeat(var(--st-count, 3), 1fr);
gap: 16px;
}
.st-card {
text-align: center;
padding: 20px 12px;
background: #f8fafc;
border-radius: 10px;
border: 1px solid #e2e8f0;
}
.st-number {
font-size: 36px;
font-weight: 900;
line-height: 1.2;
}
.st-unit {
font-size: 18px;
font-weight: 700;
margin-left: 2px;
}
.st-label {
font-size: 14px;
font-weight: 700;
color: #1e293b;
margin-top: 6px;
}
.st-desc {
font-size: 12px;
color: #64748b;
margin-top: 4px;
line-height: 1.5;
white-space: pre-line;
}
</style></div></body></html>

View File

@@ -0,0 +1,170 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>cards/card-step-vertical</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 세로 단계 카드: 좌측 단계 마커 + 우측 이미지/텍스트 -->
<!--
📋 card-step-vertical
─────────────────
용도: 생애주기 단계별 설명, 시간순 프로세스 (설계→시공→운영→유지관리)
슬롯: steps[] (각 단계에 phase, title, description, image, color)
Figma 원본: 2-3_04 "건설 생애주기와 정보모델 연계" (설계단계/시공단계/운영관리/유지관리)
-->
<div class="block-step-v">
<div class="sv-step">
<div class="sv-marker" style="background: #00aaff">
<div class="sv-phase">설계단계</div>
</div>
<div class="sv-content">
<div class="sv-title">BIM 기반 3D 설계</div>
<div class="sv-desc">• 고도화된 BIM 구현
• 시뮬레이션 분석 & 성능평가</div>
</div>
</div>
<div class="sv-connector">
<div class="sv-line" style="background: #00aaff"></div>
</div>
<div class="sv-step">
<div class="sv-marker" style="background: #006aff">
<div class="sv-phase">시공단계</div>
</div>
<div class="sv-content">
<div class="sv-title">현장 디지털 관리</div>
<div class="sv-desc">• 공정 순서 관리
• Big Room 협업 환경</div>
</div>
</div>
<div class="sv-connector">
<div class="sv-line" style="background: #006aff"></div>
</div>
<div class="sv-step">
<div class="sv-marker" style="background: #004cbe">
<div class="sv-phase">운영단계</div>
</div>
<div class="sv-content">
<div class="sv-title">디지털 트윈 운영</div>
<div class="sv-desc">• 실시간 모니터링
• 예측 기반 유지보수</div>
</div>
</div>
</div>
<style>
.block-step-v {
display: flex;
flex-direction: column;
gap: 0;
}
.sv-step {
display: flex;
gap: 16px;
align-items: flex-start;
}
.sv-marker {
width: 100px;
flex-shrink: 0;
border-radius: 8px;
padding: 10px 12px;
text-align: center;
color: #ffffff;
}
.sv-phase {
font-size: 13px;
font-weight: 700;
white-space: nowrap;
}
.sv-content {
flex: 1;
background: #f8fafc;
border-radius: 8px;
padding: 14px;
border: 1px solid #e2e8f0;
}
.sv-title {
font-size: 15px;
font-weight: 700;
color: #1e293b;
margin-bottom: 8px;
}
.sv-img {
width: 100%;
max-height: 150px;
object-fit: cover;
border-radius: 6px;
margin-bottom: 8px;
}
.sv-desc {
font-size: 13px;
color: #475569;
line-height: 1.7;
white-space: pre-line;
}
.sv-connector {
display: flex;
justify-content: 50px;
padding-left: 48px;
height: 20px;
}
.sv-line {
width: 3px;
height: 100%;
border-radius: 2px;
opacity: 0.4;
}
</style></div></body></html>

View File

@@ -0,0 +1,132 @@
<!DOCTYPE html>
<html lang="ko">
<head><meta charset="UTF-8"><title>cards/card-tag-image</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head>
<body><div class="block-container"><!-- 태그 카드: 상단 태그 라벨 + 이미지 + 설명 -->
<!--
📋 card-tag-image
─────────────────
용도: 카테고리별 분류 (제조/건축/토목 등), 태그로 구분되는 항목
슬롯: cards[] (각 카드에 tag, tag_color, image, title, description)
Figma 원본: 2-3_01 "산업별 특성과 현장의 모습" (제조, 건축, 인프라/토목)
-->
<div class="block-card-tag" style="--ct-count: 3">
<div class="ct-card">
<div class="ct-tag" style="background: #2563eb">제조</div>
<div class="ct-title">제조업 현장</div>
<div class="ct-desc">반복적 공정
표준화된 부품
자동화 라인</div>
</div>
<div class="ct-card">
<div class="ct-tag" style="background: #059669">건축</div>
<div class="ct-title">건축 현장</div>
<div class="ct-desc">수직적 작업공간
Library 기반 설계
반복 요소 활용</div>
</div>
<div class="ct-card">
<div class="ct-tag" style="background: #dc2626">인프라/토목</div>
<div class="ct-title">인프라 현장</div>
<div class="ct-desc">수평적 작업공간
비반복적 공정
GIS 기반 위치정보 필수</div>
</div>
</div>
<style>
.block-card-tag {
display: grid;
grid-template-columns: repeat(var(--ct-count, 3), 1fr);
gap: 16px;
}
.ct-card {
background: #ffffff;
border: 1px solid #e2e8f0;
border-radius: 8px;
overflow: hidden;
display: flex;
flex-direction: column;
}
.ct-tag {
display: inline-block;
padding: 4px 14px;
font-size: 12px;
font-weight: 700;
color: #ffffff;
border-radius: 0 0 8px 0;
align-self: flex-start;
}
.ct-img {
width: 100%;
height: 140px;
object-fit: cover;
}
.ct-title {
font-size: 14px;
font-weight: 700;
color: #1e293b;
padding: 10px 14px 4px;
}
.ct-desc {
font-size: 13px;
color: #475569;
line-height: 1.7;
padding: 0 14px 14px;
white-space: pre-line;
}
</style></div></body></html>

View File

@@ -0,0 +1,137 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>cards/card-text-grid</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
.block-container .block-section-title { margin: -20px -20px 0; border-radius: 8px 8px 0 0; overflow: hidden; }
</style>
</head>
<body><div class="block-container"><!-- 카드 그리드 블록: 2~4열 카드 배열 -->
<div class="block-card-grid" style="--card-count: 3">
<div class="card" style="border-top-color: None">
<div class="card-title">건설산업</div>
<span class="card-category">종합산업</span>
<div class="card-description">다양한 시설물을 광범위한 기술을 통합·융합하여 만들어내는 종합산업</div>
</div>
<div class="card" style="border-top-color: None">
<div class="card-title">BIM</div>
<span class="card-category">핵심 인프라 기술</span>
<div class="card-description">시설물 생애주기 정보를 3D 모델 기반으로 통합·관리하는 도구</div>
<div class="card-source">국토교통부, 2020</div>
</div>
<div class="card" style="border-top-color: None">
<div class="card-title">DX</div>
<span class="card-category">패러다임 변화</span>
<div class="card-description">디지털 기술 기반으로 업무방식과 가치 창출 구조를 전환하는 과정</div>
<div class="card-source">IBM, 2011</div>
</div>
</div>
<style>
.block-card-grid {
display: grid;
grid-template-columns: repeat(var(--card-count, 3), 1fr);
gap: var(--spacing-inner);
height: 100%;
}
.card {
background: var(--color-bg);
border: var(--border-width) solid var(--color-border);
border-top: var(--accent-border) solid var(--color-accent);
border-radius: var(--radius);
padding: var(--spacing-inner);
display: flex;
flex-direction: column;
}
.card-icon {
font-size: 1.5rem;
margin-bottom: var(--spacing-small);
}
.card-title {
font-size: var(--font-subtitle);
font-weight: var(--weight-bold);
color: var(--color-primary);
margin-bottom: 4px;
}
.card-category {
font-size: var(--font-small);
font-weight: var(--weight-medium);
color: var(--color-accent);
background: #dbeafe;
padding: 2px 8px;
border-radius: 12px;
display: inline-block;
margin-bottom: var(--spacing-small);
width: fit-content;
}
.card-description {
font-size: var(--font-body);
color: var(--color-text);
line-height: var(--line-height-ko);
flex: 1;
}
.card-source {
font-size: var(--font-small);
color: var(--color-text-light);
font-style: italic;
margin-top: var(--spacing-small);
border-top: var(--border-width) solid var(--color-border);
padding-top: var(--spacing-small);
}
</style></div></body>
</html>

View File

@@ -0,0 +1,133 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>블록 테스트: circle-label</title>
<style>
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body {
font-family: "Pretendard Variable", "Noto Sans KR", sans-serif;
background: #e8ecf0;
display: flex;
justify-content: center;
padding: 30px;
margin: 0;
}
.block-container {
width: 920px;
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
}
.block-container .block-section-title {
margin: -20px -20px 0;
border-radius: 8px 8px 0 0;
overflow: hidden;
}
</style>
</head>
<body>
<div class="block-container">
<!-- 원형 라벨: CSS 그라데이션 원 + 중앙 텍스트 -->
<!--
📋 circle-label
─────────────────
용도: 섹션 전환점, 핵심 키워드 강조, 시각적 구분자
슬롯: label (필수), sub_label (선택)
Figma 원본: 2-1_02 > Group 1171281590 (190x190)
-->
<div class="block-circle-label">
<div class="cl-outer">
<div class="cl-inner">
<div class="cl-text">단계별
BIM의 활용</div>
</div>
</div>
</div>
<style>
.block-circle-label {
display: flex;
justify-content: center;
padding: 20px 0;
}
.cl-outer {
width: 190px;
height: 190px;
border-radius: 50%;
background: linear-gradient(180deg, #3db8ff 0%, #006aff 100%);
box-shadow: 0 0 30px rgba(0, 106, 255, 0.25), 0 0 60px rgba(0, 106, 255, 0.1);
display: flex;
align-items: center;
justify-content: center;
}
.cl-inner {
width: 170px;
height: 170px;
border-radius: 50%;
background: linear-gradient(180deg, #4dc4ff 0%, #0080ff 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #ffffff;
text-align: center;
}
.cl-text {
font-size: 20px;
font-weight: var(--weight-bold, 700);
line-height: 1.4;
}
.cl-sub {
font-size: 12px;
opacity: 0.8;
margin-top: 4px;
}
</style>
</div>
</body>
</html>

View File

@@ -0,0 +1,124 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>compare-box</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style>
</head>
<body><div class="block-container"><!-- 비교 박스: 둥근 테두리 박스 2개 + VS 라벨 -->
<!--
📋 compare-box
─────────────────
용도: 2개 개념을 시각적으로 대비 (비교 테이블 위 헤더로 사용)
슬롯: left_label, left_sub, right_label, right_sub
Figma 원본: 2-1_02 > 하늘색 둥근 박스 2개 + 시안 텍스트
-->
<div class="block-compare-box">
<div class="cb-item">
<div class="cb-text">
<div class="cb-label">디지털 기술을 활용한
협업 프로세스</div>
</div>
</div>
<div class="cb-vs">VS</div>
<div class="cb-item">
<div class="cb-text">
<div class="cb-label">시설물의 전 생애주기 동안
정보의 생성 및 관리</div>
</div>
</div>
</div>
<style>
.block-compare-box {
display: flex;
gap: 16px;
align-items: center;
justify-content: center;
padding: 15px 0;
}
.cb-item {
width: 340px;
height: 110px;
border-radius: 55px;
border: 3px solid #7ec8f0;
background: linear-gradient(135deg, #e8f4fd 0%, #d4ecfa 100%);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 10px rgba(0, 140, 220, 0.1);
}
.cb-text {
text-align: center;
}
.cb-label {
font-size: 19px;
font-weight: 800;
color: #0090d0;
line-height: 1.4;
}
.cb-sub {
font-size: 13px;
color: #0090d0;
margin-top: 3px;
line-height: 1.5;
white-space: pre-line;
font-weight: 500;
}
.cb-vs {
font-size: 22px;
font-weight: 700;
color: #333;
flex-shrink: 0;
}
</style></div></body>
</html>

View File

@@ -0,0 +1,278 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>comparison-table</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style>
</head>
<body><div class="block-container"><!-- 비교 테이블: BIM vs DX 스타일 3단 테이블 -->
<!--
📋 comparison-table
─────────────────
용도: 다항목 비교 (좌측 A | 중앙 기준 | 우측 B)
슬롯: headers[] (3개), rows[][] (각 행 3칸)
Figma 원본: 2-1_02 > BIM VS D/X 테이블
특징: 중앙 칼럼에 파란 그라데이션 배지, 좌우 불릿 대비
-->
<div class="block-table-figma">
<table>
<thead>
<tr>
<th class="th-left">
BIM
</th>
<th class="th-center">
<span class="th-badge">VS</span>
</th>
<th class="th-right">
D/X
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="td-left">• Only 3D</td>
<td class="td-center">BIM · D/X</td>
<td class="td-right">• BIM ≪ D/X (ENG. + Management 포함)</td>
</tr>
<tr>
<td class="td-left">• 모델 제작용 상용 S/W
(Civil 3D, Revit, Navisworks, Autocad)</td>
<td class="td-center">S/W</td>
<td class="td-right">• 제작 및 운영(상용 + 전용 40~80개)
[Rhino, Sketchup, Blender...] + [EG-BIM 등]</td>
</tr>
<tr>
<td class="td-left">• 기존 2D 설계방식 유지</td>
<td class="td-center">프로세스</td>
<td class="td-right">• 근본적 문제의식을 통한 개선</td>
</tr>
<tr>
<td class="td-left">• 3D 모델 중심
• 기존 성과품 유지</td>
<td class="td-center">성과물</td>
<td class="td-right">• 공학 정보 및 콘텐츠 연계에 집중
• 도면, 수량, 시공계획 등 일식</td>
</tr>
<tr>
<td class="td-left">• 3D 모델에 의한 일반적 이해 향상</td>
<td class="td-center">활용</td>
<td class="td-right">• 설계/시공의 혁신(개념의 재정립)</td>
</tr>
<tr>
<td class="td-left">• (설계/시공/운영) 분야별 단절</td>
<td class="td-center">확장성</td>
<td class="td-right">• 전 생애주기 활용 시스템</td>
</tr>
<tr>
<td class="td-left">• 단순화(오류) - 수동적/집단적 동질화</td>
<td class="td-center">수행개념</td>
<td class="td-right">• 구체화(복잡) - 적극/구체적 실현 방안</td>
</tr>
<tr>
<td class="td-left">• 소극적, 상용 기술에 의존</td>
<td class="td-center">CIVIL + IT</td>
<td class="td-right">• 적극적, 주체적인 기술 접목/융합</td>
</tr>
<tr>
<td class="td-left">• S/W 제작사 판매 정책에 의존</td>
<td class="td-center">주체</td>
<td class="td-right">• 자체 수행능력 - 지속가능성 확보</td>
</tr>
<tr>
<td class="td-left">• 평준화, 국내 중심</td>
<td class="td-center">발주처</td>
<td class="td-right">• 차별화 및 경쟁력 확보, 해외 진출</td>
</tr>
<tr>
<td class="td-left">• 소규모 BIM팀 운영 + 단순교육에 집중</td>
<td class="td-center">설계사</td>
<td class="td-right">• IT + CIVIL ENG 220명 운영 + 기술 개발</td>
</tr>
<tr>
<td class="td-left">• 국내 토목 소극적/ 해외 토목증가</td>
<td class="td-center">시공사</td>
<td class="td-right">• 분야 확장 모델 및 시스템</td>
</tr>
</tbody>
</table>
</div>
<style>
.block-table-figma {
overflow: auto;
}
.block-table-figma table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
line-height: 1.7;
}
/* 헤더 */
.block-table-figma thead th {
padding: 14px 12px;
font-size: 16px;
font-weight: 700;
border-bottom: 2px solid #e8edf2;
}
.th-left {
text-align: center;
color: #6bcdff;
}
.th-center {
text-align: center;
width: 120px;
}
.th-badge {
display: inline-block;
background: linear-gradient(135deg, #006eff 0%, #00aaff 100%);
color: #ffffff;
font-size: 15px;
font-weight: 700;
padding: 8px 28px;
border-radius: 25px;
}
.th-right {
text-align: center;
color: #006eff;
}
/* 본문 */
.block-table-figma tbody td {
padding: 10px 14px;
border-bottom: 1px solid #f0f2f5;
vertical-align: middle;
}
.td-left {
text-align: center;
color: #444;
}
.td-center {
text-align: center;
font-weight: 700;
color: #333;
background: #f6f8fb;
font-size: 13px;
}
.td-right {
text-align: center;
color: #444;
}
.block-table-figma tbody tr:hover {
background: #fafbfd;
}
</style></div></body>
</html>

View File

@@ -0,0 +1,93 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>conclusion-bar</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style>
</head>
<body><div class="block-container"><!-- 결론 바: Figma 톤에 맞춘 하단 핵심 메시지 -->
<!--
📋 conclusion-bar
─────────────────
용도: 슬라이드/페이지 하단 핵심 한 줄 요약
슬롯: conclusion_text (필수), label (선택)
Figma 톤: 밝은 회색 배경 + 좌측 파란 액센트 라인 + 진한 텍스트
-->
<div class="block-conclusion-figma">
<div class="cf-label">핵심 요약</div>
<div class="cf-text">BIM은 건설산업의 디지털전환(DX)을 수행하는 과정에서 가장 기초가 되는 일부분이다</div>
</div>
<style>
.block-conclusion-figma {
background: #f4f6f9;
border-left: 4px solid #006aff;
border-radius: 0 8px 8px 0;
padding: 18px 28px;
display: flex;
flex-direction: column;
gap: 4px;
}
.cf-label {
font-size: 11px;
font-weight: 600;
color: #006aff;
text-transform: uppercase;
letter-spacing: 1px;
}
.cf-text {
font-size: 15px;
font-weight: 700;
color: #1e293b;
line-height: 1.6;
word-break: keep-all;
}
</style></div></body>
</html>

View File

@@ -0,0 +1,88 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>emphasis/banner-gradient</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style>
</head>
<body><div class="block-container"><!-- 그라데이션 배너 바: 전체 너비 파란 그라데이션 + 중앙 텍스트 -->
<!--
📋 banner-gradient
─────────────────
용도: 섹션 구분, 핵심 선언, 강조 문구를 전체 너비 배너로
슬롯: text (필수), sub_text (선택)
Figma 원본: 2-2_01 하단, 2-2_03 분류 바
-->
<div class="block-banner-grad">
<div class="bg-text">Engineering Software는 건설산업 DX를 실현하기 위한 핵심 도구입니다</div>
<div class="bg-sub">단순 도구가 아닌, 엔지니어의 전문지식과 프로세스를 디지털화하는 기반 기술</div>
</div>
<style>
.block-banner-grad {
background: linear-gradient(135deg, #006aff 0%, #00aaff 100%);
border-radius: 8px;
padding: 16px 30px;
text-align: center;
color: #ffffff;
}
.bg-text {
font-size: 16px;
font-weight: 700;
line-height: 1.5;
}
.bg-sub {
font-size: 12px;
font-weight: 400;
opacity: 0.85;
margin-top: 4px;
}
</style></div></body>
</html>

View File

@@ -0,0 +1,104 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>emphasis/callout-solution</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 솔루션 콜아웃: 강조 배경 + 아이콘 + 제목 + 설명 -->
<!--
📋 callout-solution
─────────────────
용도: 핵심 해결책/솔루션/방향성 강조. 눈에 띄는 콜아웃 박스.
슬롯: icon (선택), title (필수), description (필수), source (선택)
Figma 원본: 2-3_05 "Solution 제시 포인트"
-->
<div class="block-callout-sol">
<div class="cs-icon">💡</div>
<div class="cs-body">
<div class="cs-title">Solution 제시 포인트</div>
<div class="cs-desc">지금의 방식 속에서도 앞으로도 지속적으로 가능할까?
건설산업의 디지털 전환을 위해 근본적인 변화가 필요한 시점입니다.</div>
<div class="cs-source">건설산업 DX 전략 보고서</div>
</div>
</div>
<style>
.block-callout-sol {
background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
border: 2px solid #93c5fd;
border-radius: 12px;
padding: 20px 24px;
display: flex;
gap: 16px;
align-items: flex-start;
}
.cs-icon {
font-size: 2rem;
flex-shrink: 0;
margin-top: 2px;
}
.cs-body {
flex: 1;
}
.cs-title {
font-size: 17px;
font-weight: 800;
color: #1e40af;
margin-bottom: 6px;
}
.cs-desc {
font-size: 14px;
color: #334155;
line-height: 1.7;
white-space: pre-line;
word-break: keep-all;
}
.cs-source {
font-size: 11px;
color: #64748b;
font-style: italic;
margin-top: 8px;
}
</style></div></body></html>

View File

@@ -0,0 +1,95 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>emphasis/callout-warning</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 경고 콜아웃: 주의/경고/문제점 강조 -->
<!--
📋 callout-warning
─────────────────
용도: 문제점 지적, 주의사항, 잘못된 접근 경고
슬롯: title (필수), description (필수), icon (선택)
callout-solution과 다른 점: 경고 톤 (빨간/주황), 문제 지적용
-->
<div class="block-callout-warn">
<div class="cw-icon">⚠️</div>
<div class="cw-body">
<div class="cw-title">현재 접근 방식의 한계</div>
<div class="cw-desc">단순 BIM S/W 도입만으로는 DX를 달성할 수 없습니다.
기존 2D 설계방식 위에 3D를 덧씌우는 접근은
비용 증가와 효율 저하를 초래합니다.</div>
</div>
</div>
<style>
.block-callout-warn {
background: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%);
border: 2px solid #fca5a5;
border-radius: 12px;
padding: 20px 24px;
display: flex;
gap: 16px;
align-items: flex-start;
}
.cw-icon {
font-size: 2rem;
flex-shrink: 0;
}
.cw-body { flex: 1; }
.cw-title {
font-size: 17px;
font-weight: 800;
color: #991b1b;
margin-bottom: 6px;
}
.cw-desc {
font-size: 14px;
color: #7f1d1d;
line-height: 1.7;
white-space: pre-line;
word-break: keep-all;
}
</style></div></body></html>

View File

@@ -0,0 +1,111 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>emphasis/comparison-2col</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
.block-container .block-section-title { margin: -20px -20px 0; border-radius: 8px 8px 0 0; overflow: hidden; }
</style>
</head>
<body><div class="block-container"><!-- 비교 블록: 2단 병렬 레이아웃 -->
<div class="block-comparison">
<div class="comparison-left">
<div class="comparison-header comparison-header--left">잘못된 인식</div>
<div class="comparison-content">• BIM 기술 도입 = DX 완성
• DX를 단순 기술 도입으로 한정
• 개별 기술 중심의 접근</div>
</div>
<div class="comparison-divider"></div>
<div class="comparison-right">
<div class="comparison-header comparison-header--right">올바른 이해</div>
<div class="comparison-content">• DX는 BIM을 포함한 상위 개념
• 산업 패러다임의 근본적 변화
• GIS-BIM-디지털트윈 기술 융합 필수</div>
</div>
</div>
<style>
.block-comparison {
display: grid;
grid-template-columns: 1fr auto 1fr;
gap: var(--spacing-inner);
height: 100%;
}
.comparison-divider {
width: 1px;
background: var(--color-border);
}
.comparison-header {
font-size: var(--font-subtitle);
font-weight: var(--weight-bold);
padding-bottom: var(--spacing-small);
margin-bottom: var(--spacing-small);
border-bottom: var(--accent-border) solid;
}
.comparison-header--left {
border-color: var(--color-accent);
color: var(--color-accent);
}
.comparison-header--right {
border-color: var(--color-danger);
color: var(--color-danger);
}
.comparison-subtitle {
font-size: var(--font-caption);
color: var(--color-text-secondary);
margin-bottom: var(--spacing-small);
}
.comparison-content {
font-size: var(--font-body);
line-height: var(--line-height-ko);
}
</style></div></body>
</html>

View File

@@ -0,0 +1,94 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>emphasis/conclusion-accent-bar</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
.block-container .block-section-title { margin: -20px -20px 0; border-radius: 8px 8px 0 0; overflow: hidden; }
</style>
</head>
<body><div class="block-container"><!-- 결론 바: Figma 톤에 맞춘 하단 핵심 메시지 -->
<!--
📋 conclusion-bar
─────────────────
용도: 슬라이드/페이지 하단 핵심 한 줄 요약
슬롯: conclusion_text (필수), label (선택)
Figma 톤: 밝은 회색 배경 + 좌측 파란 액센트 라인 + 진한 텍스트
-->
<div class="block-conclusion-figma">
<div class="cf-label">핵심 요약</div>
<div class="cf-text">BIM은 건설산업의 디지털전환(DX)을 수행하는 과정에서 가장 기초가 되는 일부분이다</div>
</div>
<style>
.block-conclusion-figma {
background: #f4f6f9;
border-left: 4px solid #006aff;
border-radius: 0 8px 8px 0;
padding: 18px 28px;
display: flex;
flex-direction: column;
gap: 4px;
}
.cf-label {
font-size: 11px;
font-weight: 600;
color: #006aff;
text-transform: uppercase;
letter-spacing: 1px;
}
.cf-text {
font-size: 15px;
font-weight: 700;
color: #1e293b;
line-height: 1.6;
word-break: keep-all;
}
</style></div></body>
</html>

View File

@@ -0,0 +1,105 @@
<!DOCTYPE html>
<html lang="ko">
<head><meta charset="UTF-8"><title>emphasis/dark-bullet-list</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head>
<body><div class="block-container"><!-- 다크 배경 불릿 리스트: 짙은 배경 + 흰 텍스트 불릿 목록 -->
<!--
📋 dark-bullet-list
─────────────────
용도: 핵심 포인트를 짙은 배경 위에 강조. 시각적 무게감.
슬롯: title (선택), bullets[] (필수)
Figma 원본: 2-2_01 하단, 2-3_01 하단 다크 섹션
-->
<div class="block-dark-bullets">
<div class="db-title">건설산업 DX의 핵심 과제</div>
<ul class="db-list">
<li>기존 2D 설계방식의 근본적 한계 인식</li>
<li>3D 모델 기반의 정보 통합·관리 체계 구축</li>
<li>전 생애주기 데이터 연계 및 활용 시스템 도입</li>
<li>디지털 트윈 기반 시뮬레이션 및 의사결정 지원</li>
</ul>
</div>
<style>
.block-dark-bullets {
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
border-radius: 8px;
padding: 20px 28px;
color: #ffffff;
}
.db-title {
font-size: 16px;
font-weight: 700;
margin-bottom: 12px;
color: #93c5fd;
}
.db-list {
list-style: none;
padding: 0;
}
.db-list li {
font-size: 14px;
line-height: 1.8;
padding-left: 16px;
position: relative;
margin-bottom: 4px;
}
.db-list li::before {
content: '•';
position: absolute;
left: 0;
color: #60a5fa;
}
</style></div></body></html>

View File

@@ -0,0 +1,80 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>emphasis/divider-text</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 텍스트 구분선: 좌우 선 + 중앙 텍스트 -->
<!--
📋 divider-text
─────────────────
용도: 섹션 구분, 주제 전환, 시각적 휴식점
슬롯: text (필수)
-->
<div class="block-divider-text">
<div class="dt-line"></div>
<div class="dt-text">핵심 요약</div>
<div class="dt-line"></div>
</div>
<style>
.block-divider-text {
display: flex;
align-items: center;
gap: 16px;
padding: 8px 0;
}
.dt-line {
flex: 1;
height: 1px;
background: #cbd5e1;
}
.dt-text {
font-size: 13px;
font-weight: 600;
color: #64748b;
white-space: nowrap;
}
</style></div></body></html>

View File

@@ -0,0 +1,87 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>emphasis/highlight-strip</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 강조 스트립: 3구간 색상 분류 바 -->
<!--
📋 highlight-strip
─────────────────
용도: 카테고리별 색상 분류 (예: 상용/범용/전문), 비교 카드 상단 헤더
슬롯: segments[] (각 구간에 label, color)
Figma 원본: 2-2_03 "상용 | 3rd Party(범용) | 전문·전용 S/W" 색상 바
-->
<div class="block-strip">
<div class="strip-seg" style="background: #6b7280; flex: 1">
상용
</div>
<div class="strip-seg" style="background: #2563eb; flex: 1.5">
3rd Party(범용)
</div>
<div class="strip-seg" style="background: #dc2626; flex: 1.5">
전문·전용 S/W
</div>
</div>
<style>
.block-strip {
display: flex;
border-radius: 6px;
overflow: hidden;
}
.strip-seg {
padding: 10px 16px;
color: #ffffff;
font-size: 14px;
font-weight: 700;
text-align: center;
white-space: nowrap;
}
</style></div></body></html>

View File

@@ -0,0 +1,107 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>emphasis/quote-big-mark</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 큰따옴표 장식 인용: ❝❞ 큰따옴표 + 인용 텍스트 -->
<!--
📋 quote-big-mark
─────────────────
용도: 문제 제기, 핵심 발언, 임팩트 있는 인용
슬롯: quote_text (필수), source (선택)
Figma 원본: DX와 BIM 슬라이드 상단 인용 박스 (큰따옴표 장식)
-->
<div class="block-quote-big">
<div class="qb-mark qb-open"></div>
<div class="qb-content">
<div class="qb-text">건설산업의 디지털 전환 논의에서 DX와 BIM이 개념적으로 명확히 정립되지 않은 채 혼용되어 사용되고 있으며, 이로 인해 BIM 기술의 도입을 DX의 완성으로 오인하는 인식이 확산되고 있다</div>
<div class="qb-source">— 건설산업 DX 분석 보고서</div>
</div>
<div class="qb-mark qb-close"></div>
</div>
<style>
.block-quote-big {
background: #f8fafc;
border-radius: 10px;
padding: 24px 28px;
position: relative;
border: 1px solid #e2e8f0;
}
.qb-mark {
font-size: 3rem;
color: #cbd5e1;
font-weight: 900;
line-height: 1;
position: absolute;
}
.qb-open {
top: 8px;
left: 12px;
}
.qb-close {
bottom: -8px;
right: 16px;
}
.qb-content {
padding: 10px 30px 0;
}
.qb-text {
font-size: 15px;
font-weight: 500;
color: #1e293b;
line-height: 1.8;
word-break: keep-all;
white-space: pre-line;
}
.qb-source {
font-size: 12px;
color: #64748b;
font-style: italic;
margin-top: 10px;
text-align: right;
}
</style></div></body></html>

View File

@@ -0,0 +1,85 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>emphasis/quote-left-border</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
.block-container .block-section-title { margin: -20px -20px 0; border-radius: 8px 8px 0 0; overflow: hidden; }
</style>
</head>
<body><div class="block-container"><!-- 강조 인용 블록: 문제 제기, 핵심 메시지 -->
<div class="block-quote">
<div class="quote-text">BIM은 건설산업의 디지털전환을 수행하는 과정에서 가장 기초가 되는 일부분이다</div>
<div class="quote-source">건설산업 BIM 기본지침, 국토교통부, 2020</div>
</div>
<style>
.block-quote {
background: var(--color-bg-subtle);
border-left: var(--accent-border) solid var(--color-danger);
padding: var(--spacing-inner) var(--spacing-block);
border-radius: 0 var(--radius) var(--radius) 0;
display: flex;
flex-direction: column;
justify-content: center;
}
.quote-text {
font-size: var(--font-body);
color: var(--color-text);
line-height: var(--line-height-ko);
font-weight: var(--weight-medium);
}
.quote-source {
font-size: var(--font-caption);
color: var(--color-text-light);
font-style: italic;
margin-top: var(--spacing-small);
}
</style></div></body>
</html>

View File

@@ -0,0 +1,92 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>emphasis/quote-question</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style>
</head>
<body><div class="block-container"><!-- 질문형 강조 박스: 큰 질문 텍스트 + 부연 설명 -->
<!--
📋 quote-question
─────────────────
용도: 독자에게 질문 던지기, 문제 인식 유도, 전환점 강조
슬롯: question (필수), description (선택)
Figma 원본: 2-3_05 "지금의 방식으로도 가능할까?"
-->
<div class="block-quote-q">
<div class="qq-question">지금의 상황 속에서도 앞으로도,
지속적으로 가능할까?</div>
<div class="qq-desc">지금의 방식으로 복잡해지는 건설 환경에 대응할 수 있을지, 근본적인 변화가 필요한 시점입니다.</div>
</div>
<style>
.block-quote-q {
background: linear-gradient(135deg, #f0f7ff 0%, #e8f1fb 100%);
border: 2px solid #b8d4f0;
border-radius: 12px;
padding: 28px 36px;
text-align: center;
}
.qq-question {
font-size: 22px;
font-weight: 800;
color: #1e3a5f;
line-height: 1.5;
word-break: keep-all;
}
.qq-desc {
font-size: 14px;
color: #4a6b8a;
margin-top: 10px;
line-height: 1.7;
word-break: keep-all;
}
</style></div></body>
</html>

View File

@@ -0,0 +1,95 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>emphasis/tab-label-row</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 탭 라벨 행: 가로로 나열된 탭 버튼 형태 -->
<!--
📋 tab-label-row
─────────────────
용도: 카테고리 전환, 분류 표시, 선택된 항목 강조
슬롯: tabs[] (각 탭에 label, active, color)
Figma 원본: 2-3_02 상단 "건축과 인프라의 건설프로세스 특성" 탭, 2-2_01 탭
-->
<div class="block-tab-row">
<div class="tr-tab " style="">
제조
</div>
<div class="tr-tab " style="">
건축
</div>
<div class="tr-tab tr-active" style="background: #2563eb">
인프라/토목
</div>
</div>
<style>
.block-tab-row {
display: flex;
gap: 4px;
background: #f1f5f9;
border-radius: 8px;
padding: 4px;
}
.tr-tab {
flex: 1;
padding: 10px 16px;
text-align: center;
font-size: 14px;
font-weight: 600;
color: #64748b;
border-radius: 6px;
cursor: default;
}
.tr-active {
color: #ffffff;
font-weight: 700;
}
</style></div></body></html>

View File

@@ -0,0 +1,83 @@
<!DOCTYPE html>
<html lang="ko">
<head><meta charset="UTF-8"><title>headers/section-header-bar</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head>
<body><div class="block-container"><!-- 섹션 헤더 바: 파란 배경 + 흰 텍스트 제목 -->
<!--
📋 section-header-bar
─────────────────
용도: 섹션 시작부 강조 헤더. 전체 너비 파란 바 + 흰 대제목.
슬롯: title (필수), subtitle (선택)
Figma 원본: 2-2_01 "Eng. S/W의 구성", "Eng. S/W의 특성"
-->
<div class="block-section-bar">
<div class="sb-title">Eng. S/W의 구성</div>
<div class="sb-sub">Engineering Software Components</div>
</div>
<style>
.block-section-bar {
background: linear-gradient(135deg, #0d47a1 0%, #1565c0 100%);
border-radius: 6px;
padding: 14px 24px;
text-align: center;
}
.sb-title {
font-size: 18px;
font-weight: 800;
color: #ffffff;
line-height: 1.4;
}
.sb-sub {
font-size: 12px;
color: rgba(255,255,255,0.8);
margin-top: 4px;
}
</style></div></body></html>

View File

@@ -0,0 +1,123 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>headers/section-title-with-bg</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
.block-container .block-section-title { margin: -20px -20px 0; border-radius: 8px 8px 0 0; overflow: hidden; }
</style>
</head>
<body><div class="block-container"><!-- 섹션 타이틀: 배경 헤더 위 영문+한글 타이틀 오버레이 -->
<!--
📋 section-title
─────────────────
용도: 자세히보기 페이지 상단, 배경 이미지 위에 타이틀 표시
슬롯: title_ko (필수), title_en (선택), breadcrumb (선택), bg_image (선택)
Figma 원본: 공통 > section_title + bg 컴포넌트
-->
<div class="block-section-title">
<img class="st-bg" src="../figma-assets/bg_header.png" alt="">
<div class="st-breadcrumb">건설산업에서의 디지털전환 BIM</div>
<div class="st-text">
<div class="st-en">Building Information Modeling</div>
<div class="st-ko">건설정보모델링(BIM)</div>
</div>
</div>
<style>
.block-section-title {
position: relative;
width: 100%;
height: 500px;
overflow: hidden;
}
.st-bg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
z-index: 1;
}
.st-bg-default {
background: linear-gradient(135deg, #1e3a5f 0%, #2563eb 50%, #4dc4ff 100%);
}
.st-breadcrumb {
position: absolute;
top: 18px;
left: 89px;
z-index: 5;
font-size: 13px;
color: rgba(255,255,255,0.7);
}
.st-text {
position: absolute;
bottom: 40px;
left: 89px;
z-index: 5;
}
.st-en {
font-size: 15px;
font-weight: var(--weight-normal, 400);
color: #ffffff;
opacity: 0.85;
margin-bottom: 4px;
}
.st-ko {
font-size: 35px;
font-weight: var(--weight-bold, 700);
color: #ffffff;
line-height: 1.3;
}
</style></div></body>
</html>

View File

@@ -0,0 +1,91 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>headers/topic-center</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 중앙 정렬 꼭지 헤더: 대제목 + 하단 설명 -->
<!--
📋 topic-center
─────────────────
용도: 단독 강조 꼭지, 페이지 중심 주제 선언
슬롯: title (필수), subtitle (선택), description (선택)
Figma 원본: 2-2_02 "디지털전환을 위한 S/W 필요성"
-->
<div class="block-topic-center">
<div class="tc-title">디지털전환을 위한
S/W 필요성</div>
<div class="tc-sub">Software Requirements for DX</div>
<div class="tc-desc">건설산업의 DX를 실현하기 위해서는 기존 상용 S/W의 한계를 넘어
전문·전용 소프트웨어의 개발과 도입이 필수적입니다.</div>
</div>
<style>
.block-topic-center {
text-align: center;
padding: 20px 40px;
}
.tc-title {
font-size: 26px;
font-weight: 900;
color: #1e293b;
line-height: 1.4;
word-break: keep-all;
}
.tc-sub {
font-size: 14px;
color: #2563eb;
font-weight: 600;
margin-top: 6px;
}
.tc-desc {
font-size: 15px;
color: #475569;
line-height: 1.7;
margin-top: 12px;
word-break: keep-all;
white-space: pre-line;
}
</style></div></body></html>

View File

@@ -0,0 +1,95 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>headers/topic-left-right</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
.block-container .block-section-title { margin: -20px -20px 0; border-radius: 8px 8px 0 0; overflow: hidden; }
</style>
</head>
<body><div class="block-container"><!-- 꼭지 제목+설명: 좌측 질문/소제목 + 우측 설명 -->
<!--
📋 topic-header
─────────────────
용도: 각 꼭지의 시작부, 좌측에 파란 굵은 제목 + 우측에 본문 설명
슬롯: title (필수), description (필수)
비율: 좌 240px : 우 나머지
Figma 원본: sub_제목,내용 (742x68~78)
-->
<div class="block-topic-header">
<div class="th-title">단순 BIM의 적용이
D/X가 아닙니다</div>
<div class="th-desc">BIM은 건설산업의 디지털전환을 수행하는 과정에서 가장 기초가 되는 일부분임을 인지하는 것이 매우 중요합니다.</div>
</div>
<style>
.block-topic-header {
display: flex;
gap: 20px;
padding: 12px 0;
}
.th-title {
width: 240px;
flex-shrink: 0;
font-size: 24px;
font-weight: var(--weight-bold, 700);
color: var(--color-accent-deep, #004cbe);
line-height: 1.4;
word-break: keep-all;
}
.th-desc {
flex: 1;
font-size: 16px;
font-weight: var(--weight-normal, 400);
color: var(--color-text, #000000);
line-height: 1.7;
word-break: keep-all;
}
</style></div></body>
</html>

View File

@@ -0,0 +1,105 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>headers/topic-numbered</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 번호 꼭지 헤더: 번호 원형 + 제목 + 구분선 + 설명 -->
<!--
📋 topic-numbered
─────────────────
용도: 순서가 있는 꼭지 시작, 단계별 섹션 헤더
슬롯: number, title, description (선택), color (선택)
topic-left-right와 다른 점: 좌우 배치가 아닌 번호+세로 배치
-->
<div class="block-topic-num">
<div class="tn-header">
<div class="tn-number" style="background: #2563eb">1</div>
<div class="tn-title">건설산업의 디지털전환(DX)</div>
</div>
<div class="tn-divider" style="background: #2563eb"></div>
<div class="tn-desc">건설산업은 디지털 기술의 발전에 따라 빠르게 변화하고 있으며, 생산성 향상과 안전성 강화를 목표로 디지털전환이 가속화되고 있습니다.</div>
</div>
<style>
.block-topic-num {
padding: 8px 0;
}
.tn-header {
display: flex;
align-items: center;
gap: 12px;
}
.tn-number {
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
font-size: 16px;
font-weight: 800;
flex-shrink: 0;
}
.tn-title {
font-size: 20px;
font-weight: 800;
color: #1e293b;
}
.tn-divider {
height: 2px;
margin: 8px 0 10px;
border-radius: 1px;
opacity: 0.3;
}
.tn-desc {
font-size: 15px;
color: #475569;
line-height: 1.7;
white-space: pre-line;
word-break: keep-all;
}
</style></div></body></html>

View File

@@ -0,0 +1,120 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>블록 테스트: image-row</title>
<style>
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body {
font-family: "Pretendard Variable", "Noto Sans KR", sans-serif;
background: #e8ecf0;
display: flex;
justify-content: center;
padding: 30px;
margin: 0;
}
.block-container {
width: 920px;
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
}
.block-container .block-section-title {
margin: -20px -20px 0;
border-radius: 8px 8px 0 0;
overflow: hidden;
}
</style>
</head>
<body>
<div class="block-container">
<!-- 이미지 행: 2~4장 이미지 나란히 -->
<!--
📋 image-row
─────────────────
용도: 시공 사진, 근거 자료, 현장 이미지 나란히 배치
슬롯: images[] 배열 (각 이미지에 src, alt, caption)
Figma 원본: 2-1_02 > image grid (460x354 x 2)
-->
<div class="block-image-row" style="--ir-count: 2">
<div class="ir-item">
<img src="figma-assets/image_grid_left.png" alt="현장1">
</div>
<div class="ir-item">
<img src="figma-assets/image_grid_right.png" alt="현장2">
</div>
</div>
<style>
.block-image-row {
display: grid;
grid-template-columns: repeat(var(--ir-count, 2), 1fr);
gap: 0;
}
.ir-item {
overflow: hidden;
}
.ir-item img {
width: 100%;
height: 354px;
object-fit: cover;
display: block;
}
.ir-caption {
font-size: 11px;
color: var(--color-text-light, #94a3b8);
text-align: center;
padding: 4px;
}
</style>
</div>
</body>
</html>

View File

@@ -0,0 +1,108 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>media/image-before-after</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- Before/After 이미지: 좌측 Before + 우측 After + 화살표 -->
<!--
📋 image-before-after
─────────────────
용도: 변화 전후 비교, 공정 전후, 디지털 전환 전후
슬롯: before_src, before_label, after_src, after_label, caption (선택)
-->
<div class="block-ba">
<div class="ba-item">
<div class="ba-label ba-before">기존 방식</div>
<img src="../figma-assets/card_img_construction.png" alt="before">
</div>
<div class="ba-arrow"></div>
<div class="ba-item">
<div class="ba-label ba-after">DX 적용 후</div>
<img src="../figma-assets/card_img_design.png" alt="after">
</div>
</div>
<div class="ba-caption">건설 현장의 디지털 전환 전후 비교</div>
<style>
.block-ba {
display: flex;
gap: 12px;
align-items: center;
}
.ba-item {
flex: 1;
border-radius: 8px;
overflow: hidden;
border: 1px solid #e2e8f0;
}
.ba-label {
padding: 6px 14px;
font-size: 13px;
font-weight: 700;
text-align: center;
color: #ffffff;
}
.ba-before { background: #6b7280; }
.ba-after { background: #2563eb; }
.ba-item img {
width: 100%;
height: 180px;
object-fit: cover;
display: block;
}
.ba-arrow {
font-size: 2rem;
color: #2563eb;
font-weight: 900;
flex-shrink: 0;
}
.ba-caption {
font-size: 12px;
color: #64748b;
text-align: center;
margin-top: 8px;
}
</style></div></body></html>

View File

@@ -0,0 +1,80 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>media/image-full-caption</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 전체 너비 이미지 + 캡션 -->
<!--
📋 image-full-caption
─────────────────
용도: 핵심 도표, 대형 다이어그램, 전경 사진을 전체 너비로 표시
슬롯: src (필수), alt (선택), caption (선택)
Figma 원본: 2-3_05 하단 전경 사진, 2-3_03 하단 항공 사진
-->
<div class="block-img-full">
<img src="../figma-assets/image_grid_left.png" alt="건설 현장 전경">
<div class="if-caption">[사진] 인프라 건설 현장 전경 — 디지털 전환의 현장 적용</div>
</div>
<style>
.block-img-full {
width: 100%;
border-radius: 8px;
overflow: hidden;
}
.block-img-full img {
width: 100%;
height: auto;
display: block;
}
.if-caption {
font-size: 12px;
color: #64748b;
text-align: center;
padding: 6px;
background: #f8fafc;
}
</style></div></body></html>

View File

@@ -0,0 +1,111 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>media/image-grid-2x2</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style>
</head>
<body><div class="block-container"><!-- 이미지 2x2 그리드: 4장 이미지 격자 배치 -->
<!--
📋 image-grid-2x2
─────────────────
용도: 현장 사진 4장, 참고 이미지 4장, 사례 이미지 갤러리
슬롯: images[] (4개, 각각 src, alt, caption)
Figma 원본: 2-1_03 GIS 항공/위성/현장 사진
-->
<div class="block-img-2x2">
<div class="ig-item">
<img src="../figma-assets/image_grid_left.png" alt="현장1">
<div class="ig-caption">항공측량 데이터</div>
</div>
<div class="ig-item">
<img src="../figma-assets/image_grid_right.png" alt="현장2">
<div class="ig-caption">시공 현장</div>
</div>
<div class="ig-item">
<img src="../figma-assets/card_img_design.png" alt="설계">
<div class="ig-caption">3D 지형 모델</div>
</div>
<div class="ig-item">
<img src="../figma-assets/card_img_construction.png" alt="시공">
<div class="ig-caption">시공 BIM 모델</div>
</div>
</div>
<style>
.block-img-2x2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.ig-item {
overflow: hidden;
border-radius: 6px;
}
.ig-item img {
width: 100%;
height: 200px;
object-fit: cover;
display: block;
}
.ig-caption {
font-size: 11px;
color: #64748b;
text-align: center;
padding: 4px;
background: #f8f9fb;
}
</style></div></body>
</html>

View File

@@ -0,0 +1,100 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>media/image-row-2col</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
.block-container .block-section-title { margin: -20px -20px 0; border-radius: 8px 8px 0 0; overflow: hidden; }
</style>
</head>
<body><div class="block-container"><!-- 이미지 행: 2~4장 이미지 나란히 -->
<!--
📋 image-row
─────────────────
용도: 시공 사진, 근거 자료, 현장 이미지 나란히 배치
슬롯: images[] 배열 (각 이미지에 src, alt, caption)
Figma 원본: 2-1_02 > image grid (460x354 x 2)
-->
<div class="block-image-row" style="--ir-count: 2">
<div class="ir-item">
<img src="../figma-assets/image_grid_left.png" alt="시공 현장 1">
</div>
<div class="ir-item">
<img src="../figma-assets/image_grid_right.png" alt="시공 현장 2">
</div>
</div>
<style>
.block-image-row {
display: grid;
grid-template-columns: repeat(var(--ir-count, 2), 1fr);
gap: 0;
}
.ir-item {
overflow: hidden;
}
.ir-item img {
width: 100%;
height: 354px;
object-fit: cover;
display: block;
}
.ir-caption {
font-size: 11px;
color: var(--color-text-light, #94a3b8);
text-align: center;
padding: 4px;
}
</style></div></body>
</html>

View File

@@ -0,0 +1,130 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>media/image-side-text</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style>
</head>
<body><div class="block-container"><!-- 이미지+텍스트 가로 배치: 좌측 이미지 + 우측 텍스트 -->
<!--
📋 image-side-text
─────────────────
용도: 이미지에 대한 설명, 제품/시스템 소개, 참고 자료 설명
슬롯: image_src, image_alt, title, description, bullets[]
Figma 원본: 2-2_01 하단 이미지+텍스트 영역
-->
<div class="block-img-side">
<div class="is-image">
<img src="../figma-assets/card_img_design.png" alt="3D 지형 모델">
</div>
<div class="is-text">
<div class="is-title">고도화된 BIM 설계</div>
<div class="is-desc">최첨단 디지털트윈 기술을 활용하여 시뮬레이션 분석 및 성능평가를 수행합니다.</div>
<ul class="is-bullets">
<li>3D 지형 모델 기반 설계</li>
<li>시뮬레이션 분석 & 성능평가</li>
<li>지속가능한 인프라 개발</li>
</ul>
</div>
</div>
<style>
.block-img-side {
display: flex;
gap: 24px;
align-items: flex-start;
}
.is-image {
flex-shrink: 0;
width: 320px;
border-radius: 8px;
overflow: hidden;
}
.is-image img {
width: 100%;
height: auto;
display: block;
}
.is-text {
flex: 1;
padding-top: 4px;
}
.is-title {
font-size: 18px;
font-weight: 700;
color: #1e293b;
margin-bottom: 8px;
line-height: 1.4;
}
.is-desc {
font-size: 14px;
color: #444;
line-height: 1.7;
word-break: keep-all;
margin-bottom: 10px;
}
.is-bullets {
list-style: disc;
padding-left: 18px;
font-size: 13px;
color: #333;
line-height: 1.7;
}
.is-bullets li {
margin-bottom: 4px;
}
</style></div></body>
</html>

View File

@@ -0,0 +1,105 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>블록 테스트: quote-block</title>
<style>
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body {
font-family: "Pretendard Variable", "Noto Sans KR", sans-serif;
background: #e8ecf0;
display: flex;
justify-content: center;
padding: 30px;
margin: 0;
}
.block-container {
width: 920px;
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
}
.block-container .block-section-title {
margin: -20px -20px 0;
border-radius: 8px 8px 0 0;
overflow: hidden;
}
</style>
</head>
<body>
<div class="block-container">
<!-- 강조 인용 블록: 문제 제기, 핵심 메시지 -->
<div class="block-quote">
<div class="quote-text">BIM은 건설산업의 디지털전환을 수행하는 과정에서 가장 기초가 되는 일부분이다</div>
<div class="quote-source">건설산업 BIM 기본지침, 국토교통부, 2020</div>
</div>
<style>
.block-quote {
background: var(--color-bg-subtle);
border-left: var(--accent-border) solid var(--color-danger);
padding: var(--spacing-inner) var(--spacing-block);
border-radius: 0 var(--radius) var(--radius) 0;
display: flex;
flex-direction: column;
justify-content: center;
}
.quote-text {
font-size: var(--font-body);
color: var(--color-text);
line-height: var(--line-height-ko);
font-weight: var(--weight-medium);
}
.quote-source {
font-size: var(--font-caption);
color: var(--color-text-light);
font-style: italic;
margin-top: var(--spacing-small);
}
</style>
</div>
</body>
</html>

View File

@@ -0,0 +1,143 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>블록 테스트: section-title</title>
<style>
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body {
font-family: "Pretendard Variable", "Noto Sans KR", sans-serif;
background: #e8ecf0;
display: flex;
justify-content: center;
padding: 30px;
margin: 0;
}
.block-container {
width: 920px;
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
}
.block-container .block-section-title {
margin: -20px -20px 0;
border-radius: 8px 8px 0 0;
overflow: hidden;
}
</style>
</head>
<body>
<div class="block-container">
<!-- 섹션 타이틀: 배경 헤더 위 영문+한글 타이틀 오버레이 -->
<!--
📋 section-title
─────────────────
용도: 자세히보기 페이지 상단, 배경 이미지 위에 타이틀 표시
슬롯: title_ko (필수), title_en (선택), breadcrumb (선택), bg_image (선택)
Figma 원본: 공통 > section_title + bg 컴포넌트
-->
<div class="block-section-title">
<img class="st-bg" src="figma-assets/bg_header.png" alt="">
<div class="st-breadcrumb">건설산업에서의 디지털전환 BIM</div>
<div class="st-text">
<div class="st-en">Building Information Modeling</div>
<div class="st-ko">건설정보모델링(BIM)</div>
</div>
</div>
<style>
.block-section-title {
position: relative;
width: 100%;
height: 500px;
overflow: hidden;
}
.st-bg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
z-index: 1;
}
.st-bg-default {
background: linear-gradient(135deg, #1e3a5f 0%, #2563eb 50%, #4dc4ff 100%);
}
.st-breadcrumb {
position: absolute;
top: 18px;
left: 89px;
z-index: 5;
font-size: 13px;
color: rgba(255,255,255,0.7);
}
.st-text {
position: absolute;
bottom: 40px;
left: 89px;
z-index: 5;
}
.st-en {
font-size: 15px;
font-weight: var(--weight-normal, 400);
color: #ffffff;
opacity: 0.85;
margin-bottom: 4px;
}
.st-ko {
font-size: 35px;
font-weight: var(--weight-bold, 700);
color: #ffffff;
line-height: 1.3;
}
</style>
</div>
</body>
</html>

View File

@@ -0,0 +1,141 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>tables/compare-2col-split</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 2단 분할 비교: 좌측 GIS / 우측 BIM 같은 상세 비교 -->
<!--
📋 compare-2col-split
─────────────────
용도: 두 개념/기술의 상세 항목별 비교 (좌우 나란히, 중앙 기준 라벨)
슬롯: left_title, right_title, rows[] (각 행에 criteria, left, right)
Figma 원본: 2-3_03 GIS vs BIM 비교 (개념/분석/도면/발전 등)
-->
<div class="block-split-compare">
<div class="sc-header">
<div class="sc-h-left">GIS</div>
<div class="sc-h-center">구분</div>
<div class="sc-h-right">BIM</div>
</div>
<div class="sc-row sc-row-odd">
<div class="sc-left">지리적 데이터를
공간 분석하여 시각적 표현</div>
<div class="sc-center">개념</div>
<div class="sc-right">3D 모델 기반
건설 정보 통합 관리</div>
</div>
<div class="sc-row ">
<div class="sc-left">Surface Shape 분석
지형/지질 해석</div>
<div class="sc-center">분석</div>
<div class="sc-right">Object Shape 분석
구조/설비 해석</div>
</div>
<div class="sc-row sc-row-odd">
<div class="sc-left">GIS, 지도, 배치도</div>
<div class="sc-center">도면</div>
<div class="sc-right">CAD, BIM 모델</div>
</div>
<div class="sc-row ">
<div class="sc-left">GIS ↔ SPCC</div>
<div class="sc-center">발전</div>
<div class="sc-right">CAD → BIM</div>
</div>
</div>
<style>
.block-split-compare {
border: 1px solid #e2e8f0;
border-radius: 8px;
overflow: hidden;
}
.sc-header {
display: grid;
grid-template-columns: 1fr 100px 1fr;
background: linear-gradient(135deg, #0d47a1, #1565c0);
color: #ffffff;
font-weight: 700;
font-size: 15px;
text-align: center;
}
.sc-h-left, .sc-h-right, .sc-h-center {
padding: 12px;
}
.sc-h-center {
background: rgba(0,0,0,0.15);
font-size: 13px;
}
.sc-row {
display: grid;
grid-template-columns: 1fr 100px 1fr;
border-top: 1px solid #e2e8f0;
}
.sc-row-odd {
background: #f8fafc;
}
.sc-left, .sc-right {
padding: 10px 14px;
font-size: 13px;
line-height: 1.7;
color: #334155;
white-space: pre-line;
}
.sc-center {
padding: 10px 8px;
font-size: 12px;
font-weight: 700;
color: #1565c0;
text-align: center;
background: #f0f7ff;
border-left: 1px solid #e2e8f0;
border-right: 1px solid #e2e8f0;
}
</style></div></body></html>

View File

@@ -0,0 +1,219 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>tables/compare-3col-badge</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
.block-container .block-section-title { margin: -20px -20px 0; border-radius: 8px 8px 0 0; overflow: hidden; }
</style>
</head>
<body><div class="block-container"><!-- 비교 테이블: BIM vs DX 스타일 3단 테이블 -->
<!--
📋 comparison-table
─────────────────
용도: 다항목 비교 (좌측 A | 중앙 기준 | 우측 B)
슬롯: headers[] (3개), rows[][] (각 행 3칸)
Figma 원본: 2-1_02 > BIM VS D/X 테이블
특징: 중앙 칼럼에 파란 그라데이션 배지, 좌우 불릿 대비
-->
<div class="block-table-figma">
<table>
<thead>
<tr>
<th class="th-left">
BIM
</th>
<th class="th-center">
<span class="th-badge">VS</span>
</th>
<th class="th-right">
D/X
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="td-left">• Only 3D</td>
<td class="td-center">BIM · D/X</td>
<td class="td-right">• BIM ≪ D/X (ENG. + Management 포함)</td>
</tr>
<tr>
<td class="td-left">• 모델 제작용 상용 S/W
(Civil 3D, Revit, Navisworks)</td>
<td class="td-center">S/W</td>
<td class="td-right">• 제작 및 운영(상용 + 전용 40~80개)
[Rhino, Sketchup, Blender...]</td>
</tr>
<tr>
<td class="td-left">• 기존 2D 설계방식 유지</td>
<td class="td-center">프로세스</td>
<td class="td-right">• 근본적 문제의식을 통한 개선</td>
</tr>
<tr>
<td class="td-left">• 3D 모델 중심
• 기존 성과품 유지</td>
<td class="td-center">성과물</td>
<td class="td-right">• 공학 정보 및 콘텐츠 연계에 집중
• 도면, 수량, 시공계획 등 일식</td>
</tr>
<tr>
<td class="td-left">• 3D 모델에 의한 일반적 이해 향상</td>
<td class="td-center">활용</td>
<td class="td-right">• 설계/시공의 혁신(개념의 재정립)</td>
</tr>
<tr>
<td class="td-left">• (설계/시공/운영) 분야별 단절</td>
<td class="td-center">확장성</td>
<td class="td-right">• 전 생애주기 활용 시스템</td>
</tr>
</tbody>
</table>
</div>
<style>
.block-table-figma {
overflow: auto;
}
.block-table-figma table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
line-height: 1.7;
}
/* 헤더 */
.block-table-figma thead th {
padding: 14px 12px;
font-size: 16px;
font-weight: 700;
border-bottom: 2px solid #e8edf2;
}
.th-left {
text-align: center;
color: #6bcdff;
}
.th-center {
text-align: center;
width: 120px;
}
.th-badge {
display: inline-block;
background: linear-gradient(135deg, #006eff 0%, #00aaff 100%);
color: #ffffff;
font-size: 15px;
font-weight: 700;
padding: 8px 28px;
border-radius: 25px;
}
.th-right {
text-align: center;
color: #006eff;
}
/* 본문 */
.block-table-figma tbody td {
padding: 10px 14px;
border-bottom: 1px solid #f0f2f5;
vertical-align: middle;
}
.td-left {
text-align: center;
color: #444;
}
.td-center {
text-align: center;
font-weight: 700;
color: #333;
background: #f6f8fb;
font-size: 13px;
}
.td-right {
text-align: center;
color: #444;
}
.block-table-figma tbody tr:hover {
background: #fafbfd;
}
</style></div></body>
</html>

View File

@@ -0,0 +1,154 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>tables/table-simple-striped</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 심플 줄무늬 테이블: 범용 데이터 테이블 -->
<!--
📋 table-simple-striped
─────────────────
용도: 스펙, 수치, 일정, 항목별 데이터 나열
슬롯: headers[], rows[][]
compare-3col-badge와 다른 점: VS 배지 없음, 범용 데이터용
-->
<div class="block-table-striped">
<table>
<thead>
<tr>
<th>구분</th>
<th>현재</th>
<th>목표</th>
<th>비고</th>
</tr>
</thead>
<tbody>
<tr>
<td>BIM 적용률</td>
<td>30%</td>
<td>100%</td>
<td>2025 목표</td>
</tr>
<tr>
<td>디지털 전환 수준</td>
<td>초기</td>
<td>고도화</td>
<td>단계적 추진</td>
</tr>
<tr>
<td>전문 인력</td>
<td>50명</td>
<td>220명+</td>
<td>IT+CIVIL</td>
</tr>
<tr>
<td>자체 S/W</td>
<td>5개</td>
<td>80개+</td>
<td>지속 개발</td>
</tr>
</tbody>
</table>
</div>
<style>
.block-table-striped table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
line-height: 1.7;
}
.block-table-striped thead th {
background: #1e293b;
color: #ffffff;
font-weight: 700;
padding: 10px 14px;
text-align: left;
font-size: 13px;
}
.block-table-striped tbody td {
padding: 9px 14px;
border-bottom: 1px solid #e2e8f0;
white-space: pre-line;
color: #334155;
}
.block-table-striped tbody tr:nth-child(even) {
background: #f8fafc;
}
.block-table-striped tbody td:first-child {
font-weight: 600;
color: #1e293b;
}
</style></div></body></html>

View File

@@ -0,0 +1,115 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>블록 테스트: topic-header</title>
<style>
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body {
font-family: "Pretendard Variable", "Noto Sans KR", sans-serif;
background: #e8ecf0;
display: flex;
justify-content: center;
padding: 30px;
margin: 0;
}
.block-container {
width: 920px;
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
}
.block-container .block-section-title {
margin: -20px -20px 0;
border-radius: 8px 8px 0 0;
overflow: hidden;
}
</style>
</head>
<body>
<div class="block-container">
<!-- 꼭지 제목+설명: 좌측 질문/소제목 + 우측 설명 -->
<!--
📋 topic-header
─────────────────
용도: 각 꼭지의 시작부, 좌측에 파란 굵은 제목 + 우측에 본문 설명
슬롯: title (필수), description (필수)
비율: 좌 240px : 우 나머지
Figma 원본: sub_제목,내용 (742x68~78)
-->
<div class="block-topic-header">
<div class="th-title">단순 BIM의 적용이
D/X가 아닙니다</div>
<div class="th-desc">BIM은 건설산업의 디지털전환을 수행하는 과정에서 가장 기초가 되는 일부분임을 인지하는 것이 매우 중요합니다.</div>
</div>
<style>
.block-topic-header {
display: flex;
gap: 20px;
padding: 12px 0;
}
.th-title {
width: 240px;
flex-shrink: 0;
font-size: 24px;
font-weight: var(--weight-bold, 700);
color: var(--color-accent-deep, #004cbe);
line-height: 1.4;
word-break: keep-all;
}
.th-desc {
flex: 1;
font-size: 16px;
font-weight: var(--weight-normal, 400);
color: var(--color-text, #000000);
line-height: 1.7;
word-break: keep-all;
}
</style>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -0,0 +1,113 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>visuals/circle-gradient</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
.block-container .block-section-title { margin: -20px -20px 0; border-radius: 8px 8px 0 0; overflow: hidden; }
</style>
</head>
<body><div class="block-container"><!-- 원형 라벨: CSS 그라데이션 원 + 중앙 텍스트 -->
<!--
📋 circle-label
─────────────────
용도: 섹션 전환점, 핵심 키워드 강조, 시각적 구분자
슬롯: label (필수), sub_label (선택)
Figma 원본: 2-1_02 > Group 1171281590 (190x190)
-->
<div class="block-circle-label">
<div class="cl-outer">
<div class="cl-inner">
<div class="cl-text">단계별
BIM의 활용</div>
</div>
</div>
</div>
<style>
.block-circle-label {
display: flex;
justify-content: center;
padding: 20px 0;
}
.cl-outer {
width: 190px;
height: 190px;
border-radius: 50%;
background: linear-gradient(180deg, #3db8ff 0%, #006aff 100%);
box-shadow: 0 0 30px rgba(0, 106, 255, 0.25), 0 0 60px rgba(0, 106, 255, 0.1);
display: flex;
align-items: center;
justify-content: center;
}
.cl-inner {
width: 170px;
height: 170px;
border-radius: 50%;
background: linear-gradient(180deg, #4dc4ff 0%, #0080ff 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #ffffff;
text-align: center;
}
.cl-text {
font-size: 20px;
font-weight: var(--weight-bold, 700);
line-height: 1.4;
}
.cl-sub {
font-size: 12px;
opacity: 0.8;
margin-top: 4px;
}
</style></div></body>
</html>

View File

@@ -0,0 +1,131 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>visuals/compare-pill-pair</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style>
</head>
<body><div class="block-container"><!-- 비교 박스: 이중 테두리 둥근 박스 2개 + VS -->
<!--
📋 compare-pill-pair
─────────────────
용도: 2개 개념 시각적 대비 (비교 테이블 위 헤더)
슬롯: left_label, left_sub, right_label, right_sub
Figma 원본: 이중 테두리 (바깥 얇은 선 + 안쪽 둥근 박스)
-->
<div class="block-compare-pill">
<div class="cp-item">
<div class="cp-outer">
<div class="cp-inner">
<div class="cp-label">디지털 기술을 활용한
협업 프로세스</div>
</div>
</div>
</div>
<div class="cp-vs">VS</div>
<div class="cp-item">
<div class="cp-outer">
<div class="cp-inner">
<div class="cp-label">시설물의 전 생애주기 동안
정보의 생성 및 관리</div>
</div>
</div>
</div>
</div>
<style>
.block-compare-pill {
display: flex;
gap: 20px;
align-items: center;
justify-content: center;
padding: 20px 0;
}
.cp-item {
width: 350px;
}
.cp-outer {
border: 2px solid #a8d8f0;
border-radius: 60px;
padding: 6px;
background: transparent;
}
.cp-inner {
border: 2px solid #7ec8f0;
border-radius: 55px;
background: linear-gradient(135deg, #e8f4fd 0%, #d6edfa 100%);
padding: 18px 30px;
text-align: center;
}
.cp-label {
font-size: 18px;
font-weight: 800;
color: #0088cc;
line-height: 1.4;
white-space: pre-line;
}
.cp-sub {
font-size: 12px;
color: #0088cc;
margin-top: 4px;
line-height: 1.5;
white-space: pre-line;
font-weight: 500;
}
.cp-vs {
font-size: 24px;
font-weight: 800;
color: #333;
flex-shrink: 0;
}
</style></div></body>
</html>

View File

@@ -0,0 +1,104 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>visuals/flow-arrow-horizontal</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 가로 흐름도: 좌→우 화살표로 연결된 단계 (SVG) -->
<!--
📋 flow-arrow-horizontal
─────────────────
용도: 발전 과정, 기술 진화, 전환 흐름 (A → B → C)
슬롯: steps[] (각 단계에 label, sub)
Figma 원본: 2-3_03 "GIS ↔ SPCC → 토공 → ..." / 2-2_04 개발 패러다임
-->
<div class="block-flow-h">
<svg viewBox="0 0 720 80" width="100%" xmlns="http://www.w3.org/2000/svg" font-family="Pretendard Variable, sans-serif">
<rect x="10" y="10" width="120" height="50" rx="25" fill="#059669" opacity="0.9" />
<text x="70" y="32" text-anchor="middle" fill="white" font-size="13" font-weight="700">GIS</text>
<polygon points="135,35 150,35 145,28" fill="#94a3b8" />
<polygon points="135,35 150,35 145,42" fill="#94a3b8" />
<rect x="190" y="10" width="120" height="50" rx="25" fill="#2563eb" opacity="0.9" />
<text x="250" y="32" text-anchor="middle" fill="white" font-size="13" font-weight="700">SPCC</text>
<polygon points="315,35 330,35 325,28" fill="#94a3b8" />
<polygon points="315,35 330,35 325,42" fill="#94a3b8" />
<rect x="370" y="10" width="120" height="50" rx="25" fill="#7c3aed" opacity="0.9" />
<text x="430" y="32" text-anchor="middle" fill="white" font-size="13" font-weight="700">토공</text>
<polygon points="495,35 510,35 505,28" fill="#94a3b8" />
<polygon points="495,35 510,35 505,42" fill="#94a3b8" />
<rect x="550" y="10" width="120" height="50" rx="25" fill="#dc2626" opacity="0.9" />
<text x="610" y="32" text-anchor="middle" fill="white" font-size="13" font-weight="700">BIM</text>
</svg>
</div>
<style>
.block-flow-h {
padding: 10px 0;
overflow-x: auto;
}
.block-flow-h svg {
min-width: 400px;
}
</style></div></body></html>

View File

@@ -0,0 +1,153 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>visuals/keyword-circle-row</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 키워드 원형 행: 원 안에 키워드 + 하단 설명 (SVG) -->
<!--
📋 keyword-circle-row
─────────────────
용도: 핵심 키워드를 원형으로 나열하며 각각 설명. 약어 풀이.
슬롯: keywords[] (각 항목에 letter, label, description, color)
Figma 원본: 2-3_03 G-S-I-M 약어 설명, 2-2_04 개발 조건 키워드
-->
<div class="block-kw-circles">
<div class="kw-item">
<svg viewBox="0 0 80 80" width="70" height="70" xmlns="http://www.w3.org/2000/svg">
<defs>
<radialGradient id="kwGrad1" cx="40%" cy="35%" r="60%">
<stop offset="0%" stop-color="#6ee7b7" />
<stop offset="100%" stop-color="#059669" />
</radialGradient>
</defs>
<circle cx="40" cy="40" r="38" fill="url(#kwGrad1)" />
<text x="40" y="44" text-anchor="middle" dominant-baseline="central" fill="white" font-size="28" font-weight="900" font-family="Pretendard Variable, sans-serif">G</text>
</svg>
<div class="kw-label">Geographic</div>
<div class="kw-desc">지리적 위치
공간 정보</div>
</div>
<div class="kw-item">
<svg viewBox="0 0 80 80" width="70" height="70" xmlns="http://www.w3.org/2000/svg">
<defs>
<radialGradient id="kwGrad2" cx="40%" cy="35%" r="60%">
<stop offset="0%" stop-color="#93c5fd" />
<stop offset="100%" stop-color="#2563eb" />
</radialGradient>
</defs>
<circle cx="40" cy="40" r="38" fill="url(#kwGrad2)" />
<text x="40" y="44" text-anchor="middle" dominant-baseline="central" fill="white" font-size="28" font-weight="900" font-family="Pretendard Variable, sans-serif">S</text>
</svg>
<div class="kw-label">Structure</div>
<div class="kw-desc">구조물
형상 정보</div>
</div>
<div class="kw-item">
<svg viewBox="0 0 80 80" width="70" height="70" xmlns="http://www.w3.org/2000/svg">
<defs>
<radialGradient id="kwGrad3" cx="40%" cy="35%" r="60%">
<stop offset="0%" stop-color="#c4b5fd" />
<stop offset="100%" stop-color="#7c3aed" />
</radialGradient>
</defs>
<circle cx="40" cy="40" r="38" fill="url(#kwGrad3)" />
<text x="40" y="44" text-anchor="middle" dominant-baseline="central" fill="white" font-size="28" font-weight="900" font-family="Pretendard Variable, sans-serif">I</text>
</svg>
<div class="kw-label">Information</div>
<div class="kw-desc">건설 정보
속성 데이터</div>
</div>
<div class="kw-item">
<svg viewBox="0 0 80 80" width="70" height="70" xmlns="http://www.w3.org/2000/svg">
<defs>
<radialGradient id="kwGrad4" cx="40%" cy="35%" r="60%">
<stop offset="0%" stop-color="#fca5a5" />
<stop offset="100%" stop-color="#dc2626" />
</radialGradient>
</defs>
<circle cx="40" cy="40" r="38" fill="url(#kwGrad4)" />
<text x="40" y="44" text-anchor="middle" dominant-baseline="central" fill="white" font-size="28" font-weight="900" font-family="Pretendard Variable, sans-serif">M</text>
</svg>
<div class="kw-label">Model</div>
<div class="kw-desc">3D 모델
통합 관리</div>
</div>
</div>
<style>
.block-kw-circles {
display: flex;
gap: 24px;
justify-content: center;
flex-wrap: wrap;
padding: 10px 0;
}
.kw-item {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
width: 140px;
}
.kw-label {
font-size: 14px;
font-weight: 700;
color: #1e293b;
margin-top: 8px;
}
.kw-desc {
font-size: 12px;
color: #475569;
line-height: 1.5;
margin-top: 4px;
white-space: pre-line;
}
</style></div></body></html>

View File

@@ -0,0 +1,134 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>visuals/layer-diagram</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 레이어 다이어그램: 겹쳐진 레이어 표현 (SVG) -->
<!--
📋 layer-diagram
─────────────────
용도: GIS/BIM/DT 레이어 구조, 기술 스택, 계층 구조 시각화
슬롯: layers[] (각 레이어에 label, color), title (선택)
Figma 원본: 1장_1-1_미래 "GIS+BIM+DT 레이어 시각화"
-->
<div class="block-layer-diag">
<div class="ld-title">건설산업 DX 기술 레이어</div>
<svg viewBox="0 0 400 280" width="100%" xmlns="http://www.w3.org/2000/svg" font-family="Pretendard Variable, sans-serif">
<defs>
<linearGradient id="layerGrad1" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#7c3aed" stop-opacity="0.85" />
<stop offset="100%" stop-color="#7c3aed" stop-opacity="0.6" />
</linearGradient>
<linearGradient id="layerGrad2" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#2563eb" stop-opacity="0.85" />
<stop offset="100%" stop-color="#2563eb" stop-opacity="0.6" />
</linearGradient>
<linearGradient id="layerGrad3" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#059669" stop-opacity="0.85" />
<stop offset="100%" stop-color="#059669" stop-opacity="0.6" />
</linearGradient>
<linearGradient id="layerGrad4" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#475569" stop-opacity="0.85" />
<stop offset="100%" stop-color="#475569" stop-opacity="0.6" />
</linearGradient>
<filter id="layerShadow">
<feDropShadow dx="0" dy="2" stdDeviation="3" flood-opacity="0.15" />
</filter>
</defs>
<!-- 3D 효과: 사다리꼴 레이어 -->
<path d="M 40,185 L 360,185 L 340,225 L 60,225 Z"
fill="url(#layerGrad1)" filter="url(#layerShadow)" />
<text x="200" y="210" text-anchor="middle" fill="white" font-size="14" font-weight="700">Digital Twin (가상환경)</text>
<!-- 3D 효과: 사다리꼴 레이어 -->
<path d="M 55,130 L 345,130 L 325,170 L 75,170 Z"
fill="url(#layerGrad2)" filter="url(#layerShadow)" />
<text x="200" y="155" text-anchor="middle" fill="white" font-size="14" font-weight="700">BIM (건설정보모델링)</text>
<!-- 3D 효과: 사다리꼴 레이어 -->
<path d="M 70,75 L 330,75 L 310,115 L 90,115 Z"
fill="url(#layerGrad3)" filter="url(#layerShadow)" />
<text x="200" y="100" text-anchor="middle" fill="white" font-size="14" font-weight="700">GIS (공간정보시스템)</text>
<!-- 3D 효과: 사다리꼴 레이어 -->
<path d="M 85,20 L 315,20 L 295,60 L 105,60 Z"
fill="url(#layerGrad4)" filter="url(#layerShadow)" />
<text x="200" y="45" text-anchor="middle" fill="white" font-size="14" font-weight="700">실세계 인프라</text>
</svg>
</div>
<style>
.block-layer-diag {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
padding: 10px 0;
}
.ld-title {
font-size: 16px;
font-weight: 700;
color: #1e293b;
}
.block-layer-diag svg {
max-width: 400px;
}
</style></div></body></html>

View File

@@ -0,0 +1,142 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>visuals/process-horizontal</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
.block-container .block-section-title { margin: -20px -20px 0; border-radius: 8px 8px 0 0; overflow: hidden; }
</style>
</head>
<body><div class="block-container"><!-- 프로세스 블록: 가로 단계 흐름 -->
<div class="block-process">
<div class="process-step">
<div class="process-number">1</div>
<div class="process-title">요구사항 분석</div>
<div class="process-desc">목표 수립</div>
</div>
<div class="process-arrow"></div>
<div class="process-step">
<div class="process-number">2</div>
<div class="process-title">S/W 개발</div>
<div class="process-desc">솔루션 개발</div>
</div>
<div class="process-arrow"></div>
<div class="process-step">
<div class="process-number">3</div>
<div class="process-title">System 통합</div>
<div class="process-desc">데이터 통합</div>
</div>
<div class="process-arrow"></div>
<div class="process-step">
<div class="process-number">4</div>
<div class="process-title">교육/피드백</div>
<div class="process-desc">지속 개선</div>
</div>
</div>
<style>
.block-process {
display: flex;
align-items: center;
justify-content: center;
gap: var(--spacing-small);
height: 100%;
}
.process-step {
flex: 1;
text-align: center;
padding: var(--spacing-inner);
background: var(--color-bg-subtle);
border: var(--border-width) solid var(--color-border);
border-radius: var(--radius);
}
.process-number {
width: 36px;
height: 36px;
border-radius: 50%;
background: var(--color-accent);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: var(--weight-bold);
font-size: var(--font-body);
margin: 0 auto var(--spacing-small);
}
.process-title {
font-size: var(--font-body);
font-weight: var(--weight-bold);
color: var(--color-primary);
margin-bottom: 4px;
}
.process-desc {
font-size: var(--font-caption);
color: var(--color-text-secondary);
line-height: var(--line-height-ko);
}
.process-arrow {
font-size: 1.5rem;
color: var(--color-accent);
font-weight: var(--weight-bold);
flex-shrink: 0;
}
</style></div></body>
</html>

View File

@@ -0,0 +1,121 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>visuals/pyramid-hierarchy</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 피라미드 계층: 위에서 아래로 넓어지는 계층 구조 (SVG) -->
<!--
📋 pyramid-hierarchy
─────────────────
용도: 위계, 우선순위, 상위→하위 개념 (좁은→넓은)
슬롯: levels[] (상단부터, 각 레벨에 label, color)
-->
<div class="block-pyramid">
<svg viewBox="0 0 500 300" width="100%" xmlns="http://www.w3.org/2000/svg" font-family="Pretendard Variable, sans-serif">
<defs>
<linearGradient id="pyrGrad1" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#0d47a1" />
<stop offset="100%" stop-color="#0d47a1" stop-opacity="0.7" />
</linearGradient>
<linearGradient id="pyrGrad2" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#1565c0" />
<stop offset="100%" stop-color="#1565c0" stop-opacity="0.7" />
</linearGradient>
<linearGradient id="pyrGrad3" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#1976d2" />
<stop offset="100%" stop-color="#1976d2" stop-opacity="0.7" />
</linearGradient>
<linearGradient id="pyrGrad4" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#2196f3" />
<stop offset="100%" stop-color="#2196f3" stop-opacity="0.7" />
</linearGradient>
<filter id="pyrShadow">
<feDropShadow dx="0" dy="2" stdDeviation="3" flood-opacity="0.12" />
</filter>
</defs>
<rect x="190" y="10" width="120" height="50" rx="6" fill="url(#pyrGrad1)" filter="url(#pyrShadow)" />
<text x="250" y="40" text-anchor="middle" fill="white" font-size="14" font-weight="700">DX (디지털 전환)</text>
<rect x="135" y="75" width="230" height="50" rx="6" fill="url(#pyrGrad2)" filter="url(#pyrShadow)" />
<text x="250" y="105" text-anchor="middle" fill="white" font-size="14" font-weight="700">BIM + GIS + Digital Twin</text>
<rect x="80" y="140" width="340" height="50" rx="6" fill="url(#pyrGrad3)" filter="url(#pyrShadow)" />
<text x="250" y="170" text-anchor="middle" fill="white" font-size="14" font-weight="700">프로세스 혁신 + S/W 개발</text>
<rect x="25" y="205" width="450" height="50" rx="6" fill="url(#pyrGrad4)" filter="url(#pyrShadow)" />
<text x="250" y="235" text-anchor="middle" fill="white" font-size="14" font-weight="700">기반 인프라 + 인력 양성</text>
</svg>
</div>
<style>
.block-pyramid {
display: flex;
justify-content: center;
padding: 10px 0;
}
.block-pyramid svg {
max-width: 450px;
}
</style></div></body></html>

View File

@@ -0,0 +1,113 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>visuals/timeline-horizontal</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 가로 타임라인: 좌→우 시간축 + 마커 + 라벨 (SVG) -->
<!--
📋 timeline-horizontal
─────────────────
용도: 연도별 로드맵, 짧은 일정, 마일스톤 (가로 배치)
슬롯: events[] (각 이벤트에 year, title, color)
timeline-vertical과 다른 점: 가로 방향, 공간 효율적
-->
<div class="block-timeline-h">
<svg viewBox="0 0 680 100" width="100%" xmlns="http://www.w3.org/2000/svg" font-family="Pretendard Variable, sans-serif">
<!-- 가로 선 -->
<line x1="30" y1="40" x2="630" y2="40" stroke="#cbd5e1" stroke-width="2" />
<!-- 마커 -->
<circle cx="60" cy="40" r="12" fill="#059669" />
<circle cx="60" cy="40" r="5" fill="white" />
<!-- 연도 -->
<text x="60" y="22" text-anchor="middle" fill="#059669" font-size="12" font-weight="800">2020</text>
<!-- 제목 -->
<text x="60" y="65" text-anchor="middle" fill="#1e293b" font-size="12" font-weight="600">BIM 기본지침</text>
<!-- 마커 -->
<circle cx="220" cy="40" r="12" fill="#2563eb" />
<circle cx="220" cy="40" r="5" fill="white" />
<!-- 연도 -->
<text x="220" y="22" text-anchor="middle" fill="#2563eb" font-size="12" font-weight="800">2022</text>
<!-- 제목 -->
<text x="220" y="65" text-anchor="middle" fill="#1e293b" font-size="12" font-weight="600">스마트건설 방안</text>
<!-- 마커 -->
<circle cx="380" cy="40" r="12" fill="#7c3aed" />
<circle cx="380" cy="40" r="5" fill="white" />
<!-- 연도 -->
<text x="380" y="22" text-anchor="middle" fill="#7c3aed" font-size="12" font-weight="800">2023</text>
<!-- 제목 -->
<text x="380" y="65" text-anchor="middle" fill="#1e293b" font-size="12" font-weight="600">7차 기본계획</text>
<!-- 마커 -->
<circle cx="540" cy="40" r="12" fill="#dc2626" />
<circle cx="540" cy="40" r="5" fill="white" />
<!-- 연도 -->
<text x="540" y="22" text-anchor="middle" fill="#dc2626" font-size="12" font-weight="800">2025</text>
<!-- 제목 -->
<text x="540" y="65" text-anchor="middle" fill="#1e293b" font-size="12" font-weight="600">전면 BIM</text>
</svg>
</div>
<style>
.block-timeline-h {
padding: 10px 0;
overflow-x: auto;
}
.block-timeline-h svg {
min-width: 500px;
}
</style></div></body></html>

View File

@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>visuals/timeline-vertical</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 세로 타임라인: 좌측 선 + 원형 마커 + 우측 내용 (SVG 마커) -->
<!--
📋 timeline-vertical
─────────────────
용도: 연혁, 정책 시행 일정, 로드맵, 연도별 사건
슬롯: events[] (각 이벤트에 year, title, description, color)
Figma 참고: 정책 로드맵, 건설 정책 추진현황
-->
<div class="block-timeline-v">
<div class="tv-event">
<div class="tv-marker-col">
<svg viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" fill="#059669" />
<circle cx="12" cy="12" r="5" fill="white" />
</svg>
<div class="tv-line" style="background: #059669"></div>
</div>
<div class="tv-content">
<div class="tv-year" style="color: #059669">2020</div>
<div class="tv-title">건설산업 BIM 기본지침</div>
<div class="tv-desc">BIM 도입의 법적 근거 마련</div>
</div>
</div>
<div class="tv-event">
<div class="tv-marker-col">
<svg viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" fill="#2563eb" />
<circle cx="12" cy="12" r="5" fill="white" />
</svg>
<div class="tv-line" style="background: #2563eb"></div>
</div>
<div class="tv-content">
<div class="tv-year" style="color: #2563eb">2022.07</div>
<div class="tv-title">스마트 건설 활성화 방안</div>
<div class="tv-desc">BIM 전면 도입 + 전문인력 양성</div>
</div>
</div>
<div class="tv-event">
<div class="tv-marker-col">
<svg viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" fill="#7c3aed" />
<circle cx="12" cy="12" r="5" fill="white" />
</svg>
<div class="tv-line" style="background: #7c3aed"></div>
</div>
<div class="tv-content">
<div class="tv-year" style="color: #7c3aed">2023.12</div>
<div class="tv-title">제7차 건설기술진흥 기본계획</div>
<div class="tv-desc">디지털 전환을 통한 스마트 건설 확산</div>
</div>
</div>
<div class="tv-event">
<div class="tv-marker-col">
<svg viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" fill="#dc2626" />
<circle cx="12" cy="12" r="5" fill="white" />
</svg>
</div>
<div class="tv-content">
<div class="tv-year" style="color: #dc2626">2025</div>
<div class="tv-title">전면 BIM 적용</div>
<div class="tv-desc">공공 건설사업 BIM 의무화 목표</div>
</div>
</div>
</div>
<style>
.block-timeline-v {
display: flex;
flex-direction: column;
gap: 0;
padding: 10px 0;
}
.tv-event {
display: flex;
gap: 14px;
}
.tv-marker-col {
display: flex;
flex-direction: column;
align-items: center;
flex-shrink: 0;
width: 24px;
}
.tv-line {
width: 2px;
flex: 1;
min-height: 20px;
opacity: 0.3;
border-radius: 1px;
}
.tv-content {
padding-bottom: 20px;
flex: 1;
}
.tv-year {
font-size: 13px;
font-weight: 800;
margin-bottom: 2px;
}
.tv-title {
font-size: 15px;
font-weight: 700;
color: #1e293b;
margin-bottom: 4px;
}
.tv-desc {
font-size: 13px;
color: #475569;
line-height: 1.7;
white-space: pre-line;
}
</style></div></body></html>

View File

@@ -0,0 +1,151 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>venn-2items</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 벤 다이어그램: SVG premium (N개 자동 배치) -->
<!--
📋 venn-diagram (P2-B: 동적 좌표 계산)
─────────────────
용도: 상위-하위 포함 관계, 기술 융합 구조
방식: renderer가 svg_calculator.prepare_venn_data()로 좌표 사전 계산
→ items[].cx, cy, r + outer_r, center_x, center_y 전달
Phase 1 fallback: outer_r이 없으면 3개 고정 좌표 사용
-->
<div class="block-venn-svg">
<svg viewBox="0 0 600.0 550.0" width="100%" xmlns="http://www.w3.org/2000/svg" font-family="Pretendard Variable, sans-serif">
<defs>
<linearGradient id="bgGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#dce3ea" />
<stop offset="100%" stop-color="#f0f2f5" />
</linearGradient>
<radialGradient id="blueOuter" cx="45%" cy="40%" r="55%">
<stop offset="0%" stop-color="#2979ff" />
<stop offset="40%" stop-color="#1565c0" />
<stop offset="100%" stop-color="#0d47a1" />
</radialGradient>
<radialGradient id="hlGrad" cx="35%" cy="25%" r="40%">
<stop offset="0%" stop-color="rgba(255,255,255,0.5)" />
<stop offset="100%" stop-color="rgba(255,255,255,0)" />
</radialGradient>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="8" result="blur" />
<feMerge><feMergeNode in="blur" /><feMergeNode in="SourceGraphic" /></feMerge>
</filter>
<filter id="shadow">
<feDropShadow dx="0" dy="4" stdDeviation="8" flood-color="rgba(0,0,0,0.3)" />
</filter>
<radialGradient id="itemGrad1" cx="35%" cy="30%" r="65%">
<stop offset="0%" stop-color="#10b98180" />
<stop offset="100%" stop-color="#10b981" />
</radialGradient>
<radialGradient id="itemGrad2" cx="35%" cy="30%" r="65%">
<stop offset="0%" stop-color="#3b82f680" />
<stop offset="100%" stop-color="#3b82f6" />
</radialGradient>
</defs>
<rect width="600.0" height="550.0" fill="url(#bgGrad)" />
<circle cx="300.0" cy="295.0" r="235.0" fill="url(#blueOuter)" filter="url(#shadow)" />
<circle cx="300.0" cy="295.0" r="225.0" fill="none" stroke="#4a90d9" stroke-width="1.5" opacity="0.4" />
<circle cx="300.0" cy="295.0" r="210.0" fill="none" stroke="#5a9de0" stroke-width="1" opacity="0.3" />
<circle cx="300.0" cy="295.0" r="195.0" fill="none" stroke="#6aabe6" stroke-width="0.8" opacity="0.25" />
<circle cx="300.0" cy="175.0" r="75.0" fill="url(#itemGrad1)" opacity="0.9" filter="url(#glow)" />
<circle cx="300.0" cy="175.0" r="75.0" fill="url(#hlGrad)" />
<text x="300.0" y="175.0" text-anchor="middle" dominant-baseline="central" fill="white" font-size="18" font-weight="700">BIM</text>
<circle cx="300.0" cy="415.0" r="75.0" fill="url(#itemGrad2)" opacity="0.9" filter="url(#glow)" />
<circle cx="300.0" cy="415.0" r="75.0" fill="url(#hlGrad)" />
<text x="300.0" y="415.0" text-anchor="middle" dominant-baseline="central" fill="white" font-size="18" font-weight="700">GIS</text>
<text x="300.0" y="95.0" text-anchor="middle" fill="#ffffff" font-size="13" font-weight="400" opacity="0.85">Digital Transformation</text>
<text x="300.0" y="120.0" text-anchor="middle" fill="#ffffff" font-size="24" font-weight="900">DX (2개)</text>
</svg>
<div class="venn-desc">2개 요소의 기술 융합 구조</div>
</div>
<style>
.block-venn-svg {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
padding: 10px 0;
flex-shrink: 1;
min-height: 0;
overflow: hidden;
}
.block-venn-svg svg {
max-width: 450px;
width: 100%;
height: auto;
flex-shrink: 1;
}
.venn-desc {
font-size: 14px;
color: #444;
text-align: center;
max-width: 450px;
line-height: 1.7;
word-break: keep-all;
}
</style></div></body></html>

View File

@@ -0,0 +1,161 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>venn-3items</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 벤 다이어그램: SVG premium (N개 자동 배치) -->
<!--
📋 venn-diagram (P2-B: 동적 좌표 계산)
─────────────────
용도: 상위-하위 포함 관계, 기술 융합 구조
방식: renderer가 svg_calculator.prepare_venn_data()로 좌표 사전 계산
→ items[].cx, cy, r + outer_r, center_x, center_y 전달
Phase 1 fallback: outer_r이 없으면 3개 고정 좌표 사용
-->
<div class="block-venn-svg">
<svg viewBox="0 0 600.0 550.0" width="100%" xmlns="http://www.w3.org/2000/svg" font-family="Pretendard Variable, sans-serif">
<defs>
<linearGradient id="bgGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#dce3ea" />
<stop offset="100%" stop-color="#f0f2f5" />
</linearGradient>
<radialGradient id="blueOuter" cx="45%" cy="40%" r="55%">
<stop offset="0%" stop-color="#2979ff" />
<stop offset="40%" stop-color="#1565c0" />
<stop offset="100%" stop-color="#0d47a1" />
</radialGradient>
<radialGradient id="hlGrad" cx="35%" cy="25%" r="40%">
<stop offset="0%" stop-color="rgba(255,255,255,0.5)" />
<stop offset="100%" stop-color="rgba(255,255,255,0)" />
</radialGradient>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="8" result="blur" />
<feMerge><feMergeNode in="blur" /><feMergeNode in="SourceGraphic" /></feMerge>
</filter>
<filter id="shadow">
<feDropShadow dx="0" dy="4" stdDeviation="8" flood-color="rgba(0,0,0,0.3)" />
</filter>
<radialGradient id="itemGrad1" cx="35%" cy="30%" r="65%">
<stop offset="0%" stop-color="#6ee7b7" />
<stop offset="100%" stop-color="#10b981" />
</radialGradient>
<radialGradient id="itemGrad2" cx="35%" cy="30%" r="65%">
<stop offset="0%" stop-color="#93c5fd" />
<stop offset="100%" stop-color="#3b82f6" />
</radialGradient>
<radialGradient id="itemGrad3" cx="35%" cy="30%" r="65%">
<stop offset="0%" stop-color="#c4b5fd" />
<stop offset="100%" stop-color="#8b5cf6" />
</radialGradient>
</defs>
<rect width="600.0" height="550.0" fill="url(#bgGrad)" />
<circle cx="300.0" cy="295.0" r="235.0" fill="url(#blueOuter)" filter="url(#shadow)" />
<circle cx="300.0" cy="295.0" r="225.0" fill="none" stroke="#4a90d9" stroke-width="1.5" opacity="0.4" />
<circle cx="300.0" cy="295.0" r="210.0" fill="none" stroke="#5a9de0" stroke-width="1" opacity="0.3" />
<circle cx="300.0" cy="295.0" r="195.0" fill="none" stroke="#6aabe6" stroke-width="0.8" opacity="0.25" />
<circle cx="300.0" cy="175.0" r="75.0" fill="url(#itemGrad1)" opacity="0.9" filter="url(#glow)" />
<circle cx="300.0" cy="175.0" r="75.0" fill="url(#hlGrad)" />
<text x="300.0" y="175.0" text-anchor="middle" dominant-baseline="central" fill="white" font-size="18" font-weight="700">GIS</text>
<circle cx="403.9" cy="355.0" r="75.0" fill="url(#itemGrad2)" opacity="0.9" filter="url(#glow)" />
<circle cx="403.9" cy="355.0" r="75.0" fill="url(#hlGrad)" />
<text x="403.9" y="355.0" text-anchor="middle" dominant-baseline="central" fill="white" font-size="18" font-weight="700">BIM</text>
<circle cx="196.1" cy="355.0" r="75.0" fill="url(#itemGrad3)" opacity="0.9" filter="url(#glow)" />
<circle cx="196.1" cy="355.0" r="75.0" fill="url(#hlGrad)" />
<text x="196.1" y="355.0" text-anchor="middle" dominant-baseline="central" fill="white" font-size="18" font-weight="700">DT</text>
<text x="300.0" y="95.0" text-anchor="middle" fill="#ffffff" font-size="13" font-weight="400" opacity="0.85">Digital Transformation</text>
<text x="300.0" y="120.0" text-anchor="middle" fill="#ffffff" font-size="24" font-weight="900">DX (3개)</text>
</svg>
<div class="venn-desc">3개 요소의 기술 융합 구조</div>
</div>
<style>
.block-venn-svg {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
padding: 10px 0;
flex-shrink: 1;
min-height: 0;
overflow: hidden;
}
.block-venn-svg svg {
max-width: 450px;
width: 100%;
height: auto;
flex-shrink: 1;
}
.venn-desc {
font-size: 14px;
color: #444;
text-align: center;
max-width: 450px;
line-height: 1.7;
word-break: keep-all;
}
</style></div></body></html>

View File

@@ -0,0 +1,165 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>venn-3items</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style>
</head>
<body><div class="block-container"><!-- 벤 다이어그램: 수학적 계산 기반 원형 배치 + SVG -->
<!--
📋 venn-diagram (계산 기반)
─────────────────
용도: 상위-하위 포함 관계, 기술 융합 구조
방식: 중앙 큰 원 + 작은 원 N개를 360/N 간격으로 배치 (수학적 계산)
슬롯: center_label, center_sub, items[] (개수 자유), description
계산: items 개수에 따라 자동으로 360도 분할 → cos/sin으로 좌표 결정
렌더링 전 Python에서 계산 필요:
for i, item in enumerate(items):
angle = (2 * pi * i / n) - pi/2 # 12시부터 시작
item['cx'] = 200 + 110 * cos(angle)
item['cy'] = 200 + 110 * sin(angle)
-->
<div class="block-venn-calc">
<svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg" class="vc-svg">
<defs>
<radialGradient id="outerGrad" cx="50%" cy="40%" r="55%">
<stop offset="0%" stop-color="#e8f4fd" />
<stop offset="100%" stop-color="#d0e8f8" />
</radialGradient>
<filter id="glow">
<feGaussianBlur stdDeviation="6" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<filter id="shadow">
<feDropShadow dx="0" dy="2" stdDeviation="4" flood-opacity="0.15" />
</filter>
<radialGradient id="itemGrad1" cx="40%" cy="35%" r="65%">
<stop offset="0%" stop-color="#6ee7b7" />
<stop offset="100%" stop-color="#10b981" />
</radialGradient>
<radialGradient id="itemGrad2" cx="40%" cy="35%" r="65%">
<stop offset="0%" stop-color="#93c5fd" />
<stop offset="100%" stop-color="#3b82f6" />
</radialGradient>
<radialGradient id="itemGrad3" cx="40%" cy="35%" r="65%">
<stop offset="0%" stop-color="#c4b5fd" />
<stop offset="100%" stop-color="#8b5cf6" />
</radialGradient>
</defs>
<!-- 바깥 링 -->
<circle cx="200" cy="200" r="185" fill="none" stroke="#b8d4f0" stroke-width="1" opacity="0.5" />
<!-- 큰 원 (상위 개념) -->
<circle cx="200" cy="200" r="170" fill="url(#outerGrad)" stroke="#7eb8e0" stroke-width="2.5" />
<!-- 연결선 (각 작은 원 → 중심) -->
<line x1="200" y1="200" x2="200.0" y2="110.0" stroke="#a0c4e0" stroke-width="1" stroke-dasharray="4,4" opacity="0.4" />
<line x1="200" y1="200" x2="295.3" y2="275.0" stroke="#a0c4e0" stroke-width="1" stroke-dasharray="4,4" opacity="0.4" />
<line x1="200" y1="200" x2="104.7" y2="275.0" stroke="#a0c4e0" stroke-width="1" stroke-dasharray="4,4" opacity="0.4" />
<!-- 작은 원들 (하위 요소) -->
<circle cx="200.0" cy="110.0" r="42" fill="url(#itemGrad1)" filter="url(#shadow)" opacity="0.9" />
<text x="200.0" y="110.0" text-anchor="middle" dominant-baseline="central" fill="white" font-size="15" font-weight="700" font-family="Pretendard Variable, sans-serif">GIS</text>
<circle cx="295.3" cy="275.0" r="42" fill="url(#itemGrad2)" filter="url(#shadow)" opacity="0.9" />
<text x="295.3" y="275.0" text-anchor="middle" dominant-baseline="central" fill="white" font-size="15" font-weight="700" font-family="Pretendard Variable, sans-serif">BIM</text>
<circle cx="104.7" cy="275.0" r="42" fill="url(#itemGrad3)" filter="url(#shadow)" opacity="0.9" />
<text x="104.7" y="275.0" text-anchor="middle" dominant-baseline="central" fill="white" font-size="15" font-weight="700" font-family="Pretendard Variable, sans-serif">DT</text>
<!-- 중앙 텍스트 -->
<text x="200" y="85" text-anchor="middle" dominant-baseline="central" fill="#1e3a6e" font-size="20" font-weight="900" font-family="Pretendard Variable, sans-serif">건설산업의 DX</text>
<text x="200" y="108" text-anchor="middle" dominant-baseline="central" fill="#6b7280" font-size="11" font-family="Pretendard Variable, sans-serif">Digital Transformation</text>
</svg>
<div class="vc-desc">건설산업의 DX는 GIS, BIM, 디지털 트윈의 기술 융합을 통해서만 실현 가능</div>
</div>
<style>
.block-venn-calc {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
padding: 20px 0;
}
.vc-svg {
width: 380px;
height: 380px;
}
.vc-desc {
font-size: 14px;
color: #444;
text-align: center;
max-width: 450px;
line-height: 1.7;
word-break: keep-all;
}
</style></div></body>
</html>

View File

@@ -0,0 +1,171 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>venn-4items</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 벤 다이어그램: SVG premium (N개 자동 배치) -->
<!--
📋 venn-diagram (P2-B: 동적 좌표 계산)
─────────────────
용도: 상위-하위 포함 관계, 기술 융합 구조
방식: renderer가 svg_calculator.prepare_venn_data()로 좌표 사전 계산
→ items[].cx, cy, r + outer_r, center_x, center_y 전달
Phase 1 fallback: outer_r이 없으면 3개 고정 좌표 사용
-->
<div class="block-venn-svg">
<svg viewBox="0 0 600.0 550.0" width="100%" xmlns="http://www.w3.org/2000/svg" font-family="Pretendard Variable, sans-serif">
<defs>
<linearGradient id="bgGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#dce3ea" />
<stop offset="100%" stop-color="#f0f2f5" />
</linearGradient>
<radialGradient id="blueOuter" cx="45%" cy="40%" r="55%">
<stop offset="0%" stop-color="#2979ff" />
<stop offset="40%" stop-color="#1565c0" />
<stop offset="100%" stop-color="#0d47a1" />
</radialGradient>
<radialGradient id="hlGrad" cx="35%" cy="25%" r="40%">
<stop offset="0%" stop-color="rgba(255,255,255,0.5)" />
<stop offset="100%" stop-color="rgba(255,255,255,0)" />
</radialGradient>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="8" result="blur" />
<feMerge><feMergeNode in="blur" /><feMergeNode in="SourceGraphic" /></feMerge>
</filter>
<filter id="shadow">
<feDropShadow dx="0" dy="4" stdDeviation="8" flood-color="rgba(0,0,0,0.3)" />
</filter>
<radialGradient id="itemGrad1" cx="35%" cy="30%" r="65%">
<stop offset="0%" stop-color="#10b98180" />
<stop offset="100%" stop-color="#10b981" />
</radialGradient>
<radialGradient id="itemGrad2" cx="35%" cy="30%" r="65%">
<stop offset="0%" stop-color="#3b82f680" />
<stop offset="100%" stop-color="#3b82f6" />
</radialGradient>
<radialGradient id="itemGrad3" cx="35%" cy="30%" r="65%">
<stop offset="0%" stop-color="#8b5cf680" />
<stop offset="100%" stop-color="#8b5cf6" />
</radialGradient>
<radialGradient id="itemGrad4" cx="35%" cy="30%" r="65%">
<stop offset="0%" stop-color="#f59e0b80" />
<stop offset="100%" stop-color="#f59e0b" />
</radialGradient>
</defs>
<rect width="600.0" height="550.0" fill="url(#bgGrad)" />
<circle cx="300.0" cy="295.0" r="232.1" fill="url(#blueOuter)" filter="url(#shadow)" />
<circle cx="300.0" cy="295.0" r="222.1" fill="none" stroke="#4a90d9" stroke-width="1.5" opacity="0.4" />
<circle cx="300.0" cy="295.0" r="207.1" fill="none" stroke="#5a9de0" stroke-width="1" opacity="0.3" />
<circle cx="300.0" cy="295.0" r="192.1" fill="none" stroke="#6aabe6" stroke-width="0.8" opacity="0.25" />
<circle cx="300.0" cy="165.4" r="62.5" fill="url(#itemGrad1)" opacity="0.9" filter="url(#glow)" />
<circle cx="300.0" cy="165.4" r="62.5" fill="url(#hlGrad)" />
<text x="300.0" y="165.4" text-anchor="middle" dominant-baseline="central" fill="white" font-size="18" font-weight="700">설계</text>
<circle cx="429.6" cy="295.0" r="62.5" fill="url(#itemGrad2)" opacity="0.9" filter="url(#glow)" />
<circle cx="429.6" cy="295.0" r="62.5" fill="url(#hlGrad)" />
<text x="429.6" y="295.0" text-anchor="middle" dominant-baseline="central" fill="white" font-size="18" font-weight="700">시공</text>
<circle cx="300.0" cy="424.6" r="62.5" fill="url(#itemGrad3)" opacity="0.9" filter="url(#glow)" />
<circle cx="300.0" cy="424.6" r="62.5" fill="url(#hlGrad)" />
<text x="300.0" y="424.6" text-anchor="middle" dominant-baseline="central" fill="white" font-size="18" font-weight="700">유지관리</text>
<circle cx="170.4" cy="295.0" r="62.5" fill="url(#itemGrad4)" opacity="0.9" filter="url(#glow)" />
<circle cx="170.4" cy="295.0" r="62.5" fill="url(#hlGrad)" />
<text x="170.4" y="295.0" text-anchor="middle" dominant-baseline="central" fill="white" font-size="18" font-weight="700">운영</text>
<text x="300.0" y="97.9" text-anchor="middle" fill="#ffffff" font-size="13" font-weight="400" opacity="0.85">Digital Transformation</text>
<text x="300.0" y="122.9" text-anchor="middle" fill="#ffffff" font-size="24" font-weight="900">DX (4개)</text>
</svg>
<div class="venn-desc">4개 요소의 기술 융합 구조</div>
</div>
<style>
.block-venn-svg {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
padding: 10px 0;
flex-shrink: 1;
min-height: 0;
overflow: hidden;
}
.block-venn-svg svg {
max-width: 450px;
width: 100%;
height: auto;
flex-shrink: 1;
}
.venn-desc {
font-size: 14px;
color: #444;
text-align: center;
max-width: 450px;
line-height: 1.7;
word-break: keep-all;
}
</style></div></body></html>

View File

@@ -0,0 +1,175 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>venn-4items</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style>
</head>
<body><div class="block-container"><!-- 벤 다이어그램: 수학적 계산 기반 원형 배치 + SVG -->
<!--
📋 venn-diagram (계산 기반)
─────────────────
용도: 상위-하위 포함 관계, 기술 융합 구조
방식: 중앙 큰 원 + 작은 원 N개를 360/N 간격으로 배치 (수학적 계산)
슬롯: center_label, center_sub, items[] (개수 자유), description
계산: items 개수에 따라 자동으로 360도 분할 → cos/sin으로 좌표 결정
렌더링 전 Python에서 계산 필요:
for i, item in enumerate(items):
angle = (2 * pi * i / n) - pi/2 # 12시부터 시작
item['cx'] = 200 + 110 * cos(angle)
item['cy'] = 200 + 110 * sin(angle)
-->
<div class="block-venn-calc">
<svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg" class="vc-svg">
<defs>
<radialGradient id="outerGrad" cx="50%" cy="40%" r="55%">
<stop offset="0%" stop-color="#e8f4fd" />
<stop offset="100%" stop-color="#d0e8f8" />
</radialGradient>
<filter id="glow">
<feGaussianBlur stdDeviation="6" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<filter id="shadow">
<feDropShadow dx="0" dy="2" stdDeviation="4" flood-opacity="0.15" />
</filter>
<radialGradient id="itemGrad1" cx="40%" cy="35%" r="65%">
<stop offset="0%" stop-color="#7dd3fc" />
<stop offset="100%" stop-color="#00aaff" />
</radialGradient>
<radialGradient id="itemGrad2" cx="40%" cy="35%" r="65%">
<stop offset="0%" stop-color="#93c5fd" />
<stop offset="100%" stop-color="#006aff" />
</radialGradient>
<radialGradient id="itemGrad3" cx="40%" cy="35%" r="65%">
<stop offset="0%" stop-color="#6b9fff" />
<stop offset="100%" stop-color="#004cbe" />
</radialGradient>
<radialGradient id="itemGrad4" cx="40%" cy="35%" r="65%">
<stop offset="0%" stop-color="#c4b5fd" />
<stop offset="100%" stop-color="#7c3aed" />
</radialGradient>
</defs>
<!-- 바깥 링 -->
<circle cx="200" cy="200" r="185" fill="none" stroke="#b8d4f0" stroke-width="1" opacity="0.5" />
<!-- 큰 원 (상위 개념) -->
<circle cx="200" cy="200" r="170" fill="url(#outerGrad)" stroke="#7eb8e0" stroke-width="2.5" />
<!-- 연결선 (각 작은 원 → 중심) -->
<line x1="200" y1="200" x2="200.0" y2="110.0" stroke="#a0c4e0" stroke-width="1" stroke-dasharray="4,4" opacity="0.4" />
<line x1="200" y1="200" x2="310.0" y2="220.0" stroke="#a0c4e0" stroke-width="1" stroke-dasharray="4,4" opacity="0.4" />
<line x1="200" y1="200" x2="200.0" y2="330.0" stroke="#a0c4e0" stroke-width="1" stroke-dasharray="4,4" opacity="0.4" />
<line x1="200" y1="200" x2="90.0" y2="220.0" stroke="#a0c4e0" stroke-width="1" stroke-dasharray="4,4" opacity="0.4" />
<!-- 작은 원들 (하위 요소) -->
<circle cx="200.0" cy="110.0" r="42" fill="url(#itemGrad1)" filter="url(#shadow)" opacity="0.9" />
<text x="200.0" y="110.0" text-anchor="middle" dominant-baseline="central" fill="white" font-size="15" font-weight="700" font-family="Pretendard Variable, sans-serif">설계</text>
<circle cx="310.0" cy="220.0" r="42" fill="url(#itemGrad2)" filter="url(#shadow)" opacity="0.9" />
<text x="310.0" y="220.0" text-anchor="middle" dominant-baseline="central" fill="white" font-size="15" font-weight="700" font-family="Pretendard Variable, sans-serif">시공</text>
<circle cx="200.0" cy="330.0" r="42" fill="url(#itemGrad3)" filter="url(#shadow)" opacity="0.9" />
<text x="200.0" y="330.0" text-anchor="middle" dominant-baseline="central" fill="white" font-size="15" font-weight="700" font-family="Pretendard Variable, sans-serif">유지관리</text>
<circle cx="90.0" cy="220.0" r="42" fill="url(#itemGrad4)" filter="url(#shadow)" opacity="0.9" />
<text x="90.0" y="220.0" text-anchor="middle" dominant-baseline="central" fill="white" font-size="15" font-weight="700" font-family="Pretendard Variable, sans-serif">운영</text>
<!-- 중앙 텍스트 -->
<text x="200" y="85" text-anchor="middle" dominant-baseline="central" fill="#1e3a6e" font-size="20" font-weight="900" font-family="Pretendard Variable, sans-serif">생애주기</text>
<text x="200" y="108" text-anchor="middle" dominant-baseline="central" fill="#6b7280" font-size="11" font-family="Pretendard Variable, sans-serif">Life Cycle</text>
</svg>
<div class="vc-desc">설계 → 시공 → 유지관리 → 운영의 전 생애주기 관리</div>
</div>
<style>
.block-venn-calc {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
padding: 20px 0;
}
.vc-svg {
width: 380px;
height: 380px;
}
.vc-desc {
font-size: 14px;
color: #444;
text-align: center;
max-width: 450px;
line-height: 1.7;
word-break: keep-all;
}
</style></div></body>
</html>

View File

@@ -0,0 +1,181 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>venn-5items</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 벤 다이어그램: SVG premium (N개 자동 배치) -->
<!--
📋 venn-diagram (P2-B: 동적 좌표 계산)
─────────────────
용도: 상위-하위 포함 관계, 기술 융합 구조
방식: renderer가 svg_calculator.prepare_venn_data()로 좌표 사전 계산
→ items[].cx, cy, r + outer_r, center_x, center_y 전달
Phase 1 fallback: outer_r이 없으면 3개 고정 좌표 사용
-->
<div class="block-venn-svg">
<svg viewBox="0 0 600.0 550.0" width="100%" xmlns="http://www.w3.org/2000/svg" font-family="Pretendard Variable, sans-serif">
<defs>
<linearGradient id="bgGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#dce3ea" />
<stop offset="100%" stop-color="#f0f2f5" />
</linearGradient>
<radialGradient id="blueOuter" cx="45%" cy="40%" r="55%">
<stop offset="0%" stop-color="#2979ff" />
<stop offset="40%" stop-color="#1565c0" />
<stop offset="100%" stop-color="#0d47a1" />
</radialGradient>
<radialGradient id="hlGrad" cx="35%" cy="25%" r="40%">
<stop offset="0%" stop-color="rgba(255,255,255,0.5)" />
<stop offset="100%" stop-color="rgba(255,255,255,0)" />
</radialGradient>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="8" result="blur" />
<feMerge><feMergeNode in="blur" /><feMergeNode in="SourceGraphic" /></feMerge>
</filter>
<filter id="shadow">
<feDropShadow dx="0" dy="4" stdDeviation="8" flood-color="rgba(0,0,0,0.3)" />
</filter>
<radialGradient id="itemGrad1" cx="35%" cy="30%" r="65%">
<stop offset="0%" stop-color="#10b98180" />
<stop offset="100%" stop-color="#10b981" />
</radialGradient>
<radialGradient id="itemGrad2" cx="35%" cy="30%" r="65%">
<stop offset="0%" stop-color="#3b82f680" />
<stop offset="100%" stop-color="#3b82f6" />
</radialGradient>
<radialGradient id="itemGrad3" cx="35%" cy="30%" r="65%">
<stop offset="0%" stop-color="#8b5cf680" />
<stop offset="100%" stop-color="#8b5cf6" />
</radialGradient>
<radialGradient id="itemGrad4" cx="35%" cy="30%" r="65%">
<stop offset="0%" stop-color="#f59e0b80" />
<stop offset="100%" stop-color="#f59e0b" />
</radialGradient>
<radialGradient id="itemGrad5" cx="35%" cy="30%" r="65%">
<stop offset="0%" stop-color="#ef444480" />
<stop offset="100%" stop-color="#ef4444" />
</radialGradient>
</defs>
<rect width="600.0" height="550.0" fill="url(#bgGrad)" />
<circle cx="300.0" cy="295.0" r="232.8" fill="url(#blueOuter)" filter="url(#shadow)" />
<circle cx="300.0" cy="295.0" r="222.8" fill="none" stroke="#4a90d9" stroke-width="1.5" opacity="0.4" />
<circle cx="300.0" cy="295.0" r="207.8" fill="none" stroke="#5a9de0" stroke-width="1" opacity="0.3" />
<circle cx="300.0" cy="295.0" r="192.8" fill="none" stroke="#6aabe6" stroke-width="0.8" opacity="0.25" />
<circle cx="300.0" cy="155.8" r="53.6" fill="url(#itemGrad1)" opacity="0.9" filter="url(#glow)" />
<circle cx="300.0" cy="155.8" r="53.6" fill="url(#hlGrad)" />
<text x="300.0" y="155.8" text-anchor="middle" dominant-baseline="central" fill="white" font-size="15" font-weight="700">기술</text>
<circle cx="432.4" cy="252.0" r="53.6" fill="url(#itemGrad2)" opacity="0.9" filter="url(#glow)" />
<circle cx="432.4" cy="252.0" r="53.6" fill="url(#hlGrad)" />
<text x="432.4" y="252.0" text-anchor="middle" dominant-baseline="central" fill="white" font-size="15" font-weight="700">S/W</text>
<circle cx="381.8" cy="407.6" r="53.6" fill="url(#itemGrad3)" opacity="0.9" filter="url(#glow)" />
<circle cx="381.8" cy="407.6" r="53.6" fill="url(#hlGrad)" />
<text x="381.8" y="407.6" text-anchor="middle" dominant-baseline="central" fill="white" font-size="15" font-weight="700">인력</text>
<circle cx="218.2" cy="407.6" r="53.6" fill="url(#itemGrad4)" opacity="0.9" filter="url(#glow)" />
<circle cx="218.2" cy="407.6" r="53.6" fill="url(#hlGrad)" />
<text x="218.2" y="407.6" text-anchor="middle" dominant-baseline="central" fill="white" font-size="15" font-weight="700">여건</text>
<circle cx="167.6" cy="252.0" r="53.6" fill="url(#itemGrad5)" opacity="0.9" filter="url(#glow)" />
<circle cx="167.6" cy="252.0" r="53.6" fill="url(#hlGrad)" />
<text x="167.6" y="252.0" text-anchor="middle" dominant-baseline="central" fill="white" font-size="15" font-weight="700">투자</text>
<text x="300.0" y="97.19999999999999" text-anchor="middle" fill="#ffffff" font-size="13" font-weight="400" opacity="0.85">Digital Transformation</text>
<text x="300.0" y="122.19999999999999" text-anchor="middle" fill="#ffffff" font-size="24" font-weight="900">DX (5개)</text>
</svg>
<div class="venn-desc">5개 요소의 기술 융합 구조</div>
</div>
<style>
.block-venn-svg {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
padding: 10px 0;
flex-shrink: 1;
min-height: 0;
overflow: hidden;
}
.block-venn-svg svg {
max-width: 450px;
width: 100%;
height: auto;
flex-shrink: 1;
}
.venn-desc {
font-size: 14px;
color: #444;
text-align: center;
max-width: 450px;
line-height: 1.7;
word-break: keep-all;
}
</style></div></body></html>

View File

@@ -0,0 +1,185 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>venn-5items</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style>
</head>
<body><div class="block-container"><!-- 벤 다이어그램: 수학적 계산 기반 원형 배치 + SVG -->
<!--
📋 venn-diagram (계산 기반)
─────────────────
용도: 상위-하위 포함 관계, 기술 융합 구조
방식: 중앙 큰 원 + 작은 원 N개를 360/N 간격으로 배치 (수학적 계산)
슬롯: center_label, center_sub, items[] (개수 자유), description
계산: items 개수에 따라 자동으로 360도 분할 → cos/sin으로 좌표 결정
렌더링 전 Python에서 계산 필요:
for i, item in enumerate(items):
angle = (2 * pi * i / n) - pi/2 # 12시부터 시작
item['cx'] = 200 + 110 * cos(angle)
item['cy'] = 200 + 110 * sin(angle)
-->
<div class="block-venn-calc">
<svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg" class="vc-svg">
<defs>
<radialGradient id="outerGrad" cx="50%" cy="40%" r="55%">
<stop offset="0%" stop-color="#e8f4fd" />
<stop offset="100%" stop-color="#d0e8f8" />
</radialGradient>
<filter id="glow">
<feGaussianBlur stdDeviation="6" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<filter id="shadow">
<feDropShadow dx="0" dy="2" stdDeviation="4" flood-opacity="0.15" />
</filter>
<radialGradient id="itemGrad1" cx="40%" cy="35%" r="65%">
<stop offset="0%" stop-color="#67e8f9" />
<stop offset="100%" stop-color="#0891b2" />
</radialGradient>
<radialGradient id="itemGrad2" cx="40%" cy="35%" r="65%">
<stop offset="0%" stop-color="#93c5fd" />
<stop offset="100%" stop-color="#3b82f6" />
</radialGradient>
<radialGradient id="itemGrad3" cx="40%" cy="35%" r="65%">
<stop offset="0%" stop-color="#c4b5fd" />
<stop offset="100%" stop-color="#8b5cf6" />
</radialGradient>
<radialGradient id="itemGrad4" cx="40%" cy="35%" r="65%">
<stop offset="0%" stop-color="#f9a8d4" />
<stop offset="100%" stop-color="#ec4899" />
</radialGradient>
<radialGradient id="itemGrad5" cx="40%" cy="35%" r="65%">
<stop offset="0%" stop-color="#fcd34d" />
<stop offset="100%" stop-color="#f59e0b" />
</radialGradient>
</defs>
<!-- 바깥 링 -->
<circle cx="200" cy="200" r="185" fill="none" stroke="#b8d4f0" stroke-width="1" opacity="0.5" />
<!-- 큰 원 (상위 개념) -->
<circle cx="200" cy="200" r="170" fill="url(#outerGrad)" stroke="#7eb8e0" stroke-width="2.5" />
<!-- 연결선 (각 작은 원 → 중심) -->
<line x1="200" y1="200" x2="200.0" y2="110.0" stroke="#a0c4e0" stroke-width="1" stroke-dasharray="4,4" opacity="0.4" />
<line x1="200" y1="200" x2="304.6" y2="186.0" stroke="#a0c4e0" stroke-width="1" stroke-dasharray="4,4" opacity="0.4" />
<line x1="200" y1="200" x2="264.7" y2="309.0" stroke="#a0c4e0" stroke-width="1" stroke-dasharray="4,4" opacity="0.4" />
<line x1="200" y1="200" x2="135.3" y2="309.0" stroke="#a0c4e0" stroke-width="1" stroke-dasharray="4,4" opacity="0.4" />
<line x1="200" y1="200" x2="95.4" y2="186.0" stroke="#a0c4e0" stroke-width="1" stroke-dasharray="4,4" opacity="0.4" />
<!-- 작은 원들 (하위 요소) -->
<circle cx="200.0" cy="110.0" r="42" fill="url(#itemGrad1)" filter="url(#shadow)" opacity="0.9" />
<text x="200.0" y="110.0" text-anchor="middle" dominant-baseline="central" fill="white" font-size="15" font-weight="700" font-family="Pretendard Variable, sans-serif">기술기반</text>
<circle cx="304.6" cy="186.0" r="42" fill="url(#itemGrad2)" filter="url(#shadow)" opacity="0.9" />
<text x="304.6" y="186.0" text-anchor="middle" dominant-baseline="central" fill="white" font-size="15" font-weight="700" font-family="Pretendard Variable, sans-serif">S/W</text>
<circle cx="264.7" cy="309.0" r="42" fill="url(#itemGrad3)" filter="url(#shadow)" opacity="0.9" />
<text x="264.7" y="309.0" text-anchor="middle" dominant-baseline="central" fill="white" font-size="15" font-weight="700" font-family="Pretendard Variable, sans-serif">인력</text>
<circle cx="135.3" cy="309.0" r="42" fill="url(#itemGrad4)" filter="url(#shadow)" opacity="0.9" />
<text x="135.3" y="309.0" text-anchor="middle" dominant-baseline="central" fill="white" font-size="15" font-weight="700" font-family="Pretendard Variable, sans-serif">여건</text>
<circle cx="95.4" cy="186.0" r="42" fill="url(#itemGrad5)" filter="url(#shadow)" opacity="0.9" />
<text x="95.4" y="186.0" text-anchor="middle" dominant-baseline="central" fill="white" font-size="15" font-weight="700" font-family="Pretendard Variable, sans-serif">투자</text>
<!-- 중앙 텍스트 -->
<text x="200" y="85" text-anchor="middle" dominant-baseline="central" fill="#1e3a6e" font-size="20" font-weight="900" font-family="Pretendard Variable, sans-serif">DX 성공 요건</text>
<text x="200" y="108" text-anchor="middle" dominant-baseline="central" fill="#6b7280" font-size="11" font-family="Pretendard Variable, sans-serif">5대 필수 요건</text>
</svg>
<div class="vc-desc">디지털 전환 성공을 위해 기술, S/W, 인력, 여건, 투자 5가지 요건이 필요</div>
</div>
<style>
.block-venn-calc {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
padding: 20px 0;
}
.vc-svg {
width: 380px;
height: 380px;
}
.vc-desc {
font-size: 14px;
color: #444;
text-align: center;
max-width: 450px;
line-height: 1.7;
word-break: keep-all;
}
</style></div></body>
</html>

View File

@@ -0,0 +1,201 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>venn-7items</title>
<style>@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style></head><body><div class="block-container"><!-- 벤 다이어그램: SVG premium (N개 자동 배치) -->
<!--
📋 venn-diagram (P2-B: 동적 좌표 계산)
─────────────────
용도: 상위-하위 포함 관계, 기술 융합 구조
방식: renderer가 svg_calculator.prepare_venn_data()로 좌표 사전 계산
→ items[].cx, cy, r + outer_r, center_x, center_y 전달
Phase 1 fallback: outer_r이 없으면 3개 고정 좌표 사용
-->
<div class="block-venn-svg">
<svg viewBox="0 0 600.0 550.0" width="100%" xmlns="http://www.w3.org/2000/svg" font-family="Pretendard Variable, sans-serif">
<defs>
<linearGradient id="bgGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#dce3ea" />
<stop offset="100%" stop-color="#f0f2f5" />
</linearGradient>
<radialGradient id="blueOuter" cx="45%" cy="40%" r="55%">
<stop offset="0%" stop-color="#2979ff" />
<stop offset="40%" stop-color="#1565c0" />
<stop offset="100%" stop-color="#0d47a1" />
</radialGradient>
<radialGradient id="hlGrad" cx="35%" cy="25%" r="40%">
<stop offset="0%" stop-color="rgba(255,255,255,0.5)" />
<stop offset="100%" stop-color="rgba(255,255,255,0)" />
</radialGradient>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="8" result="blur" />
<feMerge><feMergeNode in="blur" /><feMergeNode in="SourceGraphic" /></feMerge>
</filter>
<filter id="shadow">
<feDropShadow dx="0" dy="4" stdDeviation="8" flood-color="rgba(0,0,0,0.3)" />
</filter>
<radialGradient id="itemGrad1" cx="35%" cy="30%" r="65%">
<stop offset="0%" stop-color="#10b98180" />
<stop offset="100%" stop-color="#10b981" />
</radialGradient>
<radialGradient id="itemGrad2" cx="35%" cy="30%" r="65%">
<stop offset="0%" stop-color="#3b82f680" />
<stop offset="100%" stop-color="#3b82f6" />
</radialGradient>
<radialGradient id="itemGrad3" cx="35%" cy="30%" r="65%">
<stop offset="0%" stop-color="#8b5cf680" />
<stop offset="100%" stop-color="#8b5cf6" />
</radialGradient>
<radialGradient id="itemGrad4" cx="35%" cy="30%" r="65%">
<stop offset="0%" stop-color="#f59e0b80" />
<stop offset="100%" stop-color="#f59e0b" />
</radialGradient>
<radialGradient id="itemGrad5" cx="35%" cy="30%" r="65%">
<stop offset="0%" stop-color="#ef444480" />
<stop offset="100%" stop-color="#ef4444" />
</radialGradient>
<radialGradient id="itemGrad6" cx="35%" cy="30%" r="65%">
<stop offset="0%" stop-color="#06b6d480" />
<stop offset="100%" stop-color="#06b6d4" />
</radialGradient>
<radialGradient id="itemGrad7" cx="35%" cy="30%" r="65%">
<stop offset="0%" stop-color="#ec489980" />
<stop offset="100%" stop-color="#ec4899" />
</radialGradient>
</defs>
<rect width="600.0" height="550.0" fill="url(#bgGrad)" />
<circle cx="300.0" cy="295.0" r="240.1" fill="url(#blueOuter)" filter="url(#shadow)" />
<circle cx="300.0" cy="295.0" r="230.1" fill="none" stroke="#4a90d9" stroke-width="1.5" opacity="0.4" />
<circle cx="300.0" cy="295.0" r="215.1" fill="none" stroke="#5a9de0" stroke-width="1" opacity="0.3" />
<circle cx="300.0" cy="295.0" r="200.1" fill="none" stroke="#6aabe6" stroke-width="0.8" opacity="0.25" />
<circle cx="300.0" cy="136.6" r="41.7" fill="url(#itemGrad1)" opacity="0.9" filter="url(#glow)" />
<circle cx="300.0" cy="136.6" r="41.7" fill="url(#hlGrad)" />
<text x="300.0" y="136.6" text-anchor="middle" dominant-baseline="central" fill="white" font-size="15" font-weight="700">GIS</text>
<circle cx="423.8" cy="196.2" r="41.7" fill="url(#itemGrad2)" opacity="0.9" filter="url(#glow)" />
<circle cx="423.8" cy="196.2" r="41.7" fill="url(#hlGrad)" />
<text x="423.8" y="196.2" text-anchor="middle" dominant-baseline="central" fill="white" font-size="15" font-weight="700">BIM</text>
<circle cx="454.4" cy="330.2" r="41.7" fill="url(#itemGrad3)" opacity="0.9" filter="url(#glow)" />
<circle cx="454.4" cy="330.2" r="41.7" fill="url(#hlGrad)" />
<text x="454.4" y="330.2" text-anchor="middle" dominant-baseline="central" fill="white" font-size="15" font-weight="700">DT</text>
<circle cx="368.7" cy="437.7" r="41.7" fill="url(#itemGrad4)" opacity="0.9" filter="url(#glow)" />
<circle cx="368.7" cy="437.7" r="41.7" fill="url(#hlGrad)" />
<text x="368.7" y="437.7" text-anchor="middle" dominant-baseline="central" fill="white" font-size="15" font-weight="700">IoT</text>
<circle cx="231.3" cy="437.7" r="41.7" fill="url(#itemGrad5)" opacity="0.9" filter="url(#glow)" />
<circle cx="231.3" cy="437.7" r="41.7" fill="url(#hlGrad)" />
<text x="231.3" y="437.7" text-anchor="middle" dominant-baseline="central" fill="white" font-size="15" font-weight="700">AI</text>
<circle cx="145.6" cy="330.2" r="41.7" fill="url(#itemGrad6)" opacity="0.9" filter="url(#glow)" />
<circle cx="145.6" cy="330.2" r="41.7" fill="url(#hlGrad)" />
<text x="145.6" y="330.2" text-anchor="middle" dominant-baseline="central" fill="white" font-size="15" font-weight="700">Cloud</text>
<circle cx="176.2" cy="196.2" r="41.7" fill="url(#itemGrad7)" opacity="0.9" filter="url(#glow)" />
<circle cx="176.2" cy="196.2" r="41.7" fill="url(#hlGrad)" />
<text x="176.2" y="196.2" text-anchor="middle" dominant-baseline="central" fill="white" font-size="15" font-weight="700">Robot</text>
<text x="300.0" y="89.9" text-anchor="middle" fill="#ffffff" font-size="13" font-weight="400" opacity="0.85">Digital Transformation</text>
<text x="300.0" y="114.9" text-anchor="middle" fill="#ffffff" font-size="24" font-weight="900">DX (7개)</text>
</svg>
<div class="venn-desc">7개 요소의 기술 융합 구조</div>
</div>
<style>
.block-venn-svg {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
padding: 10px 0;
flex-shrink: 1;
min-height: 0;
overflow: hidden;
}
.block-venn-svg svg {
max-width: 450px;
width: 100%;
height: auto;
flex-shrink: 1;
}
.venn-desc {
font-size: 14px;
color: #444;
text-align: center;
max-width: 450px;
line-height: 1.7;
word-break: keep-all;
}
</style></div></body></html>

View File

@@ -0,0 +1,172 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>venn-diagram</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
</style>
</head>
<body><div class="block-container"><!-- 벤 다이어그램: AI 생성 배경 이미지 + HTML 텍스트 오버레이 -->
<!--
📋 venn-diagram (레이어 방식)
─────────────────
용도: 상위-하위 포함 관계, 기술 융합 구조
방식: 배경 이미지(원형 다이어그램) + HTML 텍스트 오버레이
슬롯: bg_image (선택), center_label, center_sub, items[], description
참고: bg_image가 없으면 기본 CSS 원으로 fallback
-->
<div class="block-venn-layer">
<div class="vl-diagram">
<img class="vl-bg" src="../figma-assets/venn_bg.png" alt="">
<div class="vl-center-text">
<div class="vl-main">건설산업의 DX</div>
<div class="vl-sub">Digital Transformation</div>
</div>
<div class="vl-items">
<div class="vl-item vl-item-1">GIS</div>
<div class="vl-item vl-item-2">BIM</div>
<div class="vl-item vl-item-3">DT</div>
</div>
</div>
<div class="vl-desc">건설산업의 DX는 GIS, BIM, 디지털 트윈의 기술 융합을 통해서만 실현 가능</div>
</div>
<style>
.block-venn-layer {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
padding: 20px 0;
}
.vl-diagram {
position: relative;
width: 400px;
height: 400px;
}
.vl-bg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: contain;
z-index: 1;
}
.vl-bg-fallback {
border-radius: 50%;
background: linear-gradient(135deg, rgba(37,99,235,0.04), rgba(37,99,235,0.08));
border: 3px solid rgba(37,99,235,0.2);
}
.vl-center-text {
position: absolute;
top: 15%;
left: 50%;
transform: translateX(-50%);
z-index: 5;
text-align: center;
}
.vl-main {
font-size: 20px;
font-weight: 900;
color: #1e3a6e;
line-height: 1.3;
white-space: pre-line;
}
.vl-sub {
font-size: 11px;
color: #6b7280;
margin-top: 3px;
}
.vl-items {
position: absolute;
bottom: 8%;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 0;
z-index: 5;
}
.vl-item {
width: 76px;
height: 76px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
font-size: 14px;
font-weight: 700;
text-shadow: 0 1px 3px rgba(0,0,0,0.2);
/* 배경은 투명 — 이미지의 원이 보이도록 */
background: transparent;
}
.vl-item-1 { margin-right: -8px; }
.vl-item-2 { z-index: 3; }
.vl-item-3 { margin-left: -8px; }
.vl-desc {
font-size: 14px;
color: #444;
text-align: center;
max-width: 450px;
line-height: 1.7;
word-break: keep-all;
}
</style></div></body>
</html>

View File

@@ -0,0 +1,104 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>venn-premium v6</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
/* Design Agent — 디자인 토큰 */
/* CLAUDE.md에 정의된 디자인 원칙을 CSS 변수로 구현 */
:root {
/* 색상 */
--color-primary: #1e293b;
--color-accent: #2563eb;
--color-neutral: #64748b;
--color-bg: #ffffff;
--color-bg-subtle: #f8fafc;
--color-border: #e2e8f0;
--color-danger: #dc2626;
--color-success: #16a34a;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-light: #94a3b8;
/* 폰트 크기 */
--font-title: 2rem;
--font-subtitle: 1.25rem;
--font-body: 0.95rem;
--font-caption: 0.8rem;
--font-small: 0.7rem;
/* 폰트 두께 */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
--weight-black: 900;
/* 여백 */
--spacing-page: 40px;
--spacing-block: 20px;
--spacing-inner: 16px;
--spacing-small: 8px;
/* 기타 */
--radius: 6px;
--border-width: 1px;
--accent-border: 3px;
--line-height-ko: 1.7;
}
body { font-family: "Pretendard Variable", sans-serif; background: #e8ecf0; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
.venn-premium { display: flex; flex-direction: column; align-items: center; gap: 16px; padding: 10px 0; }
.vp-diagram { position: relative; width: 480px; height: 480px; }
.vp-bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; z-index: 1; }
.vp-label {
position: absolute; z-index: 5; color: #fff;
text-align: center; font-weight: 700;
text-shadow: 0 2px 6px rgba(0,0,0,0.35);
transform: translate(-50%, -50%);
}
.vp-center { top: 22%; left: 50%; }
.vp-center .en { font-size: 13px; font-weight: 400; opacity: 0.9; }
.vp-center .ko { font-size: 24px; font-weight: 900; margin-top: 2px; }
.vp-dt { top: 51.8%; left: 40.2%; }
.vp-dt .name { font-size: 20px; font-weight: 800; }
.vp-dt .sub { font-size: 13px; font-weight: 400; opacity: 0.85; margin-top: 2px; }
.vp-bim { top: 41%; left: 60.6%; }
.vp-bim .name { font-size: 20px; font-weight: 800; }
.vp-gis { top: 62.9%; left: 60.3%; }
.vp-gis .name { font-size: 20px; font-weight: 800; }
.vp-desc { font-size: 14px; color: #444; text-align: center; max-width: 500px; line-height: 1.7; word-break: keep-all; }
</style>
</head>
<body>
<div class="block-container">
<div class="venn-premium">
<div class="vp-diagram">
<img class="vp-bg" src="venn_premium_bg.png" alt="">
<div class="vp-label vp-center">
<div class="en">Digital Transformation</div>
<div class="ko">디지털 전환(D/X)</div>
</div>
<div class="vp-label vp-dt">
<div class="name">Digital Twin</div>
<div class="sub">(Metaverse)</div>
</div>
<div class="vp-label vp-bim">
<div class="name">BIM</div>
</div>
<div class="vp-label vp-gis">
<div class="name">GIS</div>
</div>
</div>
<div class="vp-desc">건설산업의 DX는 GIS, BIM, 디지털 트윈의 기술 융합을 통해서만 실현 가능</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,121 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>venn SVG premium test</title>
<style>
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
body { font-family: "Pretendard Variable", sans-serif; background: #e0e5ea; display: flex; justify-content: center; padding: 30px; margin: 0; }
.block-container { width: 920px; background: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); display: flex; flex-direction: column; align-items: center; gap: 16px; }
.desc { font-size: 14px; color: #444; text-align: center; line-height: 1.7; }
</style>
</head>
<body>
<div class="block-container">
<svg viewBox="0 0 600 550" width="500" height="460" xmlns="http://www.w3.org/2000/svg" font-family="Pretendard Variable, sans-serif">
<defs>
<!-- 배경 그라데이션 (연한 회색→흰) -->
<linearGradient id="bgGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#dce3ea" />
<stop offset="100%" stop-color="#f0f2f5" />
</linearGradient>
<!-- 큰 파란 원 그라데이션 -->
<radialGradient id="blueOuter" cx="45%" cy="40%" r="55%">
<stop offset="0%" stop-color="#2979ff" />
<stop offset="40%" stop-color="#1565c0" />
<stop offset="100%" stop-color="#0d47a1" />
</radialGradient>
<!-- 주황→마젠타 원 -->
<radialGradient id="orangeGrad" cx="35%" cy="35%" r="65%">
<stop offset="0%" stop-color="#ffab40" />
<stop offset="40%" stop-color="#ff6d00" />
<stop offset="100%" stop-color="#c2185b" />
</radialGradient>
<!-- 민트/틸 원 -->
<radialGradient id="mintGrad" cx="40%" cy="30%" r="65%">
<stop offset="0%" stop-color="#80deea" />
<stop offset="40%" stop-color="#26c6da" />
<stop offset="100%" stop-color="#00897b" />
</radialGradient>
<!-- 골드 원 -->
<radialGradient id="goldGrad" cx="40%" cy="30%" r="65%">
<stop offset="0%" stop-color="#ffd54f" />
<stop offset="40%" stop-color="#ffb300" />
<stop offset="100%" stop-color="#e65100" />
</radialGradient>
<!-- 하이라이트 (구체 광택) -->
<radialGradient id="highlight" cx="35%" cy="25%" r="40%">
<stop offset="0%" stop-color="rgba(255,255,255,0.5)" />
<stop offset="100%" stop-color="rgba(255,255,255,0)" />
</radialGradient>
<!-- 글로우 필터 -->
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="8" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<!-- 그림자 -->
<filter id="shadow">
<feDropShadow dx="0" dy="4" stdDeviation="8" flood-color="rgba(0,0,0,0.3)" />
</filter>
<!-- 큰 원 안쪽 그림자 (깊이감) -->
<filter id="innerDepth" x="-10%" y="-10%" width="120%" height="120%">
<feGaussianBlur in="SourceAlpha" stdDeviation="15" result="blur" />
<feOffset dx="0" dy="5" result="offsetBlur" />
<feComposite in="SourceGraphic" in2="offsetBlur" operator="over" />
</filter>
</defs>
<!-- 배경 -->
<rect width="600" height="550" fill="url(#bgGrad)" rx="0" />
<!-- 큰 파란 원 + 동심원 링 (바깥→안쪽) -->
<circle cx="300" cy="270" r="230" fill="url(#blueOuter)" filter="url(#shadow)" />
<circle cx="300" cy="270" r="220" fill="none" stroke="#4a90d9" stroke-width="1.5" opacity="0.4" />
<circle cx="300" cy="270" r="205" fill="none" stroke="#5a9de0" stroke-width="1" opacity="0.3" />
<circle cx="300" cy="270" r="190" fill="none" stroke="#6aabe6" stroke-width="0.8" opacity="0.25" />
<circle cx="300" cy="270" r="175" fill="none" stroke="#7ab8ec" stroke-width="0.6" opacity="0.2" />
<!-- 주황→마젠타 원 (가장 큼, 좌측) -->
<circle cx="265" cy="300" r="120" fill="url(#orangeGrad)" opacity="0.92" filter="url(#glow)" />
<circle cx="265" cy="300" r="120" fill="url(#highlight)" />
<!-- 민트 원 (우상) -->
<circle cx="370" cy="230" r="75" fill="url(#mintGrad)" opacity="0.9" filter="url(#glow)" />
<circle cx="370" cy="230" r="75" fill="url(#highlight)" />
<!-- 골드 원 (우하) -->
<circle cx="365" cy="355" r="75" fill="url(#goldGrad)" opacity="0.9" filter="url(#glow)" />
<circle cx="365" cy="355" r="75" fill="url(#highlight)" />
<!-- 텍스트: 디지털 전환 (큰 원 상단) -->
<text x="300" y="95" text-anchor="middle" fill="#ffffff" font-size="13" font-weight="400" opacity="0.85">Digital Transformation</text>
<text x="300" y="125" text-anchor="middle" fill="#ffffff" font-size="26" font-weight="900">디지털 전환(D/X)</text>
<!-- 텍스트: Digital Twin (주황 원 중앙) -->
<text x="250" y="295" text-anchor="middle" fill="#ffffff" font-size="20" font-weight="800">Digital Twin</text>
<text x="250" y="318" text-anchor="middle" fill="#ffffff" font-size="13" font-weight="400" opacity="0.85">(Metaverse)</text>
<!-- 텍스트: BIM (민트 원 중앙) -->
<text x="370" y="237" text-anchor="middle" fill="#ffffff" font-size="20" font-weight="800">BIM</text>
<!-- 텍스트: GIS (골드 원 중앙) -->
<text x="365" y="362" text-anchor="middle" fill="#ffffff" font-size="20" font-weight="800">GIS</text>
</svg>
<div class="desc">건설산업의 DX는 GIS, BIM, 디지털 트윈의 기술 융합을 통해서만 실현 가능</div>
</div>
</body>
</html>

View File

@@ -0,0 +1 @@
{"status":429,"err":"Rate limit exceeded"}

View File

@@ -0,0 +1 @@
{"status":429,"err":"Rate limit exceeded"}

View File

@@ -0,0 +1 @@
{"status":429,"err":"Rate limit exceeded"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,254 @@
# Figma 디자인 분석 보고서
## 파일 정보
- **파일명:** 바론컨설턴트 홈페이지_기획팀공유
- **페이지:** 바론 공유 2025.05.13 (node: 1574-6254)
- **분석일:** 2026-03-25
---
## 1. 전체 구조
### 메인 페이지 (3장)
| 섹션 | 프레임 | 크기 | 스크린샷 |
|------|--------|------|---------|
| 1장 바론컨설턴트 | 1-1 미래 | 1920x5990 | `1장_1-1_미래.png` |
| | 기술 | 1920x5677 | `1장_기술.png` |
| | 가치 | 1920x4157 | `1장_가치.png` |
### 자세히보기 페이지 (13개 서브 프레임)
| 섹션 | 프레임 | 크기 | 스크린샷 |
|------|--------|------|---------|
| 2-1장 건설산업 DX | 2-1_01 건설산업 | 920x1231 | `2-1_01_건설산업.png` |
| | 2-1_02 BIM | 920x2179 | `2-1_02_BIM.png` |
| | 2-1_03 GIS | 920x2208 | `2-1_03_GIS.png` |
| | 2-1_04 디지털트윈 | 920x1651 | `2-1_04_디지털트윈.png` |
| 2-2장 DX와 SW | 2-2_01 | 920x3013 | `2-2_01.png` |
| | 2-2_02 | 920x1742 | `2-2_02.png` |
| | 2-2_03 | 920x2129 | `2-2_03.png` |
| | 2-2_04 | 920x1814 | `2-2_04.png` |
| 2-3장 DX 활용 | 2-3_01 ~ 2-3_05 | 920x2300~3016 | `2-3_01~05.png` |
---
## 2. 추출된 디자인 토큰
### 색상 팔레트
| 용도 | Figma 값 | 현재 tokens.css | 비고 |
|------|----------|---------------|------|
| **메인 텍스트** | `#000000` | `--color-primary: #1e293b` | Figma가 더 진함 |
| **포인트 (파란)** | `#004cbe` | `--color-accent: #2563eb` | Figma가 더 진한 남색 |
| **포인트 밝은** | `#006aff` | 없음 | **추가 필요:** `--color-accent-bright` |
| **포인트 하늘** | `#6bcdff` | 없음 | **추가 필요:** `--color-accent-light` |
| **포인트 하늘2** | `#00aaff` | 없음 | 참고 |
| **포인트 파랑** | `#008fff`, `#007cff` | 없음 | 원형 마커 색상 |
| **배경 밝은** | `#f6f7f9` | `--color-bg-subtle: #f8fafc` | 거의 동일 |
| **배경 흰** | `#ffffff` | `--color-bg: #ffffff` | 동일 |
| **섹션 타이틀** | `#ffffff` (배경 위) | - | 다크 배경 위 흰 텍스트 |
### 폰트 체계
| 용도 | Figma | 현재 tokens.css | 비고 |
|------|-------|---------------|------|
| **페이지 타이틀** | Noto Sans CJK KR, 35px, w700 | `--font-title: 2rem (32px)` | Figma가 약간 큼 |
| **영문 서브타이틀** | 15px, w400 | 없음 | section_title 영문 |
| **꼭지 제목** | 24px, w700 | `--font-subtitle: 1.25rem (20px)` | Figma가 큼 |
| **본문** | 16px, w400 | `--font-body: 0.95rem (15.2px)` | 거의 동일 |
| **카드 내 제목** | 14px, w700 | `--font-caption: 0.8rem (12.8px)` | Figma가 큼 |
| **카드 내 본문** | 14px, w400 | `--font-caption` | 동일 |
| **태그/라벨** | 20px, w700 | 없음 | DX/BIM/VS 라벨 |
| **소제목** | 13px, w400 | `--font-small: 0.7rem (11.2px)` | Figma가 약간 큼 |
### 간격/크기
| 요소 | Figma 값 | 비고 |
|------|----------|------|
| 프레임 폭 | 920px | 자세히보기 프레임 |
| 섹션 타이틀 영역 | 249x73px | 영문+한글 2줄 |
| 꼭지 제목+설명 영역 | 742x68~78px | 좌: 제목 240px, 우: 설명 422px |
| 카드 (3열) | 240x365px 각각 | 이미지+제목+설명 |
| 이미지 그리드 | 460x354px (2열) | 그라데이션 오버레이 포함 |
| 비교 박스 | 327x116px, 325x116px | 나란히 2개 |
| 원형 마커 | 10x10px, 151x151px | 작은 점, 큰 원 |
| 배경 헤더 영역 | 963x515px | 그라데이션+웨이브 |
---
## 3. 발견된 재사용 컴포넌트
### 공통 컴포넌트 (모든 자세히보기에 반복)
#### A. section_title (섹션 타이틀)
```
구조: FRAME 249x73
├── TEXT 15px/w400 #ffffff — 영문 (예: "Building Information Modeling")
└── TEXT 35px/w700 #ffffff — 한글 (예: "건설정보모델링(BIM)")
배경: 파란 그라데이션 헤더 위에 위치
```
- **모든 자세히보기 프레임의 상단**에 동일 구조로 반복
- 영문 서브타이틀 + 한글 메인 타이틀
- 흰색 텍스트 + 다크 배경
#### B. bg (배경 헤더)
```
구조: GROUP 1319x671
├── Mask group (그라데이션 + 웨이브 이미지)
├── RECTANGLE 그라데이션 (파란→투명)
├── RECTANGLE 흰색 배경
└── shape (벡터 웨이브 라인 7줄)
```
- 상단 파란 그라데이션 + 웨이브 패턴
- **이미지로 export해서 사용해야 하는 영역** (CSS 재현 불가)
#### C. sub_제목,내용 (꼭지 제목+설명)
```
구조: FRAME 742x68~78
├── TEXT 24px/w700 #004cbe — 질문/소제목 (좌측 240px)
└── TEXT 16px/w400 #000000 — 설명 (우측 422px)
```
- 좌: 파란 굵은 제목 (질문형)
- 우: 검정 본문 설명
- 2단 가로 배치 (240:422 비율)
#### D. close 버튼
```
구조: FRAME 40x40
└── VECTOR 23x23 #ffffff — X 아이콘
```
### 콘텐츠 컴포넌트
#### E. 카드 3열 (2-1_02 BIM 페이지)
```
구조: GROUP 751x377
├── FRAME 744x365 (카드 3장 컨테이너)
│ ├── RECTANGLE 240x365 #ffffff (카드1)
│ ├── RECTANGLE 240x365 #ffffff (카드2)
│ └── RECTANGLE 240x365 #ffffff (카드3)
├── 이미지 3장 (각 카드 상단)
└── FRAME 240x180~188 (각 카드 텍스트)
├── TEXT 14px/w700 — 단계명 (영문 포함)
├── RECTANGLE 구분선
└── TEXT 14px/w400 — 설명 불릿
```
- 카드 크기: 240x365px
- 상단: 이미지 (시공/설계/유지관리)
- 중간: 14px 굵은 제목 (색상으로 구분: `#00aaff`, `#006aff`, `#004cbe`)
- 하단: 14px 일반 설명
#### F. 비교 박스 2열 (2-1_02 BIM 페이지)
```
구조: 2개 GROUP 나란히
├── GROUP 327x116
│ ├── RECTANGLE #006aff (배경)
│ └── FRAME (그라데이션 내부)
└── GROUP 325x116
├── RECTANGLE #006aff (배경)
└── FRAME (그라데이션 내부)
+ DX/BIM/VS 라벨 (20px/w700, #ffffff)
```
- 파란 배경 박스 2개 나란히
- 내부에 방사형 그라데이션
- 가운데 "VS" 라벨
#### G. 이미지 그리드 2열 (2-1_02 BIM 페이지)
```
구조: 2개 GROUP 나란히
├── GROUP 460x354
│ ├── RECTANGLE 그라데이션 오버레이
│ └── RECTANGLE IMG (실제 이미지)
└── GROUP 460x354
├── RECTANGLE 그라데이션 오버레이
└── RECTANGLE IMG (실제 이미지)
```
- 2열 이미지 나란히 (460x354 각)
- 이미지 위에 그라데이션 오버레이 (어두워지는 효과)
#### H. 산맥 시각화 (2-1_02 BIM 페이지)
```
구조: GROUP 920x183
├── RECTANGLE 그라데이션 배경
├── GROUP 벡터 산맥 라인 (20+ VECTOR)
└── GROUP 수직 라인 마커
```
- **이미지로 export** (벡터 라인이 너무 복잡)
#### I. 원형 라벨 (2-1_02 BIM 페이지)
```
구조: GROUP 190x190
├── GROUP (원형 테두리 그룹)
├── ELLIPSE 151x151 그라데이션
├── ELLIPSE 145x145 그라데이션
└── TEXT 20px/w700 #ffffff — "단계별 BIM의 활용"
```
- 중앙 큰 원 + 텍스트
- 이중 원 테두리 (그라데이션)
---
## 4. 블록 매핑 (Figma 패턴 → design_agent 블록)
| Figma 패턴 | design_agent 블록 | 구현 방식 | 상태 |
|------------|-----------------|---------|------|
| section_title | `section-title` | HTML/CSS | **신규 필요** |
| sub_제목,내용 | 기존 없음 → `topic-header` | HTML/CSS (좌:제목 우:설명) | **신규 필요** |
| 카드 3열 (이미지+제목+설명) | `card-grid` 변형 | HTML/CSS (이미지 추가) | **변형 필요** |
| 비교 박스 2열 | `comparison` | HTML/CSS | ✅ 기존 |
| 이미지 그리드 2열 | `image-gallery` | HTML + 이미지 | **신규 필요** |
| 원형 라벨 | `circle-label` | CSS 원 + 텍스트 | **신규 필요** |
| 배경 헤더 (bg) | - | **이미지 export** | Figma export |
| 산맥 시각화 | - | **이미지 export** | Figma export |
| 벡터 웨이브 | - | **이미지 export** | Figma export |
| DX/BIM/VS 비교 라벨 | `comparison` 내부 | HTML/CSS | ✅ 기존 활용 |
### 이미지로 export해야 하는 요소
| 요소 | 이유 | export 형식 |
|------|------|-----------|
| bg (배경 헤더) | 그라데이션+웨이브+마스크 | PNG (배경용) |
| 산맥 시각화 | 20+ 벡터 라인 | PNG 또는 SVG |
| 이미지 오버레이 그라데이션 | CSS로 가능하지만 Figma 원본이 더 정확 | CSS gradient (재현 가능) |
| 3D 렌더링 이미지 | Figma 내 이미지 에셋 | PNG |
---
## 5. 디자인 토큰 업데이트 제안
기존 `tokens.css`에 추가가 필요한 토큰:
```css
/* Figma 기반 추가 토큰 */
--color-accent-deep: #004cbe; /* Figma 진한 파란 (꼭지 제목) */
--color-accent-bright: #006aff; /* Figma 밝은 파란 (카드 헤더, 박스) */
--color-accent-sky: #6bcdff; /* Figma 하늘색 (BIM 라벨) */
--color-accent-cyan: #00aaff; /* Figma 시안 (설계단계) */
--font-page-title: 35px; /* Figma 페이지 타이틀 */
--font-topic-title: 24px; /* Figma 꼭지 제목 */
--font-card-title: 14px; /* Figma 카드 내 제목 */
--font-en-subtitle: 15px; /* Figma 영문 서브타이틀 */
--font-tag: 20px; /* Figma 태그/라벨 (DX, BIM, VS) */
```
---
## 6. 다음 단계
### 즉시 가능 (HTML/CSS로 구현)
1. `section-title` 블록 — 영문+한글 2줄 타이틀
2. `topic-header` 블록 — 좌:질문 우:설명 (742px, 240:422 비율)
3. `card-grid` 이미지 변형 — 상단 이미지 + 하단 텍스트
4. `image-gallery` 블록 — 2열 이미지 + 오버레이
5. `circle-label` 블록 — CSS 원형 + 중앙 텍스트
### Figma에서 이미지 export 필요
1. 배경 헤더 (bg) — 각 프레임의 상단 파란 영역
2. 산맥/웨이브 시각화
3. 3D 렌더링 이미지 (시공, 설계, 유지관리 등)
4. 아이콘 에셋
### AI 이미지 생성 영역 (레이어 방식)
1. 순환도/관계도 배경
2. 3D 효과가 필요한 다이어그램
3. 복합 시각 효과 (글로우, 보케, 입체)

Binary file not shown.

After

Width:  |  Height:  |  Size: 784 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 964 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Some files were not shown because too many files have changed in this diff Show More