Compare commits
10 Commits
bc7c08e575
...
66c00924ed
| Author | SHA1 | Date | |
|---|---|---|---|
| 66c00924ed | |||
| 51548fdc41 | |||
| 360cd8e44c | |||
| c42e01f060 | |||
| d57860578f | |||
| 05703c8e72 | |||
| 076aeb0403 | |||
| 3d1194a562 | |||
| cba2ec2be7 | |||
| b13df8b176 |
65
.claude/settings.json
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(cmd /c \"npx -y figma-developer-mcp --help\")",
|
||||||
|
"Bash(npx -y figma-developer-mcp --version)",
|
||||||
|
"mcp__Framelink_Figma_MCP__get_figma_data",
|
||||||
|
"mcp__Framelink_Figma_MCP__download_figma_images",
|
||||||
|
"Bash(start \"\" \"d:/ad-hoc/kei/design_agent/figma_to_html_agent/block-tests/prerequisites-3col.html\")",
|
||||||
|
"Bash(python -c \"from selenium import webdriver; print\\('selenium OK'\\)\")",
|
||||||
|
"Bash(python -c ':*)",
|
||||||
|
"Bash(python)",
|
||||||
|
"Bash(start \"\" \"d:/ad-hoc/kei/design_agent/figma_to_html_agent/block-tests/bim-goal-circles.html\")",
|
||||||
|
"Bash(start \"\" \"d:/ad-hoc/kei/design_agent/figma_to_html_agent/block-tests/bg-shapes-only.html\")",
|
||||||
|
"Bash(start \"\" \"d:/ad-hoc/kei/design_agent/figma_to_html_agent/block-tests/bim-figma-devmode.html\")",
|
||||||
|
"Bash(claude mcp:*)",
|
||||||
|
"Bash(curl -sS -o /dev/null -w \"mcp endpoint: HTTP %{http_code}\\\\n\" http://127.0.0.1:3845/mcp)",
|
||||||
|
"Bash(curl -sS -o /dev/null -w \"sse endpoint: HTTP %{http_code}\\\\n\" http://127.0.0.1:3845/sse)",
|
||||||
|
"Bash(curl -sS -o /dev/null -w \"root: HTTP %{http_code}\\\\n\" http://127.0.0.1:3845/)",
|
||||||
|
"Bash(curl -s -o NUL -w \"%{http_code}\" http://127.0.0.1:3845/mcp)",
|
||||||
|
"Bash(curl -s -o /dev/null -w \"%{http_code}\" http://127.0.0.1:3845/sse --max-time 3)",
|
||||||
|
"Bash(curl -s -o /dev/null -w \"%{http_code}\" http://127.0.0.1:3845/mcp --max-time 3)",
|
||||||
|
"Bash(curl -s -X POST http://127.0.0.1:3845/mcp -H \"Content-Type: application/json\" -H \"Accept: application/json, text/event-stream\" -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{},\"clientInfo\":{\"name\":\"test\",\"version\":\"1.0\"}}}' --max-time 5)",
|
||||||
|
"Bash(curl -v http://127.0.0.1:3845/mcp)",
|
||||||
|
"Bash(curl -s -m 3 http://127.0.0.1:3845/sse)",
|
||||||
|
"Bash(curl -s -m 3 -o /dev/null -w \"%{http_code}\\\\n\" http://127.0.0.1:3845/sse)",
|
||||||
|
"mcp__figma-desktop__get_metadata",
|
||||||
|
"mcp__figma-desktop__get_screenshot",
|
||||||
|
"mcp__figma-desktop__get_design_context",
|
||||||
|
"Bash(curl -sSo bg_texture.png \"http://localhost:3845/assets/849658071be46a26936e8666e3722b2dd548aee7.png\")",
|
||||||
|
"Bash(curl -sSo arc_top.png \"http://localhost:3845/assets/f05ebf15a1125b6c5809f9ffa35b4e4e750687d3.png\")",
|
||||||
|
"Bash(curl -sSo arc_side.png \"http://localhost:3845/assets/2f0f17507c681b7bc2fb109f3d4fafb9ff2f7ce0.png\")",
|
||||||
|
"Bash(curl -sSo big_fill_productivity.svg \"http://localhost:3845/assets/21a89b8138bd47debcc6f12bb140ee63bbd9fdf2.svg\")",
|
||||||
|
"Bash(curl -sSo big_ring_productivity.svg \"http://localhost:3845/assets/fbe84134d2e14bbf84b2c42516e9b85ffe6f7c1e.svg\")",
|
||||||
|
"Bash(curl -sSo big_fill_safety.svg \"http://localhost:3845/assets/1f24875931dc3c36e2c841eaf5b94466fa035a48.svg\")",
|
||||||
|
"Bash(curl -sSo big_ring_safety.svg \"http://localhost:3845/assets/c5aeccdfc884051848fc60f04abf2a9d367dd731.svg\")",
|
||||||
|
"Bash(curl -sSo big_fill_trust.svg \"http://localhost:3845/assets/67ef527c29921d401d31032c02d6b3a0ae1d3050.svg\")",
|
||||||
|
"Bash(curl -sSo acc_outer_speed.svg \"http://localhost:3845/assets/1391787caa4cb8241a1adadbb2c70aed3625e1b8.svg\")",
|
||||||
|
"Bash(curl -sSo acc_inner_speed.svg \"http://localhost:3845/assets/eeb8e9bf8b1841215ae0253017512a7e4a6d5a95.svg\")",
|
||||||
|
"Bash(curl -sSo acc_inner_profit.svg \"http://localhost:3845/assets/4885055cba20f72e83401be371fe74b9b43ec869.svg\")",
|
||||||
|
"Bash(curl -sSo acc_outer_safety.svg \"http://localhost:3845/assets/688b5af1d813b16cd6410453e3d4d1f79c084222.svg\")",
|
||||||
|
"Bash(curl -sSo acc_inner_safety.svg \"http://localhost:3845/assets/2fab268821fc763dbdff12e1dd65820dfa9b628e.svg\")",
|
||||||
|
"Bash(ls block-tests/*.html block-tests/*.md)",
|
||||||
|
"Bash(python scripts/gradient_math.py --test)",
|
||||||
|
"Bash(python scripts/gradient_math.py --w 350 --h 350 --x1 110.833 --y1 18.2292 --x2 219.479 --y2 175 --stops \"0:#FDC69E,1:#E0782C\")",
|
||||||
|
"Bash(python -c \"from scripts.gradient_math import svg_to_css; print\\(svg_to_css\\(W=350,H=350,x1=110.833,y1=18.2292,x2=219.479,y2=175,stops=[\\(0,'#FDC69E'\\),\\(1,'#E0782C'\\)]\\)\\)\")",
|
||||||
|
"Bash(python render.py cards-3col-persona example)",
|
||||||
|
"Bash(python render.py cards-3col-persona example-no-photos)",
|
||||||
|
"Bash(python render.py cycle-3way-intersect example)",
|
||||||
|
"WebFetch(domain:claude.com)",
|
||||||
|
"WebFetch(domain:help.figma.com)",
|
||||||
|
"Bash(curl -sSo \"527bd7809f4b2e5f3cd42f2e713ccbfb37537d82.png\" \"http://localhost:3845/assets/527bd7809f4b2e5f3cd42f2e713ccbfb37537d82.png\")",
|
||||||
|
"Bash(ls \"d:/ad-hoc/kei/design_agent/figma_to_html_agent/block-tests/_renders/pill_flex_\"*)",
|
||||||
|
"Bash(awk '/visual_diff:/{found=1} found && /^- id:/{print NR\": \"$0; found=0}' \"d:/ad-hoc/kei/design_agent/templates/catalog.yaml\")",
|
||||||
|
"Bash(curl -s -o \"bg_slide_texture.png\" \"http://localhost:3845/assets/16a1b2ea5b64663a3ee44bfad24671a612952c29.png\")",
|
||||||
|
"Bash(curl -s -o \"line_divider.svg\" \"http://localhost:3845/assets/01731a60f7d9d35816932c019149e301a3aae1a7.svg\")",
|
||||||
|
"Bash(head -20 /d/ad-hoc/kei/design_agent/samples/mdx/01*.mdx)",
|
||||||
|
"Bash(head -20 /d/ad-hoc/kei/design_agent/samples/mdx/02*.mdx)",
|
||||||
|
"Bash(head -20 /d/ad-hoc/kei/design_agent/samples/mdx/03*.mdx)",
|
||||||
|
"Bash(ls -la /d/ad-hoc/kei/design_agent/data/runs/20260413_*/)"
|
||||||
|
],
|
||||||
|
"additionalDirectories": [
|
||||||
|
"d:\\ad-hoc\\kei\\design_agent\\templates\\blocks\\new"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
13
.mcp.json
@@ -1,15 +1,8 @@
|
|||||||
{
|
{
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"Framelink Figma MCP": {
|
"figma-desktop": {
|
||||||
"command": "cmd",
|
"type": "sse",
|
||||||
"args": [
|
"url": "http://127.0.0.1:3845/sse"
|
||||||
"/c",
|
|
||||||
"npx",
|
|
||||||
"-y",
|
|
||||||
"figma-developer-mcp",
|
|
||||||
"--figma-api-key=figd_s23TfSDL0hS97DIialy0R2P6QsoZQHfuGx1l_t-k",
|
|
||||||
"--stdio"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
# Design Agent 전체 구조 파악 리포트
|
# ⚠ DEPRECATED — 이 문서는 2026-03-27 기준 스냅샷입니다
|
||||||
|
|
||||||
|
> **최신 파이프라인 문서는 [`PIPELINE.md`](PIPELINE.md)를 참조하세요.**
|
||||||
|
> 이 문서는 Type A/B 분기, B'/B'' 변형, Stage 1.7/1.8 등 현재 구조를 반영하지 않습니다.
|
||||||
|
> 히스토리 참고용으로만 유지합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Design Agent 전체 구조 파악 리포트 (archived)
|
||||||
|
|
||||||
**작성일:** 2026-03-27
|
**작성일:** 2026-03-27
|
||||||
**목표:** design_agent의 아키텍처, 파이프라인, 코드 구조를 체계적으로 이해
|
**목표:** design_agent의 아키텍처, 파이프라인, 코드 구조를 체계적으로 이해
|
||||||
|
|||||||
304
PIPELINE.md
Normal file
@@ -0,0 +1,304 @@
|
|||||||
|
# Design Agent 파이프라인 현황
|
||||||
|
|
||||||
|
> **최종 갱신:** 2026-04-13
|
||||||
|
> **목적:** 새 세션의 AI가 이 문서만 읽으면 파이프라인 전체를 이해하고 작업할 수 있도록 한다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 전체 흐름 요약
|
||||||
|
|
||||||
|
```
|
||||||
|
MDX 입력
|
||||||
|
↓
|
||||||
|
[Stage 0] MDX 정규화 (코드)
|
||||||
|
↓
|
||||||
|
[Stage 1A] Kei 실장 — 꼭지 추출 (AI: Opus)
|
||||||
|
→ layout_template: A 또는 B 선택
|
||||||
|
↓
|
||||||
|
[Stage 1B] 컨셉 구체화 (AI: Opus)
|
||||||
|
→ relation_type, expression_hint, source_data
|
||||||
|
↓
|
||||||
|
[Stage 1B-ST] 구조화 텍스트 생성 (AI: Opus)
|
||||||
|
→ structured_text per topic
|
||||||
|
↓
|
||||||
|
[Stage 1.5a] 컨테이너 계산 (코드: 결정론적)
|
||||||
|
→ FontHierarchy, ContainerSpec, Preset
|
||||||
|
↓
|
||||||
|
[Stage 1.7] 블록 레퍼런스 선택 (코드 + AI 1회)
|
||||||
|
→ relation_type → 카테고리 → 필터 → 블록 결정
|
||||||
|
↓
|
||||||
|
[Stage 1.8] 적합성 검증 + 보강 (코드 + Selenium + AI)
|
||||||
|
→ overflow 감지 → Kei 에스컬레이션 → 재배분
|
||||||
|
↓
|
||||||
|
[Stage 1.5b] 디자인 예산 계산 (코드)
|
||||||
|
↓
|
||||||
|
[Stage 2] HTML 생성 (Type에 따라 다름)
|
||||||
|
→ Type B/B'/B'': block_assembler (코드)
|
||||||
|
→ Type A: Sonnet 재구성 (AI, 미완성)
|
||||||
|
↓
|
||||||
|
[Stage 3] 렌더링 조립 (Type A만, Jinja2)
|
||||||
|
↓
|
||||||
|
[Stage 4] 검증 (Selenium + Opus Vision)
|
||||||
|
→ overflow 측정 + 스크린샷 품질 평가
|
||||||
|
↓
|
||||||
|
최종 HTML 출력 (data/runs/{id}/final.html)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 레이아웃 유형 (Type A / B / B' / B'')
|
||||||
|
|
||||||
|
### 2.1 Kei가 선택하는 유형: A와 B
|
||||||
|
|
||||||
|
Kei 프롬프트(`src/kei_client.py:34-46`)에서 A 또는 B를 선택한다.
|
||||||
|
|
||||||
|
| 유형 | 조건 | Zone 구조 |
|
||||||
|
|------|------|-----------|
|
||||||
|
| **Type A** | 참조자료(용어 정의, 부록 등)가 별도로 존재 | body(배경+본심) + sidebar(첨부) + footer(결론) |
|
||||||
|
| **Type B** | 본문 흐름만. 배경/첨부가 없거나 억지로 만들어야 하면 | top + bottom_left + bottom_right + footer |
|
||||||
|
|
||||||
|
### 2.2 Type B 변형: B'과 B''
|
||||||
|
|
||||||
|
B'과 B''은 **Kei가 선택하지 않는다.** 특정 MDX 테스트 과정에서 하드코딩한 변형이다.
|
||||||
|
|
||||||
|
| 변형 | 생성 경위 | 차이점 | 코드 위치 |
|
||||||
|
|------|----------|--------|----------|
|
||||||
|
| **B** | 범용 | 상단(전체폭 텍스트+이미지) + 하단 2분할 + 결론 | `block_assembler.py:461` `_assemble_slide_html_type_b()` |
|
||||||
|
| **B'** | 03번 MDX 테스트 중 생성 | 상단이 세로 카드 형태 + 하단에 표 렌더링 + 불릿 전용 | `block_assembler.py:885` `_assemble_slide_html_type_b_prime()` |
|
||||||
|
| **B''** | B'에서 스타일 변형 | border/gradient 없음. 색상바+여백으로 구분 | `block_assembler_b2.py:9` `_assemble_slide_html_type_b_double_prime()` |
|
||||||
|
|
||||||
|
**분기 코드** (`block_assembler.py:370-378`):
|
||||||
|
```python
|
||||||
|
if ctx.analysis.layout_template == "B":
|
||||||
|
return _assemble_slide_html_type_b(ctx, title_text)
|
||||||
|
if ctx.analysis.layout_template == "B'":
|
||||||
|
return _assemble_slide_html_type_b_prime(ctx, title_text)
|
||||||
|
if ctx.analysis.layout_template == "B''":
|
||||||
|
return _assemble_slide_html_type_b_double_prime(ctx, title_text)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 향후 방향
|
||||||
|
|
||||||
|
B'/B''은 **범용화가 필요하다.** 현재는 03번 콘텐츠 구조(카드형+표)를 B 조립 함수가 커버하지 못해서 만든 땜질이다. 궁극적으로는 B 하나로 다양한 콘텐츠 구조를 커버하거나, AI가 서브타입을 판단하게 해야 한다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. MDX 샘플 ↔ 유형 매핑
|
||||||
|
|
||||||
|
| MDX | 파일 | 콘텐츠 성격 | 선택 유형 | 상태 |
|
||||||
|
|-----|------|-----------|----------|------|
|
||||||
|
| **01번** | `samples/mdx/01. 건설산업 DX의 올바른 이해(0127).mdx` | 용어 혼용 문제 + 용어 정의(참조) | **Type A** | ⚠ Stage 2 미완성 (Sonnet 의존) |
|
||||||
|
| **02번** | `samples/mdx/02. DX의 시행 목표 및 기대효과.mdx` | 본문 흐름 (3대 목표) | **Type B** | ✅ 동작 |
|
||||||
|
| **03번** | `samples/mdx/03. DX 시행을 위한 필수 요건 및 혁신 방안.mdx` | 카드형 구조 + 표 + 불릿 | **Type B'** | ✅ 동작 (하드코딩) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 단계별 상세
|
||||||
|
|
||||||
|
### Stage 0: MDX 정규화
|
||||||
|
- **파일:** `src/mdx_normalizer.py` → `normalize_mdx_content()`
|
||||||
|
- **입력:** raw MDX 텍스트
|
||||||
|
- **출력:** `NormalizedContent` (sections, images, tables 분리)
|
||||||
|
|
||||||
|
### Stage 1A: Kei 실장 — 꼭지 추출
|
||||||
|
- **파일:** `src/kei_client.py` → `classify_content()`
|
||||||
|
- **AI:** Opus (Kei API)
|
||||||
|
- **입력:** 정규화된 텍스트
|
||||||
|
- **출력:** `Analysis` (title, core_message, layout_template, total_pages, page_structure, topics)
|
||||||
|
- **핵심 판단:**
|
||||||
|
- 꼭지 5개 이내 추출
|
||||||
|
- 각 꼭지에 purpose, layer, role, emphasis, direction 부여
|
||||||
|
- layout_template = "A" 또는 "B" 선택
|
||||||
|
- page_structure에 역할별 weight(비중) 배정
|
||||||
|
|
||||||
|
### Stage 1B: 컨셉 구체화
|
||||||
|
- **파일:** `src/kei_client.py` → `refine_concepts()`
|
||||||
|
- **AI:** Opus
|
||||||
|
- **출력:** topics에 relation_type, expression_hint, source_data 추가
|
||||||
|
|
||||||
|
### Stage 1B-ST: 구조화 텍스트 생성
|
||||||
|
- **파일:** `src/kei_client.py` → `generate_structured_text()`
|
||||||
|
- **AI:** Opus
|
||||||
|
- **출력:** topic별 structured_text (마크다운 형태)
|
||||||
|
|
||||||
|
### Stage 1.5a: 컨테이너 계산 (결정론적)
|
||||||
|
- **파일:** `src/space_allocator.py`
|
||||||
|
- **함수:**
|
||||||
|
- Type A → `calculate_container_specs()`
|
||||||
|
- Type B/B'/B'' → `build_containers_type_b()`
|
||||||
|
- 공통 → `calculate_font_hierarchy()`, `select_preset()`
|
||||||
|
- **입력:** page_structure의 weight, slide 크기(1280×720)
|
||||||
|
- **출력:** 역할별 `ContainerSpec` (width_px, height_px, zone)
|
||||||
|
- **로직:** weight × available_px = 각 zone px 확정
|
||||||
|
|
||||||
|
### Stage 1.7: 블록 레퍼런스 선택
|
||||||
|
- **파일:** `src/block_reference.py` → `select_and_generate_references()`
|
||||||
|
- **로직 (코드 결정론적 + AI 1회):**
|
||||||
|
1. relation_type → 블록 카테고리 매핑
|
||||||
|
2. expression_hint 키워드 매칭
|
||||||
|
3. 컨테이너 크기 적합성 필터
|
||||||
|
4. role/zone 제약 (sidebar → visuals/media 제외)
|
||||||
|
5. catalog.yaml 존재 검증 (유령 블록 차단)
|
||||||
|
6. 후보 2-3개 → Kei 1회 호출로 최종 선택
|
||||||
|
- **출력:** 역할별 `BlockReference` (block_id, design_reference_html)
|
||||||
|
|
||||||
|
### Stage 1.8: 적합성 검증 + 보강
|
||||||
|
- **파일:** `src/fit_verifier.py` → `calculate_fit()`
|
||||||
|
- **로직:**
|
||||||
|
1. 텍스트 분량 vs 할당 공간 계산
|
||||||
|
2. Selenium으로 실제 높이 측정 (3회 루프)
|
||||||
|
3. overflow 시 → Kei 에스컬레이션 (`call_kei_fit_escalation()`)
|
||||||
|
- 팝업 분리 판단, zone 간 재배분
|
||||||
|
4. 보강 제안: bold 키워드, 팝업 요약 등
|
||||||
|
|
||||||
|
### Stage 2: HTML 생성
|
||||||
|
|
||||||
|
**Type B/B'/B'' (코드 조립):**
|
||||||
|
- **파일:** `src/block_assembler.py` → `assemble_slide_html()`
|
||||||
|
- 역할별 `assemble_role_html()` 호출 → 블록 HTML 조립
|
||||||
|
- structured_text + design_reference_html 결합
|
||||||
|
- 이미지/팝업 embed
|
||||||
|
- **즉시 완성 HTML 반환**
|
||||||
|
|
||||||
|
**Type A (AI 재구성, 미완성):**
|
||||||
|
- **파일:** `src/content_verifier.py` → `generate_with_retry()`
|
||||||
|
- Sonnet에 phase_t_context 전달 → CSS + 레이아웃 생성
|
||||||
|
- **현재 검증 불완전**
|
||||||
|
|
||||||
|
### Stage 3: 렌더링 조립 (Type A만)
|
||||||
|
- **파일:** `src/renderer.py` → `render_slide_from_html()`
|
||||||
|
- Type B는 Stage 2에서 완전한 HTML이므로 스킵
|
||||||
|
|
||||||
|
### Stage 4: 검증
|
||||||
|
- **파일:** `src/slide_measurer.py`
|
||||||
|
- `measure_rendered_heights()` — Selenium 실측
|
||||||
|
- `capture_slide_screenshot()` — 스크린샷 캡처
|
||||||
|
- **파일:** `src/kei_client.py` → `vision_quality_gate()`
|
||||||
|
- Opus 멀티모달: 스크린샷 보고 시각 품질 평가
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 핵심 파일 맵
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── main.py ← FastAPI 서버, POST /api/generate
|
||||||
|
├── pipeline.py ← 파이프라인 오케스트레이터 (generate_slide)
|
||||||
|
├── pipeline_context.py ← PipelineContext 데이터 클래스
|
||||||
|
├── config.py ← 설정 (API key, 슬라이드 크기)
|
||||||
|
│
|
||||||
|
├── mdx_normalizer.py ← Stage 0: MDX → NormalizedContent
|
||||||
|
├── kei_client.py ← Stage 1A/1B/1B-ST: Kei API 호출 + 프롬프트
|
||||||
|
├── space_allocator.py ← Stage 1.5a: 컨테이너 px 계산
|
||||||
|
├── block_reference.py ← Stage 1.7: 블록 선택 (relation_type 기반)
|
||||||
|
├── fit_verifier.py ← Stage 1.8: 적합성 검증 + Selenium 루프
|
||||||
|
├── block_assembler.py ← Stage 2: Type B/B' HTML 조립
|
||||||
|
├── block_assembler_b2.py ← Stage 2: Type B'' HTML 조립
|
||||||
|
├── content_verifier.py ← Stage 2: Type A HTML (Sonnet, 미완성)
|
||||||
|
├── renderer.py ← Stage 3: Jinja2 렌더링 (Type A만)
|
||||||
|
├── slide_measurer.py ← Stage 4: Selenium 측정 + 스크린샷
|
||||||
|
├── validators.py ← Kei 응답 검증 (A/B별 구조 확인)
|
||||||
|
│
|
||||||
|
├── image_utils.py ← 이미지 크기 측정 + data URI 변환
|
||||||
|
├── svg_calculator.py ← SVG 다이어그램 좌표 계산
|
||||||
|
└── sse_utils.py ← SSE 스트리밍 유틸
|
||||||
|
|
||||||
|
templates/
|
||||||
|
├── slide-base.html ← 슬라이드 기본 구조 (Jinja2, CSS Grid)
|
||||||
|
├── catalog.yaml ← 블록 라이브러리 정의 (50+개)
|
||||||
|
└── blocks/ ← 블록 HTML 템플릿
|
||||||
|
├── headers/ (8개)
|
||||||
|
├── cards/ (17개)
|
||||||
|
├── emphasis/ (12개)
|
||||||
|
├── tables/ (8개)
|
||||||
|
├── visuals/ (6개)
|
||||||
|
├── media/ (4개)
|
||||||
|
└── BEPs/ (6개)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 데이터 흐름 (PipelineContext)
|
||||||
|
|
||||||
|
```
|
||||||
|
PipelineContext:
|
||||||
|
raw_content ← 원본 MDX
|
||||||
|
normalized ← NormalizedContent (sections, images, tables)
|
||||||
|
analysis ← Analysis (title, core_message, layout_template, page_structure, topics)
|
||||||
|
topics ← list[Topic] (relation_type, expression_hint, structured_text 포함)
|
||||||
|
page_structure ← PageStructure (roles → {topic_ids, weight, zone})
|
||||||
|
containers ← dict[role → ContainerSpec(width_px, height_px)]
|
||||||
|
font_hierarchy ← FontHierarchy (key_msg, core, bg, sidebar 폰트 크기)
|
||||||
|
references ← dict[role → list[BlockReference]]
|
||||||
|
sub_layouts ← dict[role → SubLayout]
|
||||||
|
fit_result ← 역할별 fit_status, 재배분값
|
||||||
|
enhancement_result ← bold_keywords, popup_summaries 등
|
||||||
|
generated_html ← Stage 2 출력
|
||||||
|
rendered_html ← Stage 3 출력 (완전 HTML)
|
||||||
|
measurement ← Selenium 측정값
|
||||||
|
quality_score ← 0-100
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 현재 구현 상태 (Phase Y-11~13, 2026-04-15)
|
||||||
|
|
||||||
|
> Phase Y: slide-base 기반 파이프라인 재설계. 상세: `docs/history/PHASE-Y-PLAN.md`
|
||||||
|
|
||||||
|
### 파이프라인 흐름 (현재)
|
||||||
|
|
||||||
|
```
|
||||||
|
[Stage 0] MDX → normalized.sections (source of truth)
|
||||||
|
[Stage 1A] Kei 꼭지 추출 (영역/zone 판단 안 함)
|
||||||
|
[Phase Y] 코드: normalized → 대목차 추출 → group schema 분류 → 블록 매칭 → 영역 확정
|
||||||
|
[Stage 1.5a] space_allocator: weight → zone px (% 기반)
|
||||||
|
[Stage 1.7] block_reference: tag_match → schema_match → fallback 순서
|
||||||
|
[Stage 1.8] assembler(measure_mode) → Selenium 측정 → fit 루프
|
||||||
|
[Stage 2] assembler(slide-base + 블록) → final HTML
|
||||||
|
[Stage 4] Selenium overflow + 비전 (-1 미평가)
|
||||||
|
```
|
||||||
|
|
||||||
|
### MDX별 상태
|
||||||
|
|
||||||
|
| MDX | 상태 | 비고 |
|
||||||
|
|-----|------|------|
|
||||||
|
| **03** | ✅ 동작 | prerequisites-3col + pp2. 텍스트 누락 없음. 회귀 기준. |
|
||||||
|
| **02** | ⚠ schema 1차 | top: parallel_3_with_image. bottom: 분류 정교화 필요. |
|
||||||
|
| **01** | ⬜ 미착수 | Type A. 별도 작업. |
|
||||||
|
|
||||||
|
### 핵심 원칙 (확립됨)
|
||||||
|
|
||||||
|
- source of truth = normalized.sections (Stage 0)
|
||||||
|
- 영역 = 코드가 결정 (Kei 아님). sub_titles 기반 + group schema.
|
||||||
|
- 블록 CSS에 최종 고정값. slide_font_css는 공통 레이아웃 계약만.
|
||||||
|
- zone = % 기반, block = height:100%.
|
||||||
|
- 글씨 크기 고정. fit은 padding → 내용량 → font 1단계(responsive tier).
|
||||||
|
- 기존 경로 삭제 금지. 새 schema 점진적 추가. MDX 03 회귀 기준.
|
||||||
|
- 하드코딩 금지. 프로세스가 결과를 만드는 구조.
|
||||||
|
|
||||||
|
3. **블록 글씨 크기 하드코딩 (px 고정)**
|
||||||
|
- 블록 CSS에 font-size가 Figma 원본 px로 고정
|
||||||
|
- 컨테이너 크기에 따라 조정 불가 → overflow 원인
|
||||||
|
- CSS 변수(`var(--block-font-heading)`)로 전환 → assembler가 zone 크기에 따라 계산
|
||||||
|
|
||||||
|
### 미해결 프로세스
|
||||||
|
1. **overflow 시 font 조정 루프** — 재배분만으로 부족할 때 font/padding 줄이기 (Y-5)
|
||||||
|
2. **Sonnet redesign 경로** — tag 매칭 실패 시 블록 단위 redesign → 저장 (Y-6)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 검증 계획
|
||||||
|
|
||||||
|
업데이트된 템플릿이 파이프라인에서 제대로 동작하는지 확인한다.
|
||||||
|
|
||||||
|
| MDX | 유형 | 검증 포인트 |
|
||||||
|
|-----|------|-----------|
|
||||||
|
| **01번** | Type A | 업데이트된 블록 + slide-base.html로 조립 정상 동작 |
|
||||||
|
| **02번** | Type B | 업데이트된 블록 선택 + 조립 + overflow 없음 |
|
||||||
|
| **03번** | Type B' | 카드/표 구조가 업데이트된 템플릿으로 정상 렌더링 |
|
||||||
|
|
||||||
|
### 검증 방법
|
||||||
|
1. 각 MDX를 파이프라인에 투입
|
||||||
|
2. 중간 산출물(step1_analysis.json 등) 확인 — 블록 선택이 의도대로인지
|
||||||
|
3. 최종 HTML(final.html) 렌더링 — overflow, 시각 품질 확인
|
||||||
|
4. 업데이트 전/후 비교
|
||||||
431
README.md
@@ -1,303 +1,248 @@
|
|||||||
# Kei Design Agent
|
# Kei Design Agent
|
||||||
|
|
||||||
콘텐츠를 시각적으로 구조화된 슬라이드 HTML(1280×720px, 16:9)로 변환하는 AI 파이프라인.
|
MDX 콘텐츠를 분석해 1280x720 슬라이드 HTML로 변환하는 파이프라인입니다.
|
||||||
|
|
||||||
## 개요
|
이 문서는 "지금 실제 코드 기준으로 파이프라인이 어떻게 동작하는지"를 빠르게 파악하기 위한 개요 문서입니다. 과거 Phase 문서와 일부 legacy 경로는 남아 있지만, 아래 설명은 현재 메인 경로를 기준으로 정리했습니다.
|
||||||
|
|
||||||
텍스트/MDX 콘텐츠를 입력하면:
|
## 한눈에 보기
|
||||||
1. Kei 실장(Opus)이 정보 구조와 비중을 판단하고
|
|
||||||
2. 코드가 컨테이너 크기를 계산하고
|
|
||||||
3. 블록을 선택하고
|
|
||||||
4. 콘텐츠-컨테이너 적합성을 검증하고
|
|
||||||
5. AI(Sonnet)가 블록 디자인을 참고하여 HTML을 생성하고
|
|
||||||
6. 코드가 슬라이드 프레임에 조립하고
|
|
||||||
7. 측정+비전 모델로 검증합니다
|
|
||||||
|
|
||||||
---
|
파이프라인의 큰 흐름은 아래와 같습니다.
|
||||||
|
|
||||||
## 파이프라인 (10단계)
|
1. MDX를 코드가 정규화한다.
|
||||||
|
2. Kei가 문서의 의미와 topic을 읽는다.
|
||||||
|
3. 코드는 `normalized.sections`를 기준으로 실제 슬라이드 구조를 다시 만든다.
|
||||||
|
4. 코드는 schema, recipe, tag를 바탕으로 블록을 고른다.
|
||||||
|
5. Type B는 코드가 템플릿을 조립해 `final.html`을 만든다.
|
||||||
|
6. Type A는 아직 AI/renderer 비중이 더 크다.
|
||||||
|
7. Selenium과 vision gate로 측정/검증한 뒤 run 산출물을 저장한다.
|
||||||
|
|
||||||
```
|
핵심 원칙은 다음과 같습니다.
|
||||||
MDX 원본
|
|
||||||
↓
|
|
||||||
[Stage 0] MDX 정규화 (코드)
|
|
||||||
↓
|
|
||||||
[Stage 1A] 꼭지 추출 + 영역 배정 (Kei API / Opus)
|
|
||||||
↓
|
|
||||||
[Stage 1B] 컨셉 구체화 (Kei API / Opus)
|
|
||||||
↓
|
|
||||||
[Stage 1.5a] 컨테이너 초기 계산 (코드)
|
|
||||||
↓
|
|
||||||
[Stage 1.7] 블록 선택 (코드)
|
|
||||||
↓
|
|
||||||
[Stage 1.8] 적합성 검증 + 재배분 + 보강 (코드 + Kei 에스컬레이션)
|
|
||||||
↓
|
|
||||||
[Stage 1.5b] 디자인 예산 재계산 (코드)
|
|
||||||
↓
|
|
||||||
[Stage 2] HTML 생성 (영역별 개별 호출) (Claude Sonnet)
|
|
||||||
↓
|
|
||||||
[Stage 3] 렌더링 조립 + 후처리 (코드)
|
|
||||||
↓
|
|
||||||
[Stage 4] 측정 + 품질 검증 (Selenium + Opus Vision)
|
|
||||||
↓
|
|
||||||
검증 통과 시 → final.html 저장 + 팝업 분리 (파일 출력)
|
|
||||||
```
|
|
||||||
|
|
||||||
※ Stage 4 이후의 파일 저장은 별도 Stage가 아닌 후처리입니다.
|
- source of truth는 `normalized.sections`
|
||||||
|
- block 선택은 문서명 하드코딩이 아니라 shape, schema, tag 기반
|
||||||
|
- 흐름의 우선순위는 `구조 -> payload -> layout -> fit`
|
||||||
|
- popup/detail은 overflow를 덮는 임시 장치가 아니라 `메인 요약 + 상세 보기`의 2단 표현 계약
|
||||||
|
|
||||||
---
|
## 타입 구조
|
||||||
|
|
||||||
## 단계별 상세
|
현재 메인 타입 선택은 사실상 `A`와 `B`입니다.
|
||||||
|
|
||||||
### Stage 0: MDX 정규화
|
### Type A
|
||||||
|
|
||||||
| 항목 | 내용 |
|
- 본문 외에 sidebar, reference, 부록성 영역이 함께 필요한 슬라이드
|
||||||
|------|------|
|
- 현재는 Type B보다 덜 닫혀 있고, AI 생성 + renderer 경로 비중이 큽니다
|
||||||
| **목적** | 원본 MDX에서 JSX/frontmatter를 제거하고, 섹션/팝업/이미지/테이블로 분리 |
|
|
||||||
| **적용기술** | 코드 (`normalize_mdx_content()`) |
|
|
||||||
| **인풋** | 원본 MDX 문자열 |
|
|
||||||
| **아웃풋** | `normalized` — clean_text, title, sections[], popups[], images[], tables[] |
|
|
||||||
| **연계** | → Stage 1A가 clean_text를 Kei에게 전달 |
|
|
||||||
|
|
||||||
### Stage 1A: 꼭지 추출 + 영역 배정
|
### Type B
|
||||||
|
|
||||||
| 항목 | 내용 |
|
- top, bottom 같은 본문 zone 조합으로 해결되는 슬라이드
|
||||||
|------|------|
|
- 현재 가장 안정적인 메인 경로입니다
|
||||||
| **목적** | 콘텐츠에서 핵심 파트(꼭지)를 식별하고, 슬라이드의 어떤 영역(배경/본심/첨부/결론)에 배치할지 결정 |
|
- 최근 구조화 작업은 대부분 이 Type B 경로를 중심으로 진행되었습니다
|
||||||
| **적용기술** | Kei API (`classify_content()`) |
|
|
||||||
| **인풋** | normalized.clean_text |
|
|
||||||
| **아웃풋** | `topics[]` (id, title, purpose, layer, relation_type, expression_hint), `page_structure` (role별 topic_ids, weight) |
|
|
||||||
| **연계** | → Stage 1B가 각 꼭지를 구체화 |
|
|
||||||
|
|
||||||
### Stage 1B: 컨셉 구체화
|
### Type B' / B''
|
||||||
|
|
||||||
| 항목 | 내용 |
|
- 역사적으로 실험/호환 경로에서 나온 변형입니다
|
||||||
|------|------|
|
- 문서와 일부 legacy 코드에 흔적이 남아 있습니다
|
||||||
| **목적** | 각 꼭지에 실제 원본 텍스트(source_data)와 요약(summary)을 매핑 |
|
- 현재 메인 개념의 1급 타입으로 보기보다, 과거 흐름과 호환 레이어로 이해하는 편이 맞습니다
|
||||||
| **적용기술** | Kei API (`refine_concepts()`) |
|
|
||||||
| **인풋** | topics + clean_text |
|
|
||||||
| **아웃풋** | `topics` 업데이트 — source_data, summary 추가 |
|
|
||||||
| **연계** | → Stage 1.5a가 텍스트 양을 기반으로 컨테이너 비율 계산 |
|
|
||||||
|
|
||||||
### Stage 1.5a: 컨테이너 초기 계산
|
## 단계별 파이프라인
|
||||||
|
|
||||||
| 항목 | 내용 |
|
아래는 현재 기준의 실질적인 단계입니다.
|
||||||
|------|------|
|
|
||||||
| **목적** | 폰트 위계 확정 + 슬라이드 내 영역별 컨테이너 크기(px) 계산 + 프리셋 선택 |
|
|
||||||
| **적용기술** | 코드 (`calculate_font_hierarchy()`, `calculate_dynamic_ratio()`, `calculate_container_specs()`) |
|
|
||||||
| **인풋** | topics, page_structure (weight), preset |
|
|
||||||
| **아웃풋** | `font_hierarchy` (key_msg/core/bg/sidebar px), `container_ratio` (71:29 등), `containers` (role별 width_px, height_px), `preset` |
|
|
||||||
| **연계** | → Stage 1.7이 컨테이너 크기를 보고 블록 선택 |
|
|
||||||
|
|
||||||
### Stage 1.7: 블록 선택
|
| 단계 | 담당 | 주요 파일 | 하는 일 | 주요 산출물 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Stage 0 | 코드 | `src/mdx_normalizer.py` | MDX를 정규화하고 sections, tables, images, popups로 분리 | `NormalizedContent` |
|
||||||
|
| Stage 1A | AI (Kei/Opus) | `src/kei_client.py` | title, core message, topic, 초기 layout 힌트 추출 | `Analysis`, `Topic[]` |
|
||||||
|
| Phase Y | 코드 | `src/pipeline.py`, `src/section_parser.py` | `normalized.sections` 기반으로 group schema, recipe, zone/page_structure 결정 | `PageStructure`, `mdx_sections` |
|
||||||
|
| Stage 1B | AI (Kei/Opus) | `src/kei_client.py` | topic별 relation, source_data, 표현 힌트 보강 | 보강된 `Topic[]` |
|
||||||
|
| Stage 1B-ST | AI (Kei/Opus) | `src/kei_client.py` | structured_text 생성 | `Topic.structured_text` |
|
||||||
|
| Stage 1.5a | 코드 | `src/space_allocator.py` | zone/container 크기, preset, font hierarchy 계산 | `containers`, `font_hierarchy` |
|
||||||
|
| Stage 1.7 | 코드 | `src/block_reference.py`, `templates/catalog.yaml` | tag_match, schema_match, fallback 기준으로 block 선택 | `references` |
|
||||||
|
| Stage 1.8 | 코드 + Selenium + 일부 AI | `src/pipeline.py`, `src/slide_measurer.py` | fit 측정, overflow 확인, 재배분, 보정 | `fit_result`, `measurement` |
|
||||||
|
| Stage 2 | 코드 중심 | `src/block_assembler.py` | Type B 기준 slide-base + block template + payload 조립 | `generated_html` |
|
||||||
|
| Stage 3 | 코드 | `src/renderer.py` | Type A 쪽 Jinja/renderer 조립, Type B는 대체로 생략 | `rendered_html` |
|
||||||
|
| Stage 4 | 코드 + Vision AI | `src/slide_measurer.py`, `src/kei_client.py` | Selenium overflow 측정, screenshot, vision quality 평가 | `measurement`, `quality_score` |
|
||||||
|
| Stage 5 | 코드 | `src/pipeline.py` | `final.html`, `final_context.json`, popup/detail html 저장 | run 산출물 |
|
||||||
|
|
||||||
| 항목 | 내용 |
|
## 현재 메인 실행 경로
|
||||||
|------|------|
|
|
||||||
| **목적** | 각 꼭지의 relation_type + expression_hint + 컨테이너 크기로 적합한 블록 결정. 같은 영역 꼭지들의 layer가 다르면 주종관계 판단 (블록 1개로 합침) |
|
|
||||||
| **적용기술** | 코드 (`select_and_generate_references()`) — catalog.yaml 기반 결정론적 매칭 |
|
|
||||||
| **인풋** | topics, containers, page_structure |
|
|
||||||
| **아웃풋** | `references` — role별 block_id, variant, design_reference_html, topic_id, is_hierarchical, supporting_topic_ids |
|
|
||||||
| **연계** | → Stage 1.8이 선택된 블록+콘텐츠가 컨테이너에 맞는지 검증 |
|
|
||||||
|
|
||||||
### Stage 1.8: 적합성 검증 + 재배분 + 보강 + 서브 컨테이너
|
### Type B 메인 경로
|
||||||
|
|
||||||
| 항목 | 내용 |
|
지금 실전에서 가장 중요한 경로는 아래입니다.
|
||||||
|------|------|
|
|
||||||
| **목적** | 콘텐츠가 컨테이너에 들어가는지 검증 → 안 맞으면 재배분 → 여전히 안 되면 Kei 에스컬레이션 → 여유 공간에 보충 콘텐츠 → 서브 컨테이너 배치 계산 |
|
|
||||||
| **적용기술** | 코드 (`calculate_fit()`, `redistribute()`, `analyze_enhancements()`, `apply_enhancements()`, `calculate_sub_layout()`) + Kei API (에스컬레이션 시 `call_kei_fit_escalation()`) |
|
|
||||||
| **인풋** | topics, containers, references, font_hierarchy, normalized, core_message |
|
|
||||||
| **아웃풋** | `containers` (재배분된 height_px), `fit_result` (role별 fit_status, redistribution), `enhancement_result` (V-7 subordinate_treatments, V-8 supplement_blocks, V-9 emphasis_blocks, V-10 bold_keywords, V-4 kei_decisions), `sub_layouts` (role별 서브 컨테이너 name/width/height, table_rows) |
|
|
||||||
| **내부 흐름** | Step 1: 필요 높이 계산 → Step 2: 재배분 → Step 3: Kei 에스컬레이션 → Step 4-5: 보강 분석+적용 → Step 6: fit 재검증 → Step 7: 서브 컨테이너 배치 → Step 8: 확정 |
|
|
||||||
| **연계** | → Stage 1.5b가 재배분된 크기로 디자인 예산 재계산, → Stage 2가 sub_layouts + enhancements를 프롬프트에 반영 |
|
|
||||||
|
|
||||||
### Stage 1.5b: 디자인 예산 재계산
|
1. Stage 0에서 MDX를 정규화
|
||||||
|
2. Stage 1A/1B에서 Kei가 의미와 topic 추출
|
||||||
|
3. Phase Y에서 코드가 `normalized.sections`를 읽고 page_structure를 다시 생성
|
||||||
|
4. Stage 1.7에서 block 선택
|
||||||
|
5. Stage 1.8에서 fit/overflow 검증
|
||||||
|
6. Stage 2에서 `assemble_slide_html_final()`로 최종 HTML 조립
|
||||||
|
7. Stage 4/5에서 측정과 산출물 저장
|
||||||
|
|
||||||
| 항목 | 내용 |
|
Type B의 핵심 파일은 아래입니다.
|
||||||
|------|------|
|
|
||||||
| **목적** | 재배분된 컨테이너 크기 + 선택된 블록 schema 기준으로 영역별 가용 공간 계산 |
|
|
||||||
| **적용기술** | 코드 (`calculate_design_budget()`) |
|
|
||||||
| **인풋** | containers (재배분 후), references (블록 schema) |
|
|
||||||
| **아웃풋** | `containers` 업데이트 — design_budget (available_height_px, available_width_px, fits) |
|
|
||||||
| **연계** | → Stage 2가 design_budgets를 프롬프트에 포함 |
|
|
||||||
|
|
||||||
### Stage 2: HTML 생성 (영역별 개별 호출)
|
- `src/pipeline.py`
|
||||||
|
- `src/section_parser.py`
|
||||||
|
- `src/block_reference.py`
|
||||||
|
- `src/block_assembler.py`
|
||||||
|
- `src/space_allocator.py`
|
||||||
|
- `templates/catalog.yaml`
|
||||||
|
|
||||||
| 항목 | 내용 |
|
### Type A 경로
|
||||||
|------|------|
|
|
||||||
| **목적** | page_structure에 존재하는 각 역할(배경/본심/첨부/결론)의 HTML을 **영역별 개별 Sonnet 호출**로 생성. 블록 디자인을 참고하되 콘텐츠가 구조를 결정 (Phase R' 방식) |
|
|
||||||
| **적용기술** | Claude Sonnet API — 영역당 1회 호출 (`build_area_prompt()` → `_call_claude()`) |
|
|
||||||
| **인풋** | raw_content, topics, containers, font_hierarchy, references (design_reference_html), sub_layouts (서브 컨테이너 치수), enhancements (V-4~V-10 지시), design_budgets |
|
|
||||||
| **호출 흐름** | Sonnet(배경) → bg_html, Sonnet(본심) → core_html, Sonnet(첨부) → sidebar_html, Sonnet(결론) → footer_html. 해당 역할에 꼭지가 없으면 스킵. body_html = bg_html + spacer + core_html |
|
|
||||||
| **아웃풋** | `generated_html` — body_html, sidebar_html, footer_html |
|
|
||||||
| **프롬프트에 포함되는 것** | 서브 컨테이너 레이아웃 제약, 디자인 레퍼런스 HTML (블록 CSS 참고), Kei 에스컬레이션 결정, 종속 꼭지 처리 지시, 보충 블록 지시, 강조 문장, bold 키워드, 폰트/컨테이너 크기 제약 |
|
|
||||||
| **연계** | → Stage 3이 영역별 HTML을 슬라이드 프레임에 배치 |
|
|
||||||
|
|
||||||
### Stage 3: 렌더링 조립 + 후처리
|
Type A는 현재도 살아 있지만, Type B만큼 단단하게 닫힌 상태는 아닙니다.
|
||||||
|
|
||||||
| 항목 | 내용 |
|
- AI 생성 비중이 더 큼
|
||||||
|------|------|
|
- `src/renderer.py` 의존도가 더 큼
|
||||||
| **목적** | 생성된 HTML 조각을 CSS Grid 슬라이드 프레임에 삽입 + 후처리 (폰트 캡핑, overflow 제거, sidebar width 조정, bold 변환) |
|
- sidebar/reference 구조를 포함하는 쪽에서 의미가 큼
|
||||||
| **적용기술** | 코드 (`render_slide_from_html()`) |
|
|
||||||
| **인풋** | generated_html, preset (grid_areas, grid_columns), font_hierarchy, container_ratio |
|
|
||||||
| **아웃풋** | `rendered_html` → `final.html` 파일 저장 |
|
|
||||||
| **연계** | → Stage 4가 렌더링 결과를 측정+검증 |
|
|
||||||
|
|
||||||
### Stage 4: 품질 검증
|
## schema -> recipe -> block
|
||||||
|
|
||||||
| 항목 | 내용 |
|
최근 구조화에서 가장 중요한 변화 중 하나는 `schema -> recipe -> block` 레이어입니다.
|
||||||
|------|------|
|
|
||||||
| **목적** | Selenium으로 실제 브라우저 렌더링 후 overflow 측정 + Opus Vision으로 시각적 품질 평가 |
|
|
||||||
| **적용기술** | Selenium (`measure_rendered_heights()`) + Claude Opus Vision (`vision_quality_gate()`) |
|
|
||||||
| **인풋** | rendered_html |
|
|
||||||
| **아웃풋** | `measurement` (zone별 clientHeight, scrollHeight, overflow, excess_px), `quality_score` |
|
|
||||||
| **연계** | 파이프라인 완료. overflow 시 경고 포함하여 진행 |
|
|
||||||
|
|
||||||
---
|
### schema
|
||||||
|
|
||||||
## 중간 산출물
|
콘텐츠의 의미 구조입니다.
|
||||||
|
|
||||||
파이프라인 실행마다 `data/runs/{timestamp}/`에 단계별 결과가 저장된다.
|
예:
|
||||||
|
|
||||||
### JSON Context (Stage별 누적 상태)
|
- `parallel_cluster`
|
||||||
| 파일 | Stage | 내용 |
|
- `parallel_cluster_plus_visual`
|
||||||
|------|-------|------|
|
- `compare_asymmetric_paired`
|
||||||
| `stage_0_context.json` | 0 | normalized (섹션, 팝업, 이미지) |
|
- `sequence_plus_visual`
|
||||||
| `stage_1a_context.json` | 1A | topics, page_structure |
|
- `single_block`
|
||||||
| `stage_1b_context.json` | 1B | topics (source_data 추가) |
|
|
||||||
| `stage_1_5a_context.json` | 1.5a | font_hierarchy, containers, ratio |
|
|
||||||
| `stage_1_7_context.json` | 1.7 | references (블록 선택 결과) |
|
|
||||||
| `stage_1_8_context.json` | 1.8 | fit_result, enhancements, sub_layouts |
|
|
||||||
| `stage_1_5b_context.json` | 1.5b | containers (design_budget 추가) |
|
|
||||||
| `stage_2_context.json` | 2 | generated_html |
|
|
||||||
| `stage_3_context.json` | 3 | (rendered_html은 final.html로 별도 저장) |
|
|
||||||
| `stage_4_context.json` | 4 | measurement, quality_score |
|
|
||||||
| `final_context.json` | 최종 | 전체 context |
|
|
||||||
|
|
||||||
### HTML 시각화 (`steps/` 폴더)
|
### recipe
|
||||||
| 파일 | Stage | 내용 |
|
|
||||||
|------|-------|------|
|
|
||||||
| `stage_0.html` | 0 | 섹션/팝업/이미지 목록 |
|
|
||||||
| `stage_1a.html` | 1A | 꼭지 테이블 (purpose, layer, 영역) |
|
|
||||||
| `stage_1b.html` | 1B | 꼭지 + source_data + summary |
|
|
||||||
| `stage_1_5a.html` | 1.5a | 빈 컨테이너 (1280×720) |
|
|
||||||
| `stage_1_5a_content.html` | 1.5a | 컨테이너에 콘텐츠 배치 |
|
|
||||||
| `stage_1_5b.html` | 1.5b | 디자인 예산 (available height/width) |
|
|
||||||
| `stage_1_7.html` | 1.7 | 블록 선택 표시 |
|
|
||||||
| `stage_1_8_fit_before.html` | 1.8 | 적합성 (재배분 전) |
|
|
||||||
| `stage_1_8_fit_after.html` | 1.8 | 재배분 후 + 보강 |
|
|
||||||
| `stage_1_8_blocks.html` | 1.8 | SLOT 구조 + 블록 디자인 + 주종관계 (1280×720) |
|
|
||||||
| `stage_2.html` | 2 | 영역별 Sonnet 출력을 실제 렌더링 (역할별 개별 확인) |
|
|
||||||
| `stage_3.html` | 3 | 영역을 합쳐 슬라이드 프레임에 배치한 결과 (1280×720 실제 렌더링) |
|
|
||||||
| `stage_4.html` | 4 | 측정 결과 + 품질 점수 |
|
|
||||||
|
|
||||||
---
|
block 이름이 아니라 표현 규칙입니다.
|
||||||
|
|
||||||
## 핵심 원칙
|
예:
|
||||||
|
|
||||||
1. **콘텐츠가 구조를 결정** — 블록 CSS는 참고만. AI가 콘텐츠 전달 의도를 보고 HTML 구조 결정 (Phase R')
|
- `single_block`
|
||||||
2. **하드코딩 금지** — font-size 외 모든 수치는 동적 계산. 어떤 MDX가 들어와도 동일하게 동작
|
- `two_col_text_visual`
|
||||||
3. **스크롤 절대 금지** — overflow:auto/scroll 어떤 영역에서도 불허
|
- `stacked_summary_detail`
|
||||||
4. **Kei API 필수** — fallback 없음. 성공할 때까지 무한 재시도
|
|
||||||
5. **AI가 옵션 생성, Kei가 결정** — 공간 부족 시 하드코딩 대응이 아니라 Kei 판단 요청
|
|
||||||
6. **계산 먼저, AI 판단 나중에, 렌더링은 검증만**
|
|
||||||
7. **overflow 상태에서 출력 금지** — Vision 모델 품질 게이트 통과 필수
|
|
||||||
|
|
||||||
---
|
recipe는 이런 계약을 가질 수 있습니다.
|
||||||
|
|
||||||
## 블록 라이브러리 (38개)
|
- left/right kind
|
||||||
|
- top/bottom kind
|
||||||
|
- ratio
|
||||||
|
- vertical align
|
||||||
|
|
||||||
6개 카테고리, 38개 블록. 각 블록은 `catalog.yaml`에 용도(when), 금지(not_for), purpose_fit, schema(슬롯 정의)가 있음.
|
### block
|
||||||
|
|
||||||
| 카테고리 | 개수 | 용도 |
|
실제 구현 템플릿 후보입니다.
|
||||||
|---------|------|------|
|
|
||||||
| **headers** | 5 | 타이틀, 꼭지 헤더 |
|
|
||||||
| **cards** | 9 | 항목 나열, 카드 그리드 |
|
|
||||||
| **tables** | 3 | 비교표, 데이터 테이블 |
|
|
||||||
| **visuals** | 6 | SVG 다이어그램, 관계도 |
|
|
||||||
| **emphasis** | 10 | 강조, 인용, 결론, 불릿 |
|
|
||||||
| **media** | 5 | 이미지/사진 |
|
|
||||||
|
|
||||||
---
|
예:
|
||||||
|
|
||||||
## 기술 스택
|
- `prerequisites-3col`
|
||||||
|
- `process-product-2col`
|
||||||
|
- `compare-detail-gradient`
|
||||||
|
- `card-icon-desc`
|
||||||
|
|
||||||
| 역할 | 도구 |
|
즉, "무슨 문서냐"가 아니라 "무슨 구조냐"를 먼저 읽고, 그 구조에 맞는 표현 규칙을 정한 뒤, 마지막에 구현 블록을 고르는 방향으로 가고 있습니다.
|
||||||
|------|------|
|
|
||||||
| 서버 | FastAPI + uvicorn (포트 8001) |
|
|
||||||
| AI (Kei 실장/편집자) | Kei API → Opus (localhost:8000) |
|
|
||||||
| AI (HTML 생성) | Anthropic API → Claude Sonnet |
|
|
||||||
| AI (품질 검증) | Anthropic API → Claude Opus Vision |
|
|
||||||
| 블록 검색 | FAISS + bge-m3 |
|
|
||||||
| 템플릿 | Jinja2 (블록 디자인 레퍼런스용) |
|
|
||||||
| 렌더링 | CSS Grid + 디자인 토큰 (1280×720) |
|
|
||||||
| 렌더링 측정 | Selenium headless Chrome |
|
|
||||||
| SVG 시각화 | svg_calculator.py (N개 동적 배치) |
|
|
||||||
| 이미지 | Pillow (크기 측정) + base64 인라인 |
|
|
||||||
| 폰트 | Pretendard Variable |
|
|
||||||
| 공간 계산 | space_allocator.py + fit_verifier.py (결정론적) |
|
|
||||||
|
|
||||||
---
|
## popup / detail 계약
|
||||||
|
|
||||||
## 설치 및 실행
|
popup은 지금 다음 철학으로 정리되는 중입니다.
|
||||||
|
|
||||||
```bash
|
- 메인 슬라이드에는 존 크기에 맞는 요약만 남긴다
|
||||||
# 설치
|
- 큰 표, 시각 컴포넌트, 과다한 bullet은 상세 popup으로 분리한다
|
||||||
cd design_agent
|
- 메인에서는 `자세히보기` 링크를 제공한다
|
||||||
pip install -e .
|
|
||||||
|
|
||||||
# FAISS 인덱스 빌드 (블록 추가/수정 시)
|
현재 popup 관련 핵심은 아래입니다.
|
||||||
python scripts/build_block_index.py
|
|
||||||
|
|
||||||
# .env 설정
|
- `PopupItem` 모델이 도입되어 popup 데이터를 명시적으로 다룸
|
||||||
ANTHROPIC_API_KEY=sk-ant-...
|
- `popup_id`, `popup_file` 생애주기를 분리해 관리 중
|
||||||
KEI_API_URL=http://localhost:8000
|
- 최종 목표는 popup 판단을 휴리스틱이 아니라 명시적 contract로 만드는 것
|
||||||
LOG_LEVEL=DEBUG
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
다만 아직 일부 구간엔 추측 로직과 이중 관리가 남아 있어, 이 부분은 계속 정리 중입니다.
|
||||||
# 터미널 1: Kei API (필수)
|
|
||||||
cd D:\ad-hoc\kei\persona_agent
|
|
||||||
python -m uvicorn backend.main:app --host 127.0.0.1 --port 8000
|
|
||||||
|
|
||||||
# 터미널 2: Design Agent
|
## run 산출물 구조
|
||||||
cd D:\ad-hoc\kei\design_agent
|
|
||||||
python -m uvicorn src.main:app --host 127.0.0.1 --port 8001 --reload
|
|
||||||
```
|
|
||||||
|
|
||||||
접속: http://localhost:8001
|
각 실행은 `data/runs/{run_id}/` 아래에 저장됩니다.
|
||||||
|
|
||||||
---
|
주요 파일은 다음과 같습니다.
|
||||||
|
|
||||||
## 개선 이력
|
- `final.html`
|
||||||
|
- `final_context.json`
|
||||||
|
- `steps/*.html`
|
||||||
|
- popup/detail html
|
||||||
|
|
||||||
| Phase | 내용 | 상태 |
|
### `final.html`
|
||||||
|-------|------|------|
|
|
||||||
| A~D | 슬라이드 품질 핵심 | 완료 |
|
|
||||||
| G~N | Kei API, 스토리라인, 정합성, 블록 선택, 비중, 측정 | 완료 |
|
|
||||||
| O | 컨테이너 기반 레이아웃 | 완료 |
|
|
||||||
| P | 다후보 렌더링 비교 | 완료 (20/100점 → 방향 전환) |
|
|
||||||
| Q | 제약 기반 블록 선택 | 완료 |
|
|
||||||
| R | 하이브리드 블록 (실패 — P=Q=R 동일 구조) | 실패 |
|
|
||||||
| R' | 블록 CSS 참고 + AI 구조 결정 | 설계 확정 |
|
|
||||||
| S | 검증 합격 프롬프트 + Claude HTML 생성 | 설계 확정 |
|
|
||||||
| T | 11-Stage 파이프라인 + 디자인 레퍼런스 | 완료 (31/31 통과) |
|
|
||||||
| V | 적합성 검증 + Kei 에스컬레이션 + 서브 컨테이너 | 완료 |
|
|
||||||
| W | Stage 2 출력 품질 수정 (6건) | 진행 중 |
|
|
||||||
|
|
||||||
---
|
- 최종 렌더 결과
|
||||||
|
- 실제 눈으로 보는 산출물
|
||||||
|
|
||||||
## Kei Persona와의 관계
|
### `final_context.json`
|
||||||
|
|
||||||
```
|
- 각 단계 결과를 최종 context 형태로 저장
|
||||||
Kei Persona Agent (localhost:8000)
|
- block 선택, page_structure, measurement, quality_score 등을 확인할 때 가장 중요
|
||||||
├── Opus + RAG + 세션 컨텍스트
|
|
||||||
├── 도메인 지식 (건설/DX/BIM)
|
### `steps/*.html`
|
||||||
└── 대화/생성/피드백/실행 모드
|
|
||||||
|
- 단계별 디버그/설명용 보드
|
||||||
|
- 현재는 검토용으로 유용하지만, 일부 인코딩/설명 품질은 더 다듬을 필요가 있습니다
|
||||||
|
|
||||||
|
## 자주 봐야 하는 파일
|
||||||
|
|
||||||
|
### 파이프라인 핵심
|
||||||
|
|
||||||
|
- [src/pipeline.py](src/pipeline.py)
|
||||||
|
- [src/pipeline_context.py](src/pipeline_context.py)
|
||||||
|
- [src/section_parser.py](src/section_parser.py)
|
||||||
|
- [src/block_reference.py](src/block_reference.py)
|
||||||
|
- [src/block_assembler.py](src/block_assembler.py)
|
||||||
|
- [src/space_allocator.py](src/space_allocator.py)
|
||||||
|
|
||||||
|
### 템플릿 / 카탈로그
|
||||||
|
|
||||||
|
- [templates/catalog.yaml](templates/catalog.yaml)
|
||||||
|
- [templates/blocks/new/prerequisites-3col.html](templates/blocks/new/prerequisites-3col.html)
|
||||||
|
- [templates/blocks/redesign/process-product-2col.html](templates/blocks/redesign/process-product-2col.html)
|
||||||
|
- [templates/blocks/cards/compare-detail-gradient.html](templates/blocks/cards/compare-detail-gradient.html)
|
||||||
|
- [templates/blocks/slide-base.html](templates/blocks/slide-base.html)
|
||||||
|
주의: 현재 삭제/legacy 정리 여부를 별도로 확인해야 하는 경로입니다.
|
||||||
|
|
||||||
|
### 검증 / 측정
|
||||||
|
|
||||||
|
- [src/slide_measurer.py](src/slide_measurer.py)
|
||||||
|
- [src/validators.py](src/validators.py)
|
||||||
|
|
||||||
|
### 역사 / 계획 문서
|
||||||
|
|
||||||
|
- [PIPELINE.md](PIPELINE.md)
|
||||||
|
- [docs/history/PHASE-Y-PLAN.md](docs/history/PHASE-Y-PLAN.md)
|
||||||
|
- [ARCHITECTURE_OVERVIEW.md](ARCHITECTURE_OVERVIEW.md)
|
||||||
|
|
||||||
|
## 현재 상태 요약
|
||||||
|
|
||||||
|
### 잘 닫혀가는 것
|
||||||
|
|
||||||
|
- Type B 메인 경로
|
||||||
|
- `normalized.sections` 기반 구조 해석
|
||||||
|
- schema / recipe 기반 block selection
|
||||||
|
- `prerequisites-3col`, `process-product-2col` 같은 redesign 블록 자산화
|
||||||
|
- popup/detail 2단 표현 계약의 초안 연결
|
||||||
|
|
||||||
|
### 아직 정리 중인 것
|
||||||
|
|
||||||
|
- Type A 전체 안정화
|
||||||
|
- popup을 완전한 source of truth로 정리
|
||||||
|
- `tag_match` 와 `schema_match`의 완전한 동등 점수 비교
|
||||||
|
- step 보드 인코딩/설명 품질
|
||||||
|
- legacy 경로와 문서의 정리
|
||||||
|
|
||||||
|
## 읽는 방법 추천
|
||||||
|
|
||||||
|
프로세스를 빠르게 파악하려면 아래 순서가 좋습니다.
|
||||||
|
|
||||||
|
1. 이 `README.md`
|
||||||
|
2. [src/pipeline.py](src/pipeline.py)
|
||||||
|
3. [src/section_parser.py](src/section_parser.py)
|
||||||
|
4. [src/block_assembler.py](src/block_assembler.py)
|
||||||
|
5. 최근 run의 `final_context.json`
|
||||||
|
|
||||||
|
히스토리까지 보려면 아래 문서를 이어서 보면 좋습니다.
|
||||||
|
|
||||||
|
- [PIPELINE.md](PIPELINE.md)
|
||||||
|
- [docs/history/PHASE-Y-PLAN.md](docs/history/PHASE-Y-PLAN.md)
|
||||||
|
|
||||||
Design Agent (localhost:8001, 이 프로젝트)
|
|
||||||
├── 슬라이드 생성 전용
|
|
||||||
├── Kei API로 꼭지 추출(1A) + 컨셉 구체화(1B) + 에스컬레이션(1.8) 호출
|
|
||||||
├── Sonnet으로 HTML 생성(Stage 2)
|
|
||||||
├── Opus Vision으로 품질 검증(Stage 4)
|
|
||||||
└── 두 프로젝트는 독립. 코드 공유 없음. API 연동만.
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
# 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": ""},
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 936 KiB |
611
docs/history/PHASE-Y-PLAN.md
Normal file
@@ -0,0 +1,611 @@
|
|||||||
|
# Phase Y: slide-base 기반 블록 조립 파이프라인 재설계
|
||||||
|
|
||||||
|
> **작성일:** 2026-04-14
|
||||||
|
> **목적:** assembler를 slide-base.html 기반으로 재작성. 블록 선택 → 배치 → 측정 → 조정 루프 완성.
|
||||||
|
> **근거:** Phase X-BX까지 assembler가 slide-base를 무시하고 HTML을 처음부터 생성. 블록 선택(1.7)이 조립(Stage 2)에 반영 안 됨. Stage 4 품질 점수 거짓말. 전체 파이프라인 연결 끊김.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 핵심 전환
|
||||||
|
|
||||||
|
```
|
||||||
|
[이전] assembler가 HTML 전체를 하드코딩 생성. 블록 무시. slide-base 미사용.
|
||||||
|
[이후] slide-base.html 위에 tag 매칭된 블록을 배치. 사전계산 + 실측 조정 루프.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 전체 흐름 (2026-04-15 재설계)
|
||||||
|
|
||||||
|
```
|
||||||
|
[1] slide-base.html 로드
|
||||||
|
├── title → .slide-title
|
||||||
|
├── 핵심요약(:::note) → .slide-footer (footer_text)
|
||||||
|
└── .slide-body (590px 가용) → 영역(zone)들이 여기에 배치됨
|
||||||
|
|
||||||
|
[2] Stage 1A: Kei 꼭지 추출 (영역 없이, 꼭지만)
|
||||||
|
├── Kei에게 zone/영역 판단을 시키지 않음
|
||||||
|
├── 꼭지별 title, purpose, layer, relation_type만 추출
|
||||||
|
└── 핵심요약은 conclusion_text로 분리
|
||||||
|
|
||||||
|
[3] 코드: MDX ## 파싱 → 꼭지-대목차 매핑
|
||||||
|
├── MDX에서 ## 대목차 목록 추출 (## 없는 도입부도 포함)
|
||||||
|
├── 각 꼭지의 source_data/title이 어느 ## 아래에 속하는지 매핑
|
||||||
|
└── 대목차별 꼭지 묶음 생성
|
||||||
|
|
||||||
|
[4] 코드: 대목차별 묶음으로 블록 tag 매칭 시도 (영역 확정 단계)
|
||||||
|
├── 묶음별 item_count + 꼭지 title → catalog tag 검색
|
||||||
|
├── 매칭됨 → 이 묶음 = 하나의 영역 (코드가 확정, 블록도 확정)
|
||||||
|
└── 매칭 안 됨 → Kei에게 이 꼭지들의 영역 판단 요청
|
||||||
|
├── "이 꼭지들을 어떻게 묶을지?"
|
||||||
|
├── "sidebar로 뺄 것이 있는지?" → Type A 결정
|
||||||
|
└── Kei는 주어진 꼭지 목록 안에서만 판단 (새 영역 이름 만들지 않음)
|
||||||
|
|
||||||
|
[5] 영역 확정 + 콘텐츠 소스 결정
|
||||||
|
├── 영역 제목 = MDX 원본 ## 제목 그대로
|
||||||
|
├── 영역 콘텐츠 = normalized.sections에서 MDX 원본 텍스트
|
||||||
|
├── Kei structured_text가 아님 (Kei는 구조 판단만)
|
||||||
|
└── sidebar 지정된 영역 → Type A / 나머지 → Type B
|
||||||
|
|
||||||
|
[6] 사전 계산: 영역별 비중 → 대략적 px 배정
|
||||||
|
└── 블록 후보 필터링 (너무 큰 블록 제외)
|
||||||
|
|
||||||
|
[7] .slide-body에 블록 배치 + 실측 조정 루프
|
||||||
|
├── 블록 템플릿 Jinja2 렌더링 (슬롯에 MDX 원본 텍스트 삽입)
|
||||||
|
├── slide-base + 블록 HTML 조합
|
||||||
|
├── Selenium 측정 (measure_mode: overflow:auto) → overflow 확인
|
||||||
|
├── overflow 시:
|
||||||
|
│ ├── ① font/padding 조정 (CSS 변수, 코드)
|
||||||
|
│ ├── ② 간격 축소
|
||||||
|
│ ├── ③ 텍스트 압축 (최후 수단, AI 1회)
|
||||||
|
│ └── 재측정 (최대 3회 루프)
|
||||||
|
└── overflow 없음 → 확정
|
||||||
|
|
||||||
|
[8] 최종 HTML 출력
|
||||||
|
└── slide-base + 확정된 블록들 = final.html (overflow:hidden)
|
||||||
|
|
||||||
|
[9] 품질 검증
|
||||||
|
├── Selenium overflow 측정
|
||||||
|
├── 비전 모델 평가 (가능할 때만)
|
||||||
|
└── 미평가 시 -1 (거짓말 안 함)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 핵심 변경 (이전 대비)
|
||||||
|
|
||||||
|
| 항목 | 이전 (문제) | 이후 (재설계) |
|
||||||
|
|------|-----------|-------------|
|
||||||
|
| 영역/zone 결정 | Kei가 zone 구조를 만듦 → 매번 다름, 오인 | 코드가 ##파싱 + 블록매칭으로 먼저 확정. 안 되는 것만 Kei |
|
||||||
|
| 콘텐츠 소스 | Kei structured_text (재구성) | MDX 원본 (normalized.sections) |
|
||||||
|
| 영역 제목 | Kei가 축약 ("필수요건") | MDX 원본 ## 제목 그대로 |
|
||||||
|
| Kei 역할 | 꼭지+영역+zone+텍스트 전부 | 꼭지 추출 + 성격 판단(sidebar/팝업)만 |
|
||||||
|
| 블록 매칭 시점 | 영역 확정 후 | 영역 확정 전 (블록이 영역을 결정) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 태스크 목록
|
||||||
|
|
||||||
|
### Y-1: Kei 프롬프트 — zone = 대목차 (완료)
|
||||||
|
- [x] zone = `##` 대목차 단위, `###` 소목차 = zone 안 블록
|
||||||
|
- [x] 2단계 판단: 먼저 zone 잡고 → 용어정의 있으면 Type A
|
||||||
|
- [x] 결론/핵심요약 = slide-base footer (별도 zone 아님)
|
||||||
|
- [ ] Kei 프롬프트에서 결론 zone 제거 (page_structure에서 제외)
|
||||||
|
|
||||||
|
### Y-2: space_allocator — zone=bottom 지원
|
||||||
|
- [ ] `bottom` zone 전체폭 처리 추가
|
||||||
|
- [ ] Type A/B에 따라 .slide-body 내 레이아웃 분기
|
||||||
|
- [ ] 결론 zone 미생성 (slide-base footer로 처리)
|
||||||
|
|
||||||
|
### Y-3: block_reference — tag 매칭 강화
|
||||||
|
- [x] tag 기반 0순위 매칭 추가
|
||||||
|
- [x] item_count 범위("2-3") 처리
|
||||||
|
- [ ] content_pattern 가중치 추가 (item_count만으로 매칭 방지)
|
||||||
|
- [ ] 결론 role 매칭 제외 (slide-base가 처리)
|
||||||
|
|
||||||
|
### Y-4: assembler 재작성 — slide-base 기반
|
||||||
|
- [ ] slide-base.html 로드 → Jinja2 렌더링
|
||||||
|
- [ ] title, footer_text(핵심요약) 삽입
|
||||||
|
- [ ] .slide-body에 zone별 블록 HTML 삽입
|
||||||
|
- [ ] render_block_for_role()로 블록 렌더링
|
||||||
|
- [ ] 블록 CSS를 slide-base <style>에 합침
|
||||||
|
- [ ] 기존 하드코딩 assembler 제거 (fallback 아님)
|
||||||
|
|
||||||
|
### Y-5: 사이즈 조정 루프
|
||||||
|
- [ ] 사전 계산: zone 비중 → px 배정 → 블록 필터링
|
||||||
|
- [ ] 실측 조정: Selenium 측정 → overflow → font/padding 조정
|
||||||
|
- [ ] 루프 최대 3회
|
||||||
|
- [ ] overflow 해소 안 되면 텍스트 압축 (AI 1회)
|
||||||
|
|
||||||
|
### Y-6: Sonnet redesign 경로
|
||||||
|
- [ ] tag 매칭 실패 시 유사 블록 선택
|
||||||
|
- [ ] Sonnet에 블록 HTML + 콘텐츠 구조 전달 → 블록 단위 redesign
|
||||||
|
- [ ] redesign 결과를 templates/blocks/redesign/에 저장
|
||||||
|
- [ ] catalog.yaml, INDEX.md 자동 업데이트
|
||||||
|
|
||||||
|
### Y-7: step_visualizer 정합성
|
||||||
|
- [ ] 각 stage step HTML이 실제 파이프라인 데이터와 일치
|
||||||
|
- [ ] stage_1_8_blocks → 실제 블록 렌더링 (샘플 아님)
|
||||||
|
- [ ] stage_2 → slide-base + 블록 조합 결과 표시
|
||||||
|
|
||||||
|
### Y-8: Stage 4 품질 검증
|
||||||
|
- [x] 비전 실패 시 -1 (거짓말 방지)
|
||||||
|
- [x] 비전 미평가 시 차단 안 함, 경고만
|
||||||
|
- [ ] Selenium overflow 검사 정확도 검증
|
||||||
|
|
||||||
|
### Y-9: 검증 — MDX 03 end-to-end
|
||||||
|
- [ ] MDX 03 파이프라인 실행
|
||||||
|
- [ ] 각 stage 전후 데이터 연결 확인
|
||||||
|
- [ ] 최종 결과물이 85점 수준인지 시각 확인
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 의존 관계
|
||||||
|
|
||||||
|
```
|
||||||
|
Y-1 (Kei 프롬프트) ──→ Y-2 (space_allocator) ──→ Y-3 (block_reference)
|
||||||
|
↓
|
||||||
|
Y-4 (assembler 재작성)
|
||||||
|
↓
|
||||||
|
Y-5 (사이즈 루프)
|
||||||
|
↓
|
||||||
|
Y-6 (Sonnet redesign)
|
||||||
|
↓
|
||||||
|
Y-7 (step_visualizer)
|
||||||
|
↓
|
||||||
|
Y-8 + Y-9 (검증)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 블록 tag 업데이트 (별도 작업)
|
||||||
|
- 사용자가 다른 클로드와 진행
|
||||||
|
- catalog.yaml에 tags 필드 추가
|
||||||
|
- content_pattern, item_count, content_example 등
|
||||||
|
- Phase Y와 병렬 진행 가능
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-04-14 진행 결과
|
||||||
|
|
||||||
|
### 완료된 것
|
||||||
|
|
||||||
|
| 태스크 | 상태 | 비고 |
|
||||||
|
|--------|------|------|
|
||||||
|
| Y-1 Kei 프롬프트 | ✅ | zone=대목차(##) 단위. 결론=conclusion_text 필드. page_structure에서 제거. |
|
||||||
|
| Y-2 space_allocator | ✅ | zone=bottom 전체폭 지원. 결론 zone 미생성. slide-body 590px 기준. |
|
||||||
|
| Y-3 block_reference | ✅ | tag 매칭 0순위. item_count+content_example AND 조건. 결론 매칭 제외. |
|
||||||
|
| Y-4 assembler | ✅ | slide-base.html 로드 → zone별 블록 렌더링 → .slide-body에 배치. measure_mode 분리. |
|
||||||
|
| Y-7 step_visualizer | ✅ | 1_5a~1_8 모두 slide-base 기반 _wrap(). _hdr/_box/_calc_coords 제거. |
|
||||||
|
| Y-8 Stage 4 | ✅ | 비전 실패 시 -1. 거짓말 방지. |
|
||||||
|
| validator | ✅ | 결론 zone 필수 검증 제거. |
|
||||||
|
| slide_measurer | ✅ | zone- 클래스 감지 추가 (area-만 보던 것 수정). |
|
||||||
|
|
||||||
|
### 검증된 것 (MDX 03 파이프라인 실행)
|
||||||
|
|
||||||
|
```
|
||||||
|
Kei → zone 2개 (필수요건[1,2,3] + 혁신과변화[4,5]) + conclusion_text ✅
|
||||||
|
space_allocator → top 295px + bottom 287px (합 582px, gap 8px = 590px) ✅
|
||||||
|
block_reference → prerequisites-3col (tag_match) + compare-detail-gradient (tag_match) ✅
|
||||||
|
assembler → slide-base + 블록 렌더링 → .slide-body에 배치 ✅
|
||||||
|
Selenium → zone별 overflow 감지 (bottom +190px) ✅
|
||||||
|
Stage 4 → -1 미평가 ✅
|
||||||
|
```
|
||||||
|
|
||||||
|
### 근본 설계 오류 (2026-04-14 발견)
|
||||||
|
|
||||||
|
**근본 오류: 콘텐츠 소스가 잘못됨**
|
||||||
|
- 현재: Kei가 structured_text를 재구성 → assembler가 이걸 블록에 넣음
|
||||||
|
- 올바른 방향: **assembler는 normalized.sections(MDX 원본 텍스트)에서 직접 가져옴**
|
||||||
|
- Kei 역할: 구조 판단만 (zone 분류, 팝업 분리, 블록 선택). **텍스트 재구성 안 함.**
|
||||||
|
- zone 제목도 Kei가 줄인 role_name이 아니라, MDX 원본 `##` 제목을 그대로 사용
|
||||||
|
- 데이터 흐름 변경:
|
||||||
|
```
|
||||||
|
[현재] MDX → Kei structured_text(재구성) → assembler
|
||||||
|
[올바름] MDX → normalized.sections(원본) → assembler
|
||||||
|
Kei → 구조 판단(zone/블록/팝업) → assembler에 지시만
|
||||||
|
```
|
||||||
|
- 수정 대상:
|
||||||
|
1. assembler: `topic.structured_text` 대신 `normalized.sections`에서 원본 텍스트 가져오기
|
||||||
|
2. zone 제목: `role_name` 대신 `normalized.sections`의 `##` 제목 사용
|
||||||
|
3. Kei의 topic_ids로 어떤 section이 어떤 zone에 가는지 매핑
|
||||||
|
|
||||||
|
### 미해결 오류 (수정 중)
|
||||||
|
|
||||||
|
**오류 1: 블록 색상이 Figma 원본과 다름** → nth-child로 수정 완료 (인라인 style 제거)
|
||||||
|
**오류 2: ### 마크다운 헤더 그대로 노출** → _parse_topic_to_items에서 ### 스킵 수정 완료
|
||||||
|
**오류 3: 글씨 크기 px 고정** → CSS 변수 전환 수정 완료
|
||||||
|
|
||||||
|
**오류 4: slide-base HTML 주석이 출력에 노출** → re.sub로 주석 제거 수정 완료
|
||||||
|
**오류 5: zone-bottom 중복 (body가 2번 삽입)** → 주석 안 {% block body %} 제거로 수정
|
||||||
|
**오류 6: zone 제목이 Kei 축약본** → 근본 오류. normalized.sections에서 원본 제목 가져와야 함
|
||||||
|
|
||||||
|
### 미해결 오류 (원래 3건, 추가 발견 포함)
|
||||||
|
|
||||||
|
**오류 1: 블록 색상이 Figma 원본과 다름** (수정 완료)
|
||||||
|
- prerequisites-3col 블록: 3열이 각각 다른 색(파랑/금/초록)이어야 하는데 전부 같은 색
|
||||||
|
- 원인: 블록 템플릿 CSS의 `|default()` 값이 단일 색상(파랑)만 있음
|
||||||
|
- assembler에서 색상을 전달하는 방식은 잘못됨
|
||||||
|
- 해결: **블록 템플릿 CSS 자체에 열별 색상을 가지고 있어야 함** (`:nth-child(1)`, `:nth-child(2)`, `:nth-child(3)`로 각각 다른 색)
|
||||||
|
- 또는 catalog.yaml에 블록 디자인 속성(색상 팔레트)을 정의하고 assembler가 읽어서 전달
|
||||||
|
- assembler는 콘텐츠만 전달. 디자인은 블록이 가지고 있거나 catalog에서 오는 것.
|
||||||
|
|
||||||
|
**오류 2: 하단 zone에 `### 과정의 혁신`, `### 결과의 변화` 마크다운 헤더가 그대로 노출**
|
||||||
|
- `_parse_topic_to_items()`에서 `### ` 접두사를 heading으로 포함시킴
|
||||||
|
- topic.title이 이미 "과정(Process)의 혁신"인데, structured_text 첫 줄에 또 `### 과정(Process)의 혁신`이 있음
|
||||||
|
- 해결: `_parse_topic_to_items()`에서 `### `으로 시작하는 줄은 무시 (topic.title과 중복)
|
||||||
|
- 또는 structured_text 파싱 시 `### ` 접두사 제거
|
||||||
|
|
||||||
|
**오류 3: 블록 글씨 크기가 하드코딩 (px 고정)**
|
||||||
|
- 블록 템플릿 CSS에 `font-size: 27px`, `font-size: 21px` 등 Figma 원본 크기가 고정
|
||||||
|
- 컨테이너 크기에 따라 font가 조정되어야 하는데 고정이라 overflow 발생
|
||||||
|
- 해결 방향:
|
||||||
|
1. 블록 CSS에서 font-size를 CSS 변수(`var(--block-font-heading)`)로 변환
|
||||||
|
2. assembler가 zone 크기에 따라 CSS 변수 값을 계산하여 전달
|
||||||
|
3. overflow 루프에서 CSS 변수를 줄여가며 재측정
|
||||||
|
- 이것이 Y-5(사이즈 조정 루프)의 핵심
|
||||||
|
|
||||||
|
### 미해결 프로세스
|
||||||
|
|
||||||
|
**overflow 재배분 루프가 실질적으로 안 동작**
|
||||||
|
- Selenium이 overflow +190px를 감지했지만, 재배분할 surplus zone이 없음 (top도 꽉 참)
|
||||||
|
- 재배분만으로는 해결 안 됨 → font/padding 조정이 필요
|
||||||
|
- 현재 파이프라인에 font 조정 로직 없음
|
||||||
|
- Y-5에서 구현 필요:
|
||||||
|
1. overflow 감지 → font-size 1~2px 줄이기 → 재렌더링 → 재측정
|
||||||
|
2. 최대 3회 루프
|
||||||
|
3. 그래도 안 되면 텍스트 압축 (AI 1회)
|
||||||
|
|
||||||
|
**Sonnet redesign 경로 (Y-6) 미구현**
|
||||||
|
- tag 매칭 실패 시 유사 블록 기반 Sonnet redesign → templates/blocks/redesign/에 저장
|
||||||
|
- 현재는 tag 매칭 실패 시 기존 relation_type 방식으로 fallback
|
||||||
|
|
||||||
|
**콘텐츠 소스 전환 (Y-10)** → ✅ 완료 (2026-04-15)
|
||||||
|
- normalized.sections를 단일 소스로 확정
|
||||||
|
- section_parser: raw MDX 파싱 → normalized.sections 기반으로 변경
|
||||||
|
- assembler: _find_section_content() → normalized.sections에서 가져옴
|
||||||
|
- D1:/D2: 포맷 파싱 지원 추가
|
||||||
|
- Kei structured_text 의존 제거
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-04-15 진행 결과
|
||||||
|
|
||||||
|
### 구조 안정화 1차 완료
|
||||||
|
|
||||||
|
| 항목 | 상태 | 비고 |
|
||||||
|
|------|------|------|
|
||||||
|
| process wiring | ✅ 복구됨 | normalized.sections → section shape → block assignment → slide-base → final html |
|
||||||
|
| overflow control | ✅ 통과 | Selenium zone별 측정 동작, overflow 0 (최신 run) |
|
||||||
|
| single source of truth | ✅ 확정 | normalized.sections (Stage 0). raw MDX 직접 사용 안 함. |
|
||||||
|
| layout_template 결정 | ✅ 코드 | sidebar 유무로 A/B 자동 결정 (Kei 의존 아님) |
|
||||||
|
| 영역 확정 | ✅ 코드 | sub_titles 기반 블록 매칭 → 영역 확정 (Kei zone 판단 제거) |
|
||||||
|
| validator 정리 | ✅ | page_structure 검증을 section_parser 후로 이동. Type B purpose 모순 = 경고만. |
|
||||||
|
| **semantic block matching** | ❌ 미해결 | prerequisites-3col 대신 category-strip-table 선택됨 |
|
||||||
|
| **slot filling** | ❌ 미해결 | 블록 slot에 텍스트가 비어있음 (껍데기만) |
|
||||||
|
| quality gate | ⚠ 부분 | vision 404. Selenium만 동작. |
|
||||||
|
|
||||||
|
### 핵심 미해결: 블록 매칭 정확도 + slot 채움
|
||||||
|
|
||||||
|
**1. 블록 매칭이 엉뚱한 블록을 선택**
|
||||||
|
- top: prerequisites-3col(원하는 것) → category-strip-table(선택됨)
|
||||||
|
- bottom: compare-detail-gradient(원하는 것) → dark-bullet-list(선택됨)
|
||||||
|
- 원인: tag 매칭 점수에서 category-strip-table이 더 높은 점수를 받음
|
||||||
|
- 해결: tag 매칭 점수 로직 보정 필요
|
||||||
|
- prerequisites-3col의 content_example에 "기술/사람/자연"이 정확히 매칭되면 최우선
|
||||||
|
- compare-detail-gradient의 content_example에 "과정/결과"가 매칭되면 최우선
|
||||||
|
|
||||||
|
**2. slot 채움이 비어있음**
|
||||||
|
- category-strip-table 블록의 slot에 기술/사람/자연 데이터가 안 들어감
|
||||||
|
- 원인: _build_slot_data()가 sub_title별로 normalized.sections에서 content를 찾는데,
|
||||||
|
sub_title "기술(디지털)"의 content가 대목차 "DX 시행을 위한 필수 요건"의 합친 content에 있어서
|
||||||
|
개별 sub_title별로 분리 안 됨
|
||||||
|
- 해결: normalized.sections에서 sub_title별 개별 content를 직접 가져와야 함
|
||||||
|
(major_sections의 합친 content가 아니라, 개별 level=2 section)
|
||||||
|
|
||||||
|
**3. section shape와 topic 매핑 불일치**
|
||||||
|
- bottom의 topic_ids=[4]만 있음 (5가 빠짐)
|
||||||
|
- 원인: Kei가 꼭지를 매번 다르게 만들어서 매핑이 흔들림
|
||||||
|
- 해결: topic 매핑도 sub_titles 기반으로 안정화 필요
|
||||||
|
|
||||||
|
### 추가 발견 (2026-04-15 후반)
|
||||||
|
|
||||||
|
**블록 매칭은 해결됨 (sub_titles 기반 + min_height 감점 제거):**
|
||||||
|
- top: prerequisites-3col ✅ (sub_titles 3개 매칭)
|
||||||
|
- bottom: compare-detail-gradient ✅ (sub_titles 2개 매칭)
|
||||||
|
|
||||||
|
**하지만 결과물 품질이 안 맞음:**
|
||||||
|
1. 결론 텍스트 3번 중복 (블록 slot + footer)
|
||||||
|
2. bottom 좌측 텍스트 안 보임 (D1 only → desc 빈 배열)
|
||||||
|
3. 텍스트 중복 렌더링
|
||||||
|
4. zone height px 고정 (참고는 % 기반)
|
||||||
|
5. font_scale 방식으로 글자 과도 축소 (참고는 글자 크기 고정)
|
||||||
|
6. 블록이 zone height:100%로 안 채워짐
|
||||||
|
7. weight가 content 글자 수 기준 (중요도가 아님)
|
||||||
|
8. bottom 블록으로 cdg가 선택됐지만, 참고 결과물은 pp2 사용
|
||||||
|
|
||||||
|
**근본 원인:**
|
||||||
|
- cdg로도 가능하지만, pp2가 이 콘텐츠 구조(비대칭: 표+불릿 vs 불릿)에 더 적합
|
||||||
|
- pp2는 catalog 미등록 → tag 매칭 불가
|
||||||
|
- 블록 글씨 크기가 Figma 원본(18px) → 슬라이드 적용(12px) 변환 필요
|
||||||
|
- 블록 payload schema가 확정 안 됨 → slot 데이터가 제대로 안 채워짐
|
||||||
|
- fit 루프가 구조/payload 이전에 실행돼서 의미 없음
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase Y-11: 블록 자산 → payload → layout → fit → 검증 (2026-04-15 확정)
|
||||||
|
|
||||||
|
> **원칙:** 구조 → payload → layout → fit 순서. 하드코딩 금지. 전체 프로세스 구조 속에서 정리.
|
||||||
|
> **핵심:** "프로세스가 결과를 만드는" 구조. 결과를 보고 프로세스를 땜질하지 않음.
|
||||||
|
|
||||||
|
### 전체 파이프라인 (Y-11 반영)
|
||||||
|
|
||||||
|
```
|
||||||
|
[1] slide-base.html 로드 (title + footer)
|
||||||
|
[2] Kei: 꼭지 추출만 (영역/zone 안 함)
|
||||||
|
[3] 코드: normalized.sections → 대목차 추출 → 꼭지 매핑
|
||||||
|
[4] 코드: shape 기반 block selection
|
||||||
|
sub_titles 수 + 구조 타입 → catalog tag 매칭 → 블록 확정
|
||||||
|
[5] payload 조립
|
||||||
|
블록별 payload schema에 맞게 normalized data 변환
|
||||||
|
[6] payload contract 검증 ← 새로 추가
|
||||||
|
필수 slot 비어있지 않은지, 결론이 body에 안 섞였는지
|
||||||
|
[7] layout 조립
|
||||||
|
zone % 기반 + block height:100% + 고정 글씨 크기
|
||||||
|
[8] fit 루프
|
||||||
|
overflow → padding/spacing 먼저 → 팝업 분리 → font 축소(최후)
|
||||||
|
[9] 최종 검증
|
||||||
|
block 선택 + 글자 누락 + 결론 위치 + overflow + 밀도
|
||||||
|
```
|
||||||
|
|
||||||
|
### Y-11 태스크 목록
|
||||||
|
|
||||||
|
**[Y-11a] pp2 블록 자산 정리**
|
||||||
|
- BEPs/process-product-2col.html → blocks/redesign/ 이동
|
||||||
|
- slot 구조: left_title, right_title, left_compare(asis/tobe), left_sections[], right_sections[]
|
||||||
|
- 블록 목적, 사용 조건, 미사용 조건 문서화
|
||||||
|
- HTML/CSS의 글씨 크기를 슬라이드 적용값(header:13px, mid:12px, body:11px)으로 확정
|
||||||
|
|
||||||
|
**[Y-11b] pp2 catalog.yaml 등록 + tag**
|
||||||
|
- content_pattern: "2-section-asymmetric-compare-table-and-bullets"
|
||||||
|
- item_count: 2
|
||||||
|
- content_example에 구조 설명 (문서명 하드코딩 금지)
|
||||||
|
- slide_font 필드에 슬라이드 적용 글씨 크기 기록
|
||||||
|
|
||||||
|
**[Y-11c] block selection 규칙 shape 기반 재정의**
|
||||||
|
- sub_titles 3개 + 병렬 → prerequisites-3col
|
||||||
|
- sub_titles 2개 + 비대칭(표+불릿) → pp2
|
||||||
|
- sub_titles 2개 + 대칭 비교 → cdg
|
||||||
|
- sidebar/reference → 해당 전용 블록
|
||||||
|
- pipeline Phase Y + Stage 1.7 block_reference 일관 적용
|
||||||
|
|
||||||
|
**[Y-11d] pp2 payload schema + 파이프라인 연결**
|
||||||
|
- payload 구조 확정 (left_title, right_title, left_compare, left/right_sections)
|
||||||
|
- _build_slot_data()에서 블록별 payload 생성
|
||||||
|
- D1/D2 → payload schema 변환 규칙 (D1 only 표 복원 포함)
|
||||||
|
|
||||||
|
**[Y-11e] 본문 데이터 정리 규칙**
|
||||||
|
- 결론/핵심요약 → footer 전용, 블록 payload에 절대 안 섞기
|
||||||
|
- 표 → left_compare.left_items/right_items
|
||||||
|
- 불릿 → sections[].bullets
|
||||||
|
- D1 only 평탄화 → 표 구조 복원
|
||||||
|
|
||||||
|
**[Y-11f] zone wrapper 정리**
|
||||||
|
- zone height: % 기반 (px 아님)
|
||||||
|
- block wrapper: height:100%
|
||||||
|
- zone 제목: 13px, margin-bottom:8px
|
||||||
|
- zone 간 여백: margin-bottom:1%
|
||||||
|
- 글씨 크기: catalog slide_font 값 (font_scale 아님)
|
||||||
|
|
||||||
|
**[Y-11g] 블록 내부 typography 규칙**
|
||||||
|
- prerequisites-3col: heading 12px, desc 11px, vlabel 14px
|
||||||
|
- pp2: header 13px, mid_title 12px, body 11px
|
||||||
|
- bullet 기호, padding-left, text-indent, line-height
|
||||||
|
- catalog.yaml slide_font에 기록, assembler가 읽어서 적용
|
||||||
|
|
||||||
|
**[Y-11h] payload contract 검증 (fit 전 게이트)** ✅ 구현
|
||||||
|
- 필수 slot 비어있지 않은지
|
||||||
|
- conclusion이 body payload에 안 들어갔는지
|
||||||
|
- 같은 데이터가 compare와 section에 중복 주입 안 되는지 ← 추가 필요
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Y-11 1차 검증 결과 (2026-04-15, run 20260415_091309)
|
||||||
|
|
||||||
|
**block selection: ✅ 통과**
|
||||||
|
- top: prerequisites-3col (tag_match)
|
||||||
|
- bottom: process-product-2col (tag_match)
|
||||||
|
- overflow: top 0, bottom 0
|
||||||
|
- font_scale: 1.0 (축소 안 함)
|
||||||
|
|
||||||
|
**payload/contract/layout: ❌ 미완**
|
||||||
|
|
||||||
|
아래 항목들이 남아있음 → Y-12로 정리:
|
||||||
|
|
||||||
|
### Phase Y-12: payload 정제 → contract → layout → asset → validation
|
||||||
|
|
||||||
|
> block selection은 통과. 이제 "선택된 블록에 정확한 데이터를 정확한 형태로 넣는" 단계.
|
||||||
|
|
||||||
|
**[Y-12a] payload normalization (정제)**
|
||||||
|
- `**` `****` 마크다운 잔여 토큰 제거 (final 출력 전 cleanup gate)
|
||||||
|
- top desc: `/`로 이어진 plain text → `<div class="bul">• ...</div>` 불릿 구조
|
||||||
|
- 표 잔여 토큰 (D1: As-is, D1: 구분 등) 정리
|
||||||
|
- `[핵심요약: ...]` stray note 제거
|
||||||
|
|
||||||
|
**[Y-12b] block contract assembly (중복 제거)**
|
||||||
|
- left_compare에 들어간 데이터가 left_sections에 다시 들어가지 않게
|
||||||
|
(현재: Analogue 기반 업무의 Digital화가 compare 제목 + section mid-title에 중복)
|
||||||
|
- compare → left_sections 분리 규칙: 표 항목은 compare에만, 나머지는 sections에만
|
||||||
|
- pp2 좌우 section packing 규칙 고정
|
||||||
|
|
||||||
|
**[Y-12c] layout/style contract** — 공통 레이아웃 계약 + 블록별 내부 contract
|
||||||
|
|
||||||
|
공통 들여쓰기 계층 (모든 슬라이드에 적용):
|
||||||
|
```
|
||||||
|
대제목 (slide-base) ← left: 52px (고정)
|
||||||
|
중제목 (zone 제목) ← 대제목과 같은 시작선 (padding-left: 12px)
|
||||||
|
블록 wrapper ← 중제목보다 안쪽 (padding: 0 12px 0 24px)
|
||||||
|
소제목 ← 블록 내부 기준선
|
||||||
|
불릿 ← 소제목보다 안쪽
|
||||||
|
두번째줄 ← 첫줄 문장 시작선 정렬 (hanging indent: padding-left:14px; text-indent:-14px)
|
||||||
|
```
|
||||||
|
|
||||||
|
블록별 내부 contract:
|
||||||
|
- p3c: bar 56px, vlabel-area 56px, section left:60px
|
||||||
|
- pp2: display:flex 좌/우 병렬, 소제목 행 정렬, body padding 6px 16px
|
||||||
|
- 불릿: `.bul`, `.pp2-body-text`, `.cdg-bullet` 공통 hanging indent
|
||||||
|
|
||||||
|
**[Y-12d] asset packaging**
|
||||||
|
- 배경 텍스처 (svg/bg_slide_texture.png) → base64 내장 또는 data URI
|
||||||
|
- 화살표 이미지 (arrow) → base64
|
||||||
|
- final.html이 단독으로 열어도 asset 깨지지 않게
|
||||||
|
|
||||||
|
**[Y-12e] final validation**
|
||||||
|
- 글자 누락 없음
|
||||||
|
- markdown residue (`**`, `****`) 없음
|
||||||
|
- 본문 중복 없음 (compare/section 중복)
|
||||||
|
- 결론이 footer에만 있음
|
||||||
|
- 좌우 정렬 이상 없음
|
||||||
|
- asset 깨짐 없음
|
||||||
|
- overflow 없음
|
||||||
|
|
||||||
|
**[Y-12f] 파이프라인 실행 + 참고 비교**
|
||||||
|
- MDX 03 실행 → ✅ 완료 (run 20260415_105516, 텍스트 누락 없음)
|
||||||
|
- mdx03_final/final.html과 비교
|
||||||
|
- 하드코딩 없이 프로세스로 도달한 결과인지 확인
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase Y-13: Group Schema 계층 (fit 블록 없을 때의 프로세스)
|
||||||
|
|
||||||
|
> **근거:** MDX 02를 돌렸을 때, fit한 블록이 없어서 venn-diagram 같은 엉뚱한 fallback으로 빠짐.
|
||||||
|
> fit 블록이 있으면 Y-11~12 프로세스로 충분. 없을 때 "바로 fallback 아무거나"가 아니라
|
||||||
|
> **중목차 → 소목차 관계 판단 → group schema → 블록 선택/조합** 경로가 필요.
|
||||||
|
|
||||||
|
### 핵심 프로세스
|
||||||
|
|
||||||
|
```
|
||||||
|
[1] section group 추출
|
||||||
|
## 중목차 기준으로 하나의 section group으로 묶기
|
||||||
|
(이미 extract_major_sections()가 sub_titles를 제공)
|
||||||
|
|
||||||
|
[2] group relation classifier
|
||||||
|
같은 중목차 안의 소목차들의 관계 판단:
|
||||||
|
- 병렬 목표 (안전/생산성/소통) → parallel_3
|
||||||
|
- 비교 (과정혁신/결과변화) → compare_2
|
||||||
|
- 순서/프로세스 → process_list
|
||||||
|
- 독립 카드 → independent_cards
|
||||||
|
- 보조 설명 → summary_with_visual
|
||||||
|
Kei가 판단 지원 (코드만으로 어려움)
|
||||||
|
|
||||||
|
[3] group schema enum
|
||||||
|
relation → schema 변환:
|
||||||
|
- parallel_3 → 3열 카드/표/요약
|
||||||
|
- compare_2 → 2열 비교
|
||||||
|
- process_list → 단계/변화 목록
|
||||||
|
- summary_with_visual → 텍스트+이미지
|
||||||
|
|
||||||
|
[4] schema → block matcher
|
||||||
|
schema에 맞는 블록을 catalog에서 찾기
|
||||||
|
├── 정확히 맞는 블록 있음 → 사용
|
||||||
|
├── 유사 블록 → Sonnet redesign → blocks/redesign/ 저장
|
||||||
|
└── 없음 → composition (메인 블록 + 보조 블록 조합)
|
||||||
|
fallback 아무거나 금지
|
||||||
|
|
||||||
|
[5] payload 조립 (group schema 기준)
|
||||||
|
group schema → block payload 변환
|
||||||
|
이미지 포함 여부, bullet 정리, 표 축약, popup 분기
|
||||||
|
|
||||||
|
[6] layout / fit
|
||||||
|
zone 비율, padding, indent, overflow, popup fallback
|
||||||
|
```
|
||||||
|
|
||||||
|
### Y-13 진행 결과 (2026-04-15)
|
||||||
|
|
||||||
|
**Y-13a~d: 구현 완료**
|
||||||
|
|
||||||
|
구현된 것:
|
||||||
|
- `classify_group_relations()`: D1: 개수로 병렬 항목 감지 + 키워드 기반 schema 분류
|
||||||
|
- schema 세분화: `compare_asymmetric_2col` (표+비대칭), `process_plus_visual` (불릿+시각) 추가
|
||||||
|
- 기존 `process_list` 유지 (삭제 안 함, 점진적 추가)
|
||||||
|
- `GROUP_SCHEMA_BLOCK_MAP`: 새 schema → 블록 후보 매핑 추가
|
||||||
|
- pipeline + block_reference에서 tag 실패 시 schema 후보로 선택
|
||||||
|
|
||||||
|
**MDX 03 회귀 검증 ✅:**
|
||||||
|
```
|
||||||
|
top: parallel_3 → prerequisites-3col ✅ (이전과 동일)
|
||||||
|
bottom: compare_asymmetric_2col → process-product-2col ✅ (pp2 유지)
|
||||||
|
```
|
||||||
|
|
||||||
|
**MDX 02 현재 상태:**
|
||||||
|
```
|
||||||
|
top: parallel_3_with_image → prerequisites-3col (이미지 배치 미해결)
|
||||||
|
bottom: compare_2 → compare-detail-gradient (<DxEffect> 감지 안 됨 → process_plus_visual 미적용)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 미해결 + 방향
|
||||||
|
|
||||||
|
**1. tag_match와 schema_match 동등 비교 (향후)**
|
||||||
|
- 현재: tag 실패 시에만 schema fallback
|
||||||
|
- 향후: 둘 다 점수화해서 동등 비교
|
||||||
|
- schema_match를 block selection의 1급 기준으로 승격 예정
|
||||||
|
|
||||||
|
**2. GROUP_SCHEMA_BLOCK_MAP → 선언형 이동 (향후)**
|
||||||
|
- 현재: section_parser.py에 dict 하드코딩
|
||||||
|
- 향후: catalog.yaml 또는 별도 schema 파일로 선언형 관리
|
||||||
|
|
||||||
|
**3. Kei는 보조 힌트**
|
||||||
|
- 구조적 근거(D1: 수, sub_titles 수, 키워드) = 1순위
|
||||||
|
- Kei = 보조 의미 힌트 (하드 의존 안 함)
|
||||||
|
|
||||||
|
**4. composition 경로 (향후)**
|
||||||
|
- 단일 블록으로 안 되는 경우에만
|
||||||
|
- 지금은 먼저 단일 블록 경로를 정교화
|
||||||
|
- composition은 검증 결과를 보고 필요한 경우에만 추가
|
||||||
|
|
||||||
|
**5. MDX 02 bottom 분류 정교화** → ✅ 해결
|
||||||
|
- `<DxEffect />`가 normalized에서 제거됨 → sub_titles 키워드("기대효과")로 분기
|
||||||
|
- content + sub_text 합쳐서 키워드 검색하도록 수정
|
||||||
|
- MDX 02 bottom: `process_plus_visual` → checklist-dark 후보
|
||||||
|
|
||||||
|
**6. section_parser 책임 분리 (향후)**
|
||||||
|
- 현재: group 추출 + relation 분류 + schema enum + block 후보 다 있음
|
||||||
|
- 향후: group_schema.py 별도 모듈로 분리 예정
|
||||||
|
|
||||||
|
**7. 메인/popup 2단 표현 계약 (파이프라인 공통)**
|
||||||
|
- 콘텐츠가 zone에 다 안 들어갈 때의 공통 규칙
|
||||||
|
- MDX의 `<DxEffect />` 같은 시각 컴포넌트, 큰 표, 과다 불릿 대응
|
||||||
|
- 구조:
|
||||||
|
```
|
||||||
|
메인 HTML (zone 안):
|
||||||
|
- 존 크기에 맞는 요약형 (2~3행 요약, 핵심 포인트)
|
||||||
|
- "자세히보기" 링크/버튼
|
||||||
|
popup HTML (별도 파일):
|
||||||
|
- 전체 표, 전체 bullet, 컴포넌트 원형 구조
|
||||||
|
- 예: detail_dx_effect.html (run 폴더 안)
|
||||||
|
```
|
||||||
|
- popup 분기 조건:
|
||||||
|
- 표가 크다 (행 5개 이상)
|
||||||
|
- 시각 컴포넌트가 있다 (`<DxEffect />` 등)
|
||||||
|
- 존 높이에 안 맞는다 (overflow)
|
||||||
|
- 본문에 넣으면 가독성이 깨진다
|
||||||
|
- 검증 규칙:
|
||||||
|
- popup으로 보낸 경우 본문엔 최소 요약이 남아 있어야 함 (빈칸 금지)
|
||||||
|
- 링크 대상 파일이 실제 생성돼 있어야 함
|
||||||
|
- Astro 컴포넌트 연결:
|
||||||
|
- `<DxEffect />` → `samples/src/components/dx.astro`에 연결
|
||||||
|
- 파이프라인은 Astro를 실행하지 않음
|
||||||
|
- dx.astro를 읽어서 HTML로 변환 → popup 파일로 생성
|
||||||
|
- 메인에는 요약형 카드 + 자세히보기 링크
|
||||||
|
- fit 루프와의 관계:
|
||||||
|
- overflow 발생 → font 축소(최후) 전에 popup 분리를 먼저 시도
|
||||||
|
- 순서: padding 조정 → popup 분리 → font 1단계 축소
|
||||||
|
|
||||||
|
**8. tag_match / schema_match 동등 비교 (향후)**
|
||||||
|
- 현재: tag 실패 시에만 schema fallback
|
||||||
|
- 향후: 둘 다 점수화해서 동등 비교
|
||||||
|
- schema_match를 block selection의 1급 기준으로 승격
|
||||||
|
|
||||||
|
### 의존 관계
|
||||||
|
```
|
||||||
|
Y-12 (payload/layout) → Y-13 (group schema) → Y-14 (popup 2단 표현)
|
||||||
|
MDX 03: Y-12로 충분 ✅ (fit 블록 있음, 회귀 검증 통과)
|
||||||
|
MDX 02: Y-13 분류 완료 ✅, 블록 선택 후 popup 경로 필요 (Y-14)
|
||||||
|
MDX 01: Type A — 별도 작업
|
||||||
|
```
|
||||||
|
Before Width: | Height: | Size: 1.1 MiB |
@@ -1,806 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ko">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>건설정보모델링(BIM)</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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Design Agent — 기본 슬라이드 스타일 */
|
|
||||||
|
|
||||||
@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css');
|
|
||||||
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 슬라이드 컨테이너: 16:9 고정 비율 */
|
|
||||||
.slide {
|
|
||||||
width: 1280px;
|
|
||||||
height: 720px;
|
|
||||||
aspect-ratio: 16 / 9;
|
|
||||||
overflow: hidden;
|
|
||||||
background: var(--color-bg);
|
|
||||||
font-family: 'Pretendard Variable', 'Pretendard', 'Noto Sans KR', sans-serif;
|
|
||||||
color: var(--color-text);
|
|
||||||
font-size: var(--font-body);
|
|
||||||
line-height: var(--line-height-ko);
|
|
||||||
word-break: keep-all;
|
|
||||||
padding: var(--spacing-page);
|
|
||||||
display: grid;
|
|
||||||
gap: var(--spacing-block);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 슬라이드 제목 */
|
|
||||||
.slide-title {
|
|
||||||
font-size: var(--font-title);
|
|
||||||
font-weight: var(--weight-black);
|
|
||||||
color: var(--color-primary);
|
|
||||||
border-bottom: var(--accent-border) solid var(--color-accent);
|
|
||||||
padding-bottom: var(--spacing-small);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 섹션 제목 */
|
|
||||||
.section-title {
|
|
||||||
font-size: var(--font-subtitle);
|
|
||||||
font-weight: var(--weight-bold);
|
|
||||||
color: var(--color-primary);
|
|
||||||
margin-bottom: var(--spacing-small);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 본문 */
|
|
||||||
.body-text {
|
|
||||||
font-size: var(--font-body);
|
|
||||||
color: var(--color-text);
|
|
||||||
line-height: var(--line-height-ko);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 캡션/출처 */
|
|
||||||
.caption {
|
|
||||||
font-size: var(--font-caption);
|
|
||||||
color: var(--color-text-light);
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 강조 텍스트 */
|
|
||||||
.highlight {
|
|
||||||
color: var(--color-accent);
|
|
||||||
font-weight: var(--weight-bold);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 경고/문제 강조 */
|
|
||||||
.danger {
|
|
||||||
color: var(--color-danger);
|
|
||||||
font-weight: var(--weight-bold);
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
<style>
|
|
||||||
|
|
||||||
.slide-1 {
|
|
||||||
grid-template-areas: 'header' 'topic1' 'topic2' 'images' 'mountain' 'compare' 'table' 'circle' 'cards';
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
grid-template-rows: 500px auto auto 354px auto auto auto auto auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-1 .area-header {
|
|
||||||
grid-area: header;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-1 .area-topic1 {
|
|
||||||
grid-area: topic1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-1 .area-topic2 {
|
|
||||||
grid-area: topic2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-1 .area-images {
|
|
||||||
grid-area: images;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-1 .area-compare {
|
|
||||||
grid-area: compare;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-1 .area-table {
|
|
||||||
grid-area: table;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-1 .area-circle {
|
|
||||||
grid-area: circle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-1 .area-cards {
|
|
||||||
grid-area: cards;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* 다중 페이지: 페이지 간 간격 */
|
|
||||||
.slide + .slide {
|
|
||||||
margin-top: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 인쇄 시 페이지 분리 */
|
|
||||||
@media print {
|
|
||||||
.slide {
|
|
||||||
page-break-after: always;
|
|
||||||
}
|
|
||||||
.slide + .slide {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="slide slide-1">
|
|
||||||
|
|
||||||
<div class="slide-title" style="grid-area: header;">건설정보모델링(BIM)</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="area-header">
|
|
||||||
<!-- 섹션 타이틀: 배경 헤더 위 영문+한글 타이틀 오버레이 -->
|
|
||||||
<!--
|
|
||||||
📋 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>
|
|
||||||
|
|
||||||
<div class="area-topic1">
|
|
||||||
<!-- 꼭지 제목+설명: 좌측 질문/소제목 + 우측 설명 -->
|
|
||||||
<!--
|
|
||||||
📋 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>
|
|
||||||
|
|
||||||
<div class="area-topic2">
|
|
||||||
<!-- 꼭지 제목+설명: 좌측 질문/소제목 + 우측 설명 -->
|
|
||||||
<!--
|
|
||||||
📋 topic-header
|
|
||||||
─────────────────
|
|
||||||
용도: 각 꼭지의 시작부, 좌측에 파란 굵은 제목 + 우측에 본문 설명
|
|
||||||
슬롯: title (필수), description (필수)
|
|
||||||
비율: 좌 240px : 우 나머지
|
|
||||||
Figma 원본: sub_제목,내용 (742x68~78)
|
|
||||||
-->
|
|
||||||
<div class="block-topic-header">
|
|
||||||
<div class="th-title">건설산업에서의 BIM</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>
|
|
||||||
|
|
||||||
<div class="area-images">
|
|
||||||
<!-- 이미지 행: 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>
|
|
||||||
|
|
||||||
<div class="area-compare">
|
|
||||||
<!-- 비교 박스: 이미지 배경 + 텍스트 오버레이 + VS 라벨 -->
|
|
||||||
<!--
|
|
||||||
📋 compare-box
|
|
||||||
─────────────────
|
|
||||||
용도: 2개 개념을 시각적으로 대비 (배경 이미지 + 텍스트 오버레이)
|
|
||||||
슬롯: left_label, left_sub, right_label, right_sub, left_bg (선택), right_bg (선택)
|
|
||||||
Figma 원본: 2-1_02 > Group 1171281597/1598
|
|
||||||
-->
|
|
||||||
<div class="block-compare-box">
|
|
||||||
<div class="cb-item">
|
|
||||||
|
|
||||||
<div class="cb-bg cb-bg-default cb-bg-left"></div>
|
|
||||||
|
|
||||||
<div class="cb-overlay">
|
|
||||||
<div class="cb-label">D/X</div>
|
|
||||||
<div class="cb-sub">디지털 기술을 활용한
|
|
||||||
협업 프로세스</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="cb-vs">VS</div>
|
|
||||||
<div class="cb-item">
|
|
||||||
|
|
||||||
<div class="cb-bg cb-bg-default cb-bg-right"></div>
|
|
||||||
|
|
||||||
<div class="cb-overlay">
|
|
||||||
<div class="cb-label">BIM</div>
|
|
||||||
<div class="cb-sub">시설물의 전 생애주기 동안
|
|
||||||
정보의 생성 및 관리</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.block-compare-box {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 10px 0;
|
|
||||||
}
|
|
||||||
.cb-item {
|
|
||||||
position: relative;
|
|
||||||
width: 327px;
|
|
||||||
height: 116px;
|
|
||||||
border-radius: 10px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.cb-bg {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
.cb-bg-default {
|
|
||||||
background: linear-gradient(135deg, #006aff 0%, #004cbe 100%);
|
|
||||||
}
|
|
||||||
.cb-overlay {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
z-index: 2;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #ffffff;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.cb-label {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: var(--weight-bold, 700);
|
|
||||||
}
|
|
||||||
.cb-sub {
|
|
||||||
font-size: 12px;
|
|
||||||
opacity: 0.85;
|
|
||||||
margin-top: 4px;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
.cb-vs {
|
|
||||||
background: #ffffff;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 8px 18px;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: var(--weight-bold, 700);
|
|
||||||
color: var(--color-accent-bright, #006eff);
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="area-table">
|
|
||||||
<!-- 비교 테이블 블록: 다항목 비교 -->
|
|
||||||
<div class="block-table">
|
|
||||||
<table class="comparison-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
|
|
||||||
<th class="table-row-header">BIM</th>
|
|
||||||
|
|
||||||
<th>VS</th>
|
|
||||||
|
|
||||||
<th>D/X</th>
|
|
||||||
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
|
|
||||||
<td class="table-row-header">Only 3D</td>
|
|
||||||
|
|
||||||
<td>BIM·D/X</td>
|
|
||||||
|
|
||||||
<td>BIM ≪ D/X (ENG.+Management 포함)</td>
|
|
||||||
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
|
|
||||||
<td class="table-row-header">모델 제작용 상용 S/W</td>
|
|
||||||
|
|
||||||
<td>S/W</td>
|
|
||||||
|
|
||||||
<td>제작 및 운영(상용+전용 40~80개)</td>
|
|
||||||
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
|
|
||||||
<td class="table-row-header">기존 2D 설계방식 유지</td>
|
|
||||||
|
|
||||||
<td>프로세스</td>
|
|
||||||
|
|
||||||
<td>근본적 문제의식을 통한 개선</td>
|
|
||||||
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
|
|
||||||
<td class="table-row-header">3D 모델 중심, 기존 성과품 유지</td>
|
|
||||||
|
|
||||||
<td>성과물</td>
|
|
||||||
|
|
||||||
<td>공학 정보 및 콘텐츠 연계에 집중</td>
|
|
||||||
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
|
|
||||||
<td class="table-row-header">3D 모델에 의한 일반적 이해 향상</td>
|
|
||||||
|
|
||||||
<td>활용</td>
|
|
||||||
|
|
||||||
<td>설계/시공의 혁신(개념의 재정립)</td>
|
|
||||||
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
|
|
||||||
<td class="table-row-header">(설계/시공/운영) 분야별 단절</td>
|
|
||||||
|
|
||||||
<td>확장성</td>
|
|
||||||
|
|
||||||
<td>전 생애주기 활용 시스템</td>
|
|
||||||
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.block-table {
|
|
||||||
overflow: auto;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
.comparison-table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
font-size: var(--font-caption);
|
|
||||||
line-height: var(--line-height-ko);
|
|
||||||
}
|
|
||||||
.comparison-table th {
|
|
||||||
background: var(--color-primary);
|
|
||||||
color: white;
|
|
||||||
font-weight: var(--weight-bold);
|
|
||||||
padding: var(--spacing-small) var(--spacing-inner);
|
|
||||||
text-align: left;
|
|
||||||
font-size: var(--font-caption);
|
|
||||||
}
|
|
||||||
.comparison-table td {
|
|
||||||
padding: var(--spacing-small) var(--spacing-inner);
|
|
||||||
border-bottom: var(--border-width) solid var(--color-border);
|
|
||||||
font-size: var(--font-caption);
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
.comparison-table tbody tr:nth-child(even) {
|
|
||||||
background: var(--color-bg-subtle);
|
|
||||||
}
|
|
||||||
.table-row-header {
|
|
||||||
font-weight: var(--weight-bold);
|
|
||||||
color: var(--color-primary);
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="area-circle">
|
|
||||||
<!-- 원형 라벨: 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>
|
|
||||||
|
|
||||||
<div class="area-cards">
|
|
||||||
<!-- 이미지 카드: 상단 이미지 + 하단 텍스트 (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>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,401 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ko">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>2-1_02 건설정보모델링(BIM) — 레이어 분리 v2</title>
|
|
||||||
<style>
|
|
||||||
@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css');
|
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
||||||
body {
|
|
||||||
font-family: 'Pretendard Variable', 'Noto Sans KR', sans-serif;
|
|
||||||
background: #e8ecf0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 20px;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
.frame { width: 920px; background: #fff; }
|
|
||||||
|
|
||||||
/* ═══ 1. 배경 헤더 + 타이틀 오버레이 ═══ */
|
|
||||||
.header {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 500px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.header img { width: 100%; height: 100%; object-fit: cover; }
|
|
||||||
.header-text {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 40px;
|
|
||||||
left: 89px;
|
|
||||||
z-index: 5;
|
|
||||||
}
|
|
||||||
.header-text .en { font-size: 15px; font-weight: 400; color: #fff; opacity: 0.85; }
|
|
||||||
.header-text .ko { font-size: 35px; font-weight: 700; color: #fff; margin-top: 4px; }
|
|
||||||
.breadcrumb {
|
|
||||||
position: absolute;
|
|
||||||
top: 18px; left: 89px;
|
|
||||||
font-size: 13px; color: rgba(255,255,255,0.7);
|
|
||||||
z-index: 5;
|
|
||||||
}
|
|
||||||
.breadcrumb span { margin: 0 6px; color: rgba(255,255,255,0.5); }
|
|
||||||
.close-x {
|
|
||||||
position: absolute;
|
|
||||||
top: 18px; right: 20px;
|
|
||||||
font-size: 22px; color: #fff;
|
|
||||||
cursor: pointer; z-index: 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══ 2. 꼭지 제목+설명 ═══ */
|
|
||||||
.topic { padding: 28px 89px 12px; }
|
|
||||||
.topic-row { display: flex; gap: 20px; margin-bottom: 18px; }
|
|
||||||
.topic-title {
|
|
||||||
width: 240px; flex-shrink: 0;
|
|
||||||
font-size: 24px; font-weight: 700; color: #004cbe;
|
|
||||||
line-height: 1.4; word-break: keep-all;
|
|
||||||
}
|
|
||||||
.topic-desc {
|
|
||||||
flex: 1;
|
|
||||||
font-size: 16px; color: #000; line-height: 1.7; word-break: keep-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══ 3. 이미지 그리드 2열 ═══ */
|
|
||||||
.img-grid { display: flex; gap: 0; padding: 0; }
|
|
||||||
.img-grid img { flex: 1; height: 354px; object-fit: cover; }
|
|
||||||
|
|
||||||
/* ═══ 4. 산맥 시각화 ═══ */
|
|
||||||
.mountain img { width: 100%; }
|
|
||||||
|
|
||||||
/* ═══ 5. 비교 박스 (이미지 배경 + HTML 텍스트) ═══ */
|
|
||||||
.compare-boxes {
|
|
||||||
padding: 25px 89px;
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
.cbox {
|
|
||||||
position: relative;
|
|
||||||
width: 327px; height: 116px;
|
|
||||||
border-radius: 10px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.cbox img { width: 100%; height: 100%; object-fit: cover; }
|
|
||||||
.cbox-text {
|
|
||||||
position: absolute; inset: 0;
|
|
||||||
display: flex; flex-direction: column;
|
|
||||||
align-items: center; justify-content: center;
|
|
||||||
color: #fff; text-align: center;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
.cbox-text .label { font-size: 20px; font-weight: 700; }
|
|
||||||
.cbox-text .sub { font-size: 12px; opacity: 0.85; margin-top: 4px; }
|
|
||||||
.vs-badge {
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 8px 18px;
|
|
||||||
font-size: 20px; font-weight: 700; color: #006eff;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══ 6. 비교 테이블 (HTML) ═══ */
|
|
||||||
.compare-table { padding: 10px 89px 20px; }
|
|
||||||
.compare-table table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
.compare-table thead th {
|
|
||||||
padding: 10px 12px;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
.compare-table thead th:first-child { color: #6bcdff; }
|
|
||||||
.compare-table thead th:nth-child(2) {
|
|
||||||
background: linear-gradient(135deg, #006eff, #00aaff);
|
|
||||||
color: #fff; border-radius: 20px;
|
|
||||||
text-align: center; width: 100px;
|
|
||||||
}
|
|
||||||
.compare-table thead th:last-child { color: #006eff; text-align: right; }
|
|
||||||
.compare-table tbody td {
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-bottom: 1px solid #f0f0f0;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
.compare-table tbody td:first-child { color: #333; }
|
|
||||||
.compare-table tbody td:nth-child(2) {
|
|
||||||
text-align: center;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #333;
|
|
||||||
background: #f8f9fb;
|
|
||||||
}
|
|
||||||
.compare-table tbody td:last-child { text-align: right; color: #333; }
|
|
||||||
.compare-table tbody tr:nth-child(even) td { background: #fafbfc; }
|
|
||||||
.compare-table tbody tr:nth-child(even) td:nth-child(2) { background: #f0f2f5; }
|
|
||||||
|
|
||||||
/* ═══ 7. 원형 라벨 (CSS + HTML) ═══ */
|
|
||||||
.circle-label-section {
|
|
||||||
display: flex; justify-content: center;
|
|
||||||
padding: 25px 0 15px;
|
|
||||||
}
|
|
||||||
.circle-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;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.circle-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: #fff;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.circle-inner .main { font-size: 20px; font-weight: 700; line-height: 1.4; }
|
|
||||||
|
|
||||||
/* ═══ 8. 카드 3열 (HTML + 개별 이미지) ═══ */
|
|
||||||
.cards-section { padding: 10px 89px 40px; }
|
|
||||||
.cards-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
.card {
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.card-img {
|
|
||||||
width: 100%;
|
|
||||||
height: 160px;
|
|
||||||
object-fit: contain;
|
|
||||||
background: #f8f9fb;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
.card-body { padding: 16px; }
|
|
||||||
.card-stage {
|
|
||||||
font-size: 14px; font-weight: 700;
|
|
||||||
text-decoration: underline;
|
|
||||||
text-underline-offset: 3px;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
.card-stage-en {
|
|
||||||
font-size: 12px; font-weight: 400; color: #666;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.card-divider {
|
|
||||||
width: 100%; height: 1px;
|
|
||||||
background: #000; margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.card-body ul {
|
|
||||||
list-style: disc;
|
|
||||||
padding-left: 18px;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.7;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
.card-body li { margin-bottom: 3px; }
|
|
||||||
.card-design .card-stage { color: #00aaff; }
|
|
||||||
.card-construction .card-stage { color: #006aff; }
|
|
||||||
.card-maintenance .card-stage { color: #004cbe; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="frame">
|
|
||||||
|
|
||||||
<!-- 1. 배경 헤더 + 타이틀 -->
|
|
||||||
<div class="header">
|
|
||||||
<img src="figma-assets/bg_header.png" alt="">
|
|
||||||
<div class="breadcrumb">건설산업에서의 디지털전환 <span>›</span> Building Information Modeling</div>
|
|
||||||
<div class="close-x">✕</div>
|
|
||||||
<div class="header-text">
|
|
||||||
<div class="en">Building Information Modeling</div>
|
|
||||||
<div class="ko">건설정보모델링(BIM)</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 2. 꼭지 -->
|
|
||||||
<div class="topic">
|
|
||||||
<div class="topic-row">
|
|
||||||
<div class="topic-title">단순 BIM의 적용이<br>D/X가 아닙니다</div>
|
|
||||||
<div class="topic-desc">BIM은 건설산업의 디지털전환을 수행하는 과정에서 가장 기초가 되는 일부분임을 인지하는 것이 매우 중요합니다.</div>
|
|
||||||
</div>
|
|
||||||
<div class="topic-row">
|
|
||||||
<div class="topic-title">건설산업에서의 BIM</div>
|
|
||||||
<div class="topic-desc">BIM은 설계, 시공, 유지관리 단계에서의 정보를 통합하고 협업을 촉진하여 인프라 건설 전 생애주기 과정의 효율성을 향상시킵니다.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 3. 이미지 그리드 -->
|
|
||||||
<div class="img-grid">
|
|
||||||
<img src="figma-assets/image_grid_left.png" alt="">
|
|
||||||
<img src="figma-assets/image_grid_right.png" alt="">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 4. 산맥 -->
|
|
||||||
<div class="mountain">
|
|
||||||
<img src="figma-assets/mountain_viz.png" alt="">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 5. 비교 박스 -->
|
|
||||||
<div class="compare-boxes">
|
|
||||||
<div class="cbox">
|
|
||||||
<img src="figma-assets/compare_box_left.png" alt="">
|
|
||||||
<div class="cbox-text">
|
|
||||||
<div class="label">D/X</div>
|
|
||||||
<div class="sub">디지털 기술을 활용한<br>협업 프로세스</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="vs-badge">VS</div>
|
|
||||||
<div class="cbox">
|
|
||||||
<img src="figma-assets/compare_box_right.png" alt="">
|
|
||||||
<div class="cbox-text">
|
|
||||||
<div class="label">BIM</div>
|
|
||||||
<div class="sub">시설물의 전 생애주기 동안<br>정보의 생성 및 관리</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 6. 비교 테이블 (HTML) -->
|
|
||||||
<div class="compare-table">
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>BIM</th>
|
|
||||||
<th>VS</th>
|
|
||||||
<th>D/X</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>• Only 3D</td>
|
|
||||||
<td>BIM · D/X</td>
|
|
||||||
<td>• BIM ≪ D/X (ENG. + Management 포함)</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>• 모델 제작용 상용 S/W<br>(Civil 3D, Revit, Navisworks, Autocad)</td>
|
|
||||||
<td>S/W</td>
|
|
||||||
<td>• 제작 및 운영(상용 + 전용 40~80개)<br>[Rhino, Sketchup, Blender...] + [EG-BIM 등]</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>• 기존 2D 설계방식 유지</td>
|
|
||||||
<td>프로세스</td>
|
|
||||||
<td>• 근본적 문제의식을 통한 개선</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>• 3D 모델 중심<br>• 기존 성과품 유지</td>
|
|
||||||
<td>성과물</td>
|
|
||||||
<td>• 공학 정보 및 콘텐츠 연계에 집중<br>• 도면, 수량, 시공계획 등 일식</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>• 3D 모델에 의한 일반적 이해 향상</td>
|
|
||||||
<td>활용</td>
|
|
||||||
<td>• 설계/시공의 혁신(개념의 재정립)</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>• (설계/시공/운영) 분야별 단절</td>
|
|
||||||
<td>확장성</td>
|
|
||||||
<td>• 전 생애주기 활용 시스템</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>• 단순화(오류) - 수동적/집단적 동질화</td>
|
|
||||||
<td>수행개념</td>
|
|
||||||
<td>• 구체화(복잡) - 적극/구체적 실현 방안</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>• 소극적, 상용 기술에 의존</td>
|
|
||||||
<td>CIVIL + IT</td>
|
|
||||||
<td>• 적극적, 주체적인 기술 접목/융합</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>• S/W 제작사 판매 정책에 의존</td>
|
|
||||||
<td>주체</td>
|
|
||||||
<td>• 자체 수행능력 - 지속가능성 확보</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>• 평준화, 국내 중심</td>
|
|
||||||
<td>발주처</td>
|
|
||||||
<td>• 차별화 및 경쟁력 확보, 해외 진출</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>• 소규모 BIM팀 운영 + 단순교육에 집중</td>
|
|
||||||
<td>설계사</td>
|
|
||||||
<td>• IT + CIVIL ENG 220명 운영 + 기술 개발</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>• 국내 토목 소극적/ 해외 토목증가</td>
|
|
||||||
<td>시공사</td>
|
|
||||||
<td>• 분야 확장 모델 및 시스템</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 7. 원형 라벨 (CSS) -->
|
|
||||||
<div class="circle-label-section">
|
|
||||||
<div class="circle-outer">
|
|
||||||
<div class="circle-inner">
|
|
||||||
<div class="main">단계별<br>BIM의 활용</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 8. 카드 3열 (HTML + 개별 이미지) -->
|
|
||||||
<div class="cards-section">
|
|
||||||
<div class="cards-row">
|
|
||||||
<div class="card card-design">
|
|
||||||
<img class="card-img" src="figma-assets/card_img_design.png" alt="설계">
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="card-stage">설계단계</div>
|
|
||||||
<div class="card-stage-en">Design Stage</div>
|
|
||||||
<div class="card-divider"></div>
|
|
||||||
<ul>
|
|
||||||
<li>고도화된 BIM 구현</li>
|
|
||||||
<li>최첨단 디지털트윈</li>
|
|
||||||
<li>시뮬레이션 분석 & 성능평가</li>
|
|
||||||
<li>지속가능한 인프라개발</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card card-construction">
|
|
||||||
<img class="card-img" src="figma-assets/card_img_construction.png" alt="시공">
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="card-stage">시공 단계</div>
|
|
||||||
<div class="card-stage-en">Construction Stage</div>
|
|
||||||
<div class="card-divider"></div>
|
|
||||||
<ul>
|
|
||||||
<li>향상된 건설 계획과 공정 순서 관리</li>
|
|
||||||
<li>Big Room 등 환경을 통한 협업 및 조정</li>
|
|
||||||
<li>정확한 수량산출서와 비용 산정</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card card-maintenance">
|
|
||||||
<img class="card-img" src="figma-assets/card_img_maintenance.png" alt="유지관리">
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="card-stage">유지관리 단계</div>
|
|
||||||
<div class="card-stage-en">Maintenance Stage</div>
|
|
||||||
<div class="card-divider"></div>
|
|
||||||
<ul>
|
|
||||||
<li>자산 정보 및 데이터 관리</li>
|
|
||||||
<li>예측 기반 유지보수 및 생애주기 분석</li>
|
|
||||||
<li>효율적인 시설 운영 및 지속가능한 관리</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,328 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ko">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>2-1_02 건설정보모델링(BIM) — 레이어 방식 재현</title>
|
|
||||||
<style>
|
|
||||||
@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css');
|
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
||||||
body {
|
|
||||||
font-family: 'Pretendard Variable', 'Noto Sans KR', sans-serif;
|
|
||||||
background: #e8ecf0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══ 프레임 컨테이너 (920x2179 원본 비율) ═══ */
|
|
||||||
.frame {
|
|
||||||
width: 920px;
|
|
||||||
background: #ffffff;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══ 섹션 1: 배경 헤더 + 타이틀 ═══ */
|
|
||||||
.header-section {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 515px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.header-bg {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
.header-bg img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
.header-overlay {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
z-index: 2;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 40px 89px;
|
|
||||||
}
|
|
||||||
.close-btn {
|
|
||||||
position: absolute;
|
|
||||||
top: 20px;
|
|
||||||
right: 20px;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
cursor: pointer;
|
|
||||||
z-index: 10;
|
|
||||||
color: white;
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
.section-title-block {
|
|
||||||
margin-top: auto;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
.section-title-en {
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 400;
|
|
||||||
color: #ffffff;
|
|
||||||
opacity: 0.9;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
.section-title-ko {
|
|
||||||
font-size: 35px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #ffffff;
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══ 섹션 2-3: 꼭지 제목+설명 ═══ */
|
|
||||||
.topic-section {
|
|
||||||
padding: 25px 89px 15px;
|
|
||||||
}
|
|
||||||
.topic-row {
|
|
||||||
display: flex;
|
|
||||||
gap: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.topic-title {
|
|
||||||
width: 240px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #004cbe;
|
|
||||||
line-height: 1.4;
|
|
||||||
word-break: keep-all;
|
|
||||||
}
|
|
||||||
.topic-desc {
|
|
||||||
flex: 1;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 400;
|
|
||||||
color: #000000;
|
|
||||||
line-height: 1.7;
|
|
||||||
word-break: keep-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══ 섹션 4: 이미지 그리드 2열 ═══ */
|
|
||||||
.image-grid-section {
|
|
||||||
padding: 0 89px 20px;
|
|
||||||
display: flex;
|
|
||||||
gap: 0;
|
|
||||||
}
|
|
||||||
.image-grid-item {
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.image-grid-item img {
|
|
||||||
width: 100%;
|
|
||||||
height: 354px;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══ 섹션 5: 산맥 시각화 ═══ */
|
|
||||||
.mountain-section {
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.mountain-section img {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══ 섹션 6: 이미지 (EbYopf) ═══ */
|
|
||||||
.middle-image-section {
|
|
||||||
width: 100%;
|
|
||||||
background: #f6f7f9;
|
|
||||||
padding: 0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══ 섹션 7: 비교 박스 2열 ═══ */
|
|
||||||
.compare-boxes-section {
|
|
||||||
padding: 25px 89px;
|
|
||||||
display: flex;
|
|
||||||
gap: 15px;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.compare-box {
|
|
||||||
width: 327px;
|
|
||||||
height: 116px;
|
|
||||||
border-radius: 8px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.compare-box img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
.vs-label {
|
|
||||||
background: #ffffff;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 8px 20px;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #006eff;
|
|
||||||
}
|
|
||||||
.dx-label, .bim-label {
|
|
||||||
position: absolute;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #ffffff;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══ 섹션 8: 비교 테이블 ═══ */
|
|
||||||
.table-section {
|
|
||||||
padding: 10px 89px 20px;
|
|
||||||
}
|
|
||||||
.table-section img {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══ 섹션 9: 원형 라벨 ═══ */
|
|
||||||
.circle-section {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 20px 0;
|
|
||||||
}
|
|
||||||
.circle-section img {
|
|
||||||
width: 190px;
|
|
||||||
height: 190px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══ 섹션 10: 카드 3열 ═══ */
|
|
||||||
.card-section {
|
|
||||||
padding: 10px 89px 40px;
|
|
||||||
}
|
|
||||||
.card-section img {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══ 네비게이션 화살표 ═══ */
|
|
||||||
.nav-arrows {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 200px;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 0 15px;
|
|
||||||
z-index: 10;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.nav-arrow {
|
|
||||||
width: 29px;
|
|
||||||
height: 40px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #006aff;
|
|
||||||
font-size: 24px;
|
|
||||||
pointer-events: auto;
|
|
||||||
cursor: pointer;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══ 브레드크럼 ═══ */
|
|
||||||
.breadcrumb {
|
|
||||||
position: absolute;
|
|
||||||
top: 20px;
|
|
||||||
left: 89px;
|
|
||||||
z-index: 5;
|
|
||||||
font-size: 13px;
|
|
||||||
color: rgba(255,255,255,0.7);
|
|
||||||
}
|
|
||||||
.breadcrumb span {
|
|
||||||
margin: 0 6px;
|
|
||||||
color: rgba(255,255,255,0.5);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="frame">
|
|
||||||
|
|
||||||
<!-- ═══ 섹션 1: 배경 헤더 + 타이틀 ═══ -->
|
|
||||||
<div class="header-section">
|
|
||||||
<div class="header-bg">
|
|
||||||
<img src="figma-assets/bg_header.png" alt="">
|
|
||||||
</div>
|
|
||||||
<div class="header-overlay">
|
|
||||||
<div class="breadcrumb">
|
|
||||||
건설산업에서의 디지털전환 <span>›</span> Building Information Modeling
|
|
||||||
</div>
|
|
||||||
<div class="close-btn">✕</div>
|
|
||||||
<div class="section-title-block">
|
|
||||||
<div class="section-title-en">Building Information Modeling</div>
|
|
||||||
<div class="section-title-ko">건설정보모델링(BIM)</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ═══ 섹션 2: 꼭지1 ═══ -->
|
|
||||||
<div class="topic-section">
|
|
||||||
<div class="topic-row">
|
|
||||||
<div class="topic-title">단순 BIM의 적용이<br>D/X가 아닙니다</div>
|
|
||||||
<div class="topic-desc">BIM은 건설산업의 디지털전환을 수행하는 과정에서 가장 기초가 되는 일부분임을 인지하는 것이 매우 중요합니다.</div>
|
|
||||||
</div>
|
|
||||||
<div class="topic-row">
|
|
||||||
<div class="topic-title">건설산업에서의 BIM</div>
|
|
||||||
<div class="topic-desc">BIM은 설계, 시공, 유지관리 단계에서의 정보를 통합하고 협업을 촉진하여 인프라 건설 전 생애주기 과정의 효율성을 향상시킵니다.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ═══ 섹션 3: 이미지 그리드 2열 ═══ -->
|
|
||||||
<div class="image-grid-section">
|
|
||||||
<div class="image-grid-item">
|
|
||||||
<img src="figma-assets/image_grid_left.png" alt="시공 현장 1">
|
|
||||||
</div>
|
|
||||||
<div class="image-grid-item">
|
|
||||||
<img src="figma-assets/image_grid_right.png" alt="시공 현장 2">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ═══ 섹션 4: 산맥 시각화 ═══ -->
|
|
||||||
<div class="mountain-section">
|
|
||||||
<img src="figma-assets/mountain_viz.png" alt="산맥 시각화">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ═══ 섹션 5: 비교 박스 ═══ -->
|
|
||||||
<div class="compare-boxes-section">
|
|
||||||
<div class="compare-box">
|
|
||||||
<img src="figma-assets/compare_box_left.png" alt="DX">
|
|
||||||
</div>
|
|
||||||
<div class="vs-label">VS</div>
|
|
||||||
<div class="compare-box">
|
|
||||||
<img src="figma-assets/compare_box_right.png" alt="BIM">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ═══ 섹션 6: DX vs BIM 비교 테이블 ═══ -->
|
|
||||||
<div class="table-section">
|
|
||||||
<img src="figma-assets/dx_bim_table.png" alt="DX vs BIM 비교">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ═══ 섹션 7: 원형 라벨 ═══ -->
|
|
||||||
<div class="circle-section">
|
|
||||||
<img src="figma-assets/circle_label.png" alt="단계별 BIM의 활용">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ═══ 섹션 8: 카드 3열 ═══ -->
|
|
||||||
<div class="card-section">
|
|
||||||
<img src="figma-assets/card_3col.png" alt="설계/시공/유지관리">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
Before Width: | Height: | Size: 1.1 MiB |
@@ -1,206 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ko">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>DX와 BIM의 개념적 구분과 재정립</title>
|
|
||||||
<style>
|
|
||||||
@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css');
|
|
||||||
|
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
||||||
body { font-family: 'Pretendard Variable', sans-serif; }
|
|
||||||
|
|
||||||
.slide {
|
|
||||||
width: 1280px;
|
|
||||||
height: 720px;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Layer 1: 배경 이미지 */
|
|
||||||
.bg-layer {
|
|
||||||
position: absolute;
|
|
||||||
top: 0; left: 0;
|
|
||||||
width: 100%; height: 100%;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
.bg-layer img {
|
|
||||||
width: 100%; height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Layer 2: 슬라이드 제목 */
|
|
||||||
.title-layer {
|
|
||||||
position: absolute;
|
|
||||||
top: 15px; left: 30px;
|
|
||||||
z-index: 10;
|
|
||||||
font-size: 1.6rem;
|
|
||||||
font-weight: 900;
|
|
||||||
color: #1e293b;
|
|
||||||
border-bottom: 3px solid #2563eb;
|
|
||||||
padding-bottom: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Layer 3: 상단 5개 원 위 아이콘 + 텍스트 */
|
|
||||||
.circle-labels {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 10;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.circle-labels .icon {
|
|
||||||
font-size: 1.8rem;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
.circle-labels .label {
|
|
||||||
font-size: 0.95rem;
|
|
||||||
font-weight: 800;
|
|
||||||
color: #1e293b;
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
|
||||||
.circle-labels .desc {
|
|
||||||
font-size: 0.7rem;
|
|
||||||
color: #64748b;
|
|
||||||
line-height: 1.4;
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
.circle-labels .highlight {
|
|
||||||
color: #2563eb;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 각 원 위치 (배경 이미지의 원 위치에 맞춤) */
|
|
||||||
.circle-1 { top: 175px; left: 52px; width: 120px; }
|
|
||||||
.circle-2 { top: 95px; left: 245px; width: 120px; }
|
|
||||||
.circle-3 { top: 50px; left: 460px; width: 120px; }
|
|
||||||
.circle-4 { top: 95px; left: 680px; width: 120px; }
|
|
||||||
.circle-5 { top: 175px; left: 870px; width: 140px; }
|
|
||||||
|
|
||||||
/* Layer 4: 중앙 큰 원 텍스트 */
|
|
||||||
.center-label {
|
|
||||||
position: absolute;
|
|
||||||
top: 410px; left: 440px;
|
|
||||||
width: 200px;
|
|
||||||
z-index: 10;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.center-label .main {
|
|
||||||
font-size: 1.3rem;
|
|
||||||
font-weight: 900;
|
|
||||||
color: #1e293b;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
.center-label .sub {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
color: #64748b;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Layer 5: 좌우 하단 박스 텍스트 */
|
|
||||||
.bottom-box {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 35px;
|
|
||||||
z-index: 10;
|
|
||||||
padding: 8px 20px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.bottom-left {
|
|
||||||
left: 30px;
|
|
||||||
width: 260px;
|
|
||||||
}
|
|
||||||
.bottom-right {
|
|
||||||
right: 30px;
|
|
||||||
width: 260px;
|
|
||||||
}
|
|
||||||
.bottom-box .box-title {
|
|
||||||
font-size: 0.7rem;
|
|
||||||
color: #64748b;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
.bottom-box .box-content {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #1e293b;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Layer 6: 하단 결론 바 */
|
|
||||||
.conclusion-bar {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0; left: 0; right: 0;
|
|
||||||
z-index: 10;
|
|
||||||
background: #1e293b;
|
|
||||||
color: white;
|
|
||||||
text-align: center;
|
|
||||||
padding: 10px 40px;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: -0.3px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="slide">
|
|
||||||
|
|
||||||
<!-- Layer 1: AI 생성 배경 이미지 -->
|
|
||||||
<div class="bg-layer">
|
|
||||||
<img src="test-bg-layer.png" alt="background">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Layer 2: 슬라이드 제목 -->
|
|
||||||
<div class="title-layer">DX와 BIM의 개념적 구분과 재정립</div>
|
|
||||||
|
|
||||||
<!-- Layer 3: 상단 5개 원 위 텍스트 -->
|
|
||||||
<div class="circle-labels circle-1">
|
|
||||||
<div class="icon">📋</div>
|
|
||||||
<div class="label">용어 혼용</div>
|
|
||||||
<div class="desc">DX와 BIM 개념이<br>명확히 <span class="highlight">정립되지 않은 채</span><br>혼용되어 사용</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="circle-labels circle-2">
|
|
||||||
<div class="icon">🏛️</div>
|
|
||||||
<div class="label">정책 사례</div>
|
|
||||||
<div class="desc">건설기술진흥 기본계획<br><span class="highlight">BIM 도입 = 디지털화</span><br>로 표현</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="circle-labels circle-3">
|
|
||||||
<div class="icon">📐</div>
|
|
||||||
<div class="label">BIM</div>
|
|
||||||
<div class="desc">3D 모델 기반<br><span class="highlight">정보 통합·관리</span> 도구<br>핵심 인프라 기술</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="circle-labels circle-4">
|
|
||||||
<div class="icon">🔄</div>
|
|
||||||
<div class="label">DX</div>
|
|
||||||
<div class="desc">디지털 기술 기반<br><span class="highlight">산업 패러다임 전환</span><br>업무방식·가치 구조 변혁</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="circle-labels circle-5">
|
|
||||||
<div class="icon">🔗</div>
|
|
||||||
<div class="label">기술 융합</div>
|
|
||||||
<div class="desc"><span class="highlight">GIS + BIM + DT</span><br>기술 융합으로만<br>DX 실현 가능</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Layer 4: 중앙 큰 원 텍스트 -->
|
|
||||||
<div class="center-label">
|
|
||||||
<div class="main">DX와 BIM의<br>관계</div>
|
|
||||||
<div class="sub">개념적 구분과 재정립</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Layer 5: 좌우 하단 박스 -->
|
|
||||||
<div class="bottom-box bottom-left">
|
|
||||||
<div class="box-title">상위 개념</div>
|
|
||||||
<div class="box-content">산업 패러다임 전환<br>프로세스 혁신</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bottom-box bottom-right">
|
|
||||||
<div class="box-title">핵심 기초 기술</div>
|
|
||||||
<div class="box-content">건설정보 통합 관리<br>디지털 협업 인프라</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Layer 6: 결론 바 -->
|
|
||||||
<div class="conclusion-bar">
|
|
||||||
BIM은 건설산업의 디지털전환(DX)을 수행하는 과정에서 가장 기초가 되는 일부분이다
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,222 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ko">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>DX와 BIM의 개념적 구분과 재정립</title>
|
|
||||||
<style>
|
|
||||||
@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css');
|
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
||||||
body { font-family: 'Pretendard Variable', sans-serif; background: #f0f4f8; display: flex; justify-content: center; padding: 20px; }
|
|
||||||
|
|
||||||
.slide {
|
|
||||||
width: 1280px;
|
|
||||||
height: 720px;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══ Layer 1: 배경 텍스처 ═══ */
|
|
||||||
.bg { position: absolute; inset: 0; z-index: 1; }
|
|
||||||
.bg img { width: 100%; height: 100%; object-fit: cover; }
|
|
||||||
|
|
||||||
/* ═══ Layer 2: 연결선 (SVG) ═══ */
|
|
||||||
.lines-layer {
|
|
||||||
position: absolute; inset: 0; z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══ Layer 3: 원 (각각 개별) ═══ */
|
|
||||||
.node {
|
|
||||||
position: absolute; z-index: 5;
|
|
||||||
display: flex; flex-direction: column; align-items: center;
|
|
||||||
}
|
|
||||||
.node-circle {
|
|
||||||
width: 90px; height: 90px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: white;
|
|
||||||
border: 3px solid #d0dce8;
|
|
||||||
box-shadow: 0 4px 15px rgba(0,0,0,0.08);
|
|
||||||
display: flex; align-items: center; justify-content: center;
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
|
||||||
.node-label {
|
|
||||||
margin-top: 8px; text-align: center;
|
|
||||||
}
|
|
||||||
.node-label .title {
|
|
||||||
font-size: 1rem; font-weight: 800; color: #1e293b;
|
|
||||||
}
|
|
||||||
.node-label .desc {
|
|
||||||
font-size: 0.72rem; color: #64748b; line-height: 1.5; margin-top: 3px;
|
|
||||||
}
|
|
||||||
.node-label .highlight {
|
|
||||||
color: #2563eb; font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 중앙 큰 원 */
|
|
||||||
.center-node {
|
|
||||||
position: absolute; z-index: 5;
|
|
||||||
left: 530px; top: 390px;
|
|
||||||
display: flex; flex-direction: column; align-items: center;
|
|
||||||
}
|
|
||||||
.center-circle {
|
|
||||||
width: 160px; height: 160px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: linear-gradient(180deg, #ffffff 0%, #e8f4fd 100%);
|
|
||||||
border: 4px solid #2563eb;
|
|
||||||
box-shadow: 0 0 40px rgba(37, 99, 235, 0.3), 0 0 80px rgba(37, 99, 235, 0.1);
|
|
||||||
display: flex; flex-direction: column;
|
|
||||||
align-items: center; justify-content: center;
|
|
||||||
}
|
|
||||||
.center-circle .main-text {
|
|
||||||
font-size: 1.2rem; font-weight: 900; color: #1e293b; text-align: center; line-height: 1.4;
|
|
||||||
}
|
|
||||||
.center-circle .sub-text {
|
|
||||||
font-size: 0.7rem; color: #64748b; margin-top: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══ Layer 4: 제목 ═══ */
|
|
||||||
.slide-title {
|
|
||||||
position: absolute; top: 18px; left: 30px; z-index: 10;
|
|
||||||
font-size: 1.5rem; font-weight: 900; color: #1e293b;
|
|
||||||
border-bottom: 3px solid #2563eb; padding-bottom: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══ Layer 5: 하단 좌우 박스 ═══ */
|
|
||||||
.bottom-box {
|
|
||||||
position: absolute; z-index: 10; bottom: 45px;
|
|
||||||
background: rgba(255,255,255,0.85);
|
|
||||||
border: 1px solid #d0dce8;
|
|
||||||
border-radius: 25px;
|
|
||||||
padding: 10px 24px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.bottom-left { left: 40px; }
|
|
||||||
.bottom-right { right: 40px; }
|
|
||||||
.bottom-box .box-label {
|
|
||||||
font-size: 0.65rem; color: #94a3b8; font-weight: 500;
|
|
||||||
}
|
|
||||||
.bottom-box .box-text {
|
|
||||||
font-size: 0.9rem; font-weight: 700; color: #1e293b; line-height: 1.5;
|
|
||||||
}
|
|
||||||
.bottom-box .arrow {
|
|
||||||
font-size: 1.2rem; color: #2563eb; font-weight: 900;
|
|
||||||
}
|
|
||||||
.bottom-left-wrap, .bottom-right-wrap {
|
|
||||||
display: flex; align-items: center; gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══ Layer 6: 결론 바 ═══ */
|
|
||||||
.conclusion {
|
|
||||||
position: absolute; bottom: 0; left: 0; right: 0; z-index: 10;
|
|
||||||
background: #1e293b;
|
|
||||||
color: white; text-align: center;
|
|
||||||
padding: 10px 40px;
|
|
||||||
font-size: 0.95rem; font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══ 5개 노드 위치 ═══ */
|
|
||||||
.node-1 { left: 50px; top: 170px; width: 130px; }
|
|
||||||
.node-2 { left: 250px; top: 80px; width: 140px; }
|
|
||||||
.node-3 { left: 510px; top: 40px; width: 130px; }
|
|
||||||
.node-4 { left: 770px; top: 80px; width: 150px; }
|
|
||||||
.node-5 { left: 1000px; top: 170px; width: 140px; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="slide">
|
|
||||||
|
|
||||||
<!-- Layer 1: 배경 텍스처만 -->
|
|
||||||
<div class="bg">
|
|
||||||
<img src="bg-texture-only.png" alt="">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Layer 2: 연결선 SVG -->
|
|
||||||
<svg class="lines-layer" viewBox="0 0 1280 720" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<!-- 5개 원에서 중앙 원으로 곡선 -->
|
|
||||||
<path d="M 115,260 Q 300,400 610,470" stroke="#b0c4de" stroke-width="1.5" fill="none" opacity="0.5"/>
|
|
||||||
<path d="M 320,170 Q 420,350 610,470" stroke="#b0c4de" stroke-width="1.5" fill="none" opacity="0.5"/>
|
|
||||||
<path d="M 575,130 Q 590,300 610,470" stroke="#b0c4de" stroke-width="1.5" fill="none" opacity="0.5"/>
|
|
||||||
<path d="M 845,170 Q 750,350 610,470" stroke="#b0c4de" stroke-width="1.5" fill="none" opacity="0.5"/>
|
|
||||||
<path d="M 1070,260 Q 850,400 610,470" stroke="#b0c4de" stroke-width="1.5" fill="none" opacity="0.5"/>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<!-- Layer 3: 슬라이드 제목 -->
|
|
||||||
<div class="slide-title">DX와 BIM의 개념적 구분과 재정립</div>
|
|
||||||
|
|
||||||
<!-- Layer 4: 상단 5개 노드 (각각 원 + 아이콘 + 텍스트) -->
|
|
||||||
<div class="node node-1">
|
|
||||||
<div class="node-circle">📋</div>
|
|
||||||
<div class="node-label">
|
|
||||||
<div class="title">용어 혼용</div>
|
|
||||||
<div class="desc">DX와 BIM 개념이<br><span class="highlight">정립되지 않은 채</span><br>혼용되어 사용</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="node node-2">
|
|
||||||
<div class="node-circle">🏛️</div>
|
|
||||||
<div class="node-label">
|
|
||||||
<div class="title">정책 사례</div>
|
|
||||||
<div class="desc">건설기술진흥 기본계획<br><span class="highlight">BIM 도입 = 디지털화</span></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="node node-3">
|
|
||||||
<div class="node-circle">📐</div>
|
|
||||||
<div class="node-label">
|
|
||||||
<div class="title">BIM</div>
|
|
||||||
<div class="desc">3D 모델 기반<br><span class="highlight">정보 통합·관리</span> 도구</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="node node-4">
|
|
||||||
<div class="node-circle">🔄</div>
|
|
||||||
<div class="node-label">
|
|
||||||
<div class="title">DX</div>
|
|
||||||
<div class="desc">디지털 기술 기반<br><span class="highlight">산업 패러다임 전환</span><br>업무방식·가치 구조 변혁</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="node node-5">
|
|
||||||
<div class="node-circle">🔗</div>
|
|
||||||
<div class="node-label">
|
|
||||||
<div class="title">기술 융합</div>
|
|
||||||
<div class="desc"><span class="highlight">GIS + BIM + DT</span><br>기술 융합으로만<br>DX 실현 가능</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Layer 5: 중앙 큰 원 -->
|
|
||||||
<div class="center-node">
|
|
||||||
<div class="center-circle">
|
|
||||||
<div class="main-text">DX와 BIM의<br>관계</div>
|
|
||||||
<div class="sub-text">개념적 구분과 재정립</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Layer 6: 하단 좌우 박스 -->
|
|
||||||
<div class="bottom-box bottom-left">
|
|
||||||
<div class="bottom-left-wrap">
|
|
||||||
<span class="arrow">→</span>
|
|
||||||
<div>
|
|
||||||
<div class="box-label">상위 개념</div>
|
|
||||||
<div class="box-text">산업 패러다임 전환<br>프로세스 혁신</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bottom-box bottom-right">
|
|
||||||
<div class="bottom-right-wrap">
|
|
||||||
<div>
|
|
||||||
<div class="box-label">핵심 기초 기술</div>
|
|
||||||
<div class="box-text">건설정보 통합 관리<br>디지털 협업 인프라</div>
|
|
||||||
</div>
|
|
||||||
<span class="arrow">←</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Layer 7: 결론 바 -->
|
|
||||||
<div class="conclusion">
|
|
||||||
BIM은 건설산업의 디지털전환(DX)을 수행하는 과정에서 가장 기초가 되는 일부분이다
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<?xml-stylesheet href="https://assets.antv.antgroup.com/AlibabaPuHuiTi-Regular/result.css" type="text/css"?>
|
|
||||||
<?xml-stylesheet href="https://assets.antv.antgroup.com/AlibabaPuHuiTi-Bold/result.css" type="text/css"?>
|
|
||||||
<svg height="445" width="434.5" style="" font-family="Alibaba PuHuiTi" xmlns="http://www.w3.org/2000/svg" viewBox="-20 -20 434.5 445"><defs /><defs><radialGradient id="#1783ff-icon" cx="50%" cy="30%" r="70%"><stop offset="0%" stop-color="#b0d5ff" /><stop offset="100%" stop-color="#1783ff" /></radialGradient><linearGradient id="#ff6b6b-badge" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#ff6b6b" /><stop offset="100%" stop-color="#ee5a52" /></linearGradient><radialGradient id="#00c9c9-icon" cx="50%" cy="30%" r="70%"><stop offset="0%" stop-color="#63ffff" /><stop offset="100%" stop-color="#00c9c9" /></radialGradient><radialGradient id="#f0884d-icon" cx="50%" cy="30%" r="70%"><stop offset="0%" stop-color="#fce6da" /><stop offset="100%" stop-color="#f0884d" /></radialGradient><radialGradient id="#d580ff-icon" cx="50%" cy="30%" r="70%"><stop offset="0%" stop-color="#ffffff" /><stop offset="100%" stop-color="#d580ff" /></radialGradient></defs><g id="infographic-container"><g><g><g transform="translate(310.5, 150)"><ellipse x="0" y="0" width="80" height="80" fill="url(##1783ff-icon)" cx="40" cy="40" rx="40" ry="40" /><ellipse x="60" width="24" height="24" fill="url(##ff6b6b-badge)" cx="72" cy="12" rx="12" ry="12" /><g transform="translate(60, 0)"><text width="24" height="24" x="12" y="12" fill="#ffffff" font-size="10" text-anchor="middle" dominant-baseline="central" font-weight="bold" /></g><g transform="translate(0, 88)"><foreignObject height="17" width="80" y="0" x="0" overflow="visible"><span style="overflow:visible;color:#262626;font-weight:bold;font-size:12px;line-height:1.4;width:100%;height:100%;display:flex;flex-wrap:wrap;word-break:break-word;white-space:pre-wrap;text-align:center;justify-content:center;align-content:flex-start;align-items:flex-start" xmlns="http://www.w3.org/1999/xhtml">건설산업 DX</span></foreignObject></g></g><g transform="translate(160.5, 300)"><ellipse x="0" y="0" width="80" height="80" fill="url(##00c9c9-icon)" cx="40" cy="40" rx="40" ry="40" /><ellipse x="60" width="24" height="24" fill="url(##ff6b6b-badge)" cx="72" cy="12" rx="12" ry="12" /><g transform="translate(60, 0)"><text width="24" height="24" x="12" y="12" fill="#ffffff" font-size="10" text-anchor="middle" dominant-baseline="central" font-weight="bold" /></g><g transform="translate(0, 88)"><foreignObject height="17" width="80" y="0" x="0" overflow="visible"><span style="overflow:visible;color:#262626;font-weight:bold;font-size:12px;line-height:1.4;width:100%;height:100%;display:flex;flex-wrap:wrap;word-break:break-word;white-space:pre-wrap;text-align:center;justify-content:center;align-content:flex-start;align-items:flex-start" xmlns="http://www.w3.org/1999/xhtml">GIS</span></foreignObject></g></g><g transform="translate(10.5, 150.00000000000003)"><ellipse x="0" y="0" width="80" height="80" fill="url(##f0884d-icon)" cx="40" cy="40" rx="40" ry="40" /><ellipse x="60" width="24" height="24" fill="url(##ff6b6b-badge)" cx="72" cy="12" rx="12" ry="12" /><g transform="translate(60, 0)"><text width="24" height="24" x="12" y="12" fill="#ffffff" font-size="10" text-anchor="middle" dominant-baseline="central" font-weight="bold" /></g><g transform="translate(0, 88)"><foreignObject height="17" width="80" y="0" x="0" overflow="visible"><span style="overflow:visible;color:#262626;font-weight:bold;font-size:12px;line-height:1.4;width:100%;height:100%;display:flex;flex-wrap:wrap;word-break:break-word;white-space:pre-wrap;text-align:center;justify-content:center;align-content:flex-start;align-items:flex-start" xmlns="http://www.w3.org/1999/xhtml">BIM</span></foreignObject></g></g><g transform="translate(160.49999999999997, 0)"><ellipse x="0" y="0" width="80" height="80" fill="url(##d580ff-icon)" cx="40" cy="40" rx="40" ry="40" /><ellipse x="60" width="24" height="24" fill="url(##ff6b6b-badge)" cx="72" cy="12" rx="12" ry="12" /><g transform="translate(60, 0)"><text width="24" height="24" x="12" y="12" fill="#ffffff" font-size="10" text-anchor="middle" dominant-baseline="central" font-weight="bold" /></g><g transform="translate(0, 88)"><foreignObject height="17" width="80" y="0" x="0" overflow="visible"><span style="overflow:visible;color:#262626;font-weight:bold;font-size:12px;line-height:1.4;width:100%;height:100%;display:flex;flex-wrap:wrap;word-break:break-word;white-space:pre-wrap;text-align:center;justify-content:center;align-content:flex-start;align-items:flex-start" xmlns="http://www.w3.org/1999/xhtml">Digital Twin</span></foreignObject></g></g></g></g></g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 4.7 KiB |
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<?xml-stylesheet href="https://assets.antv.antgroup.com/AlibabaPuHuiTi-Regular/result.css" type="text/css"?>
|
|
||||||
<?xml-stylesheet href="https://assets.antv.antgroup.com/AlibabaPuHuiTi-Bold/result.css" type="text/css"?>
|
|
||||||
<svg height="445" width="434.5" style="" font-family="Alibaba PuHuiTi" xmlns="http://www.w3.org/2000/svg" viewBox="-20 -20 434.5 445"><defs /><defs><radialGradient id="#1783ff-icon" cx="50%" cy="30%" r="70%"><stop offset="0%" stop-color="#b0d5ff" /><stop offset="100%" stop-color="#1783ff" /></radialGradient><linearGradient id="#ff6b6b-badge" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#ff6b6b" /><stop offset="100%" stop-color="#ee5a52" /></linearGradient><radialGradient id="#00c9c9-icon" cx="50%" cy="30%" r="70%"><stop offset="0%" stop-color="#63ffff" /><stop offset="100%" stop-color="#00c9c9" /></radialGradient><radialGradient id="#f0884d-icon" cx="50%" cy="30%" r="70%"><stop offset="0%" stop-color="#fce6da" /><stop offset="100%" stop-color="#f0884d" /></radialGradient><radialGradient id="#d580ff-icon" cx="50%" cy="30%" r="70%"><stop offset="0%" stop-color="#ffffff" /><stop offset="100%" stop-color="#d580ff" /></radialGradient></defs><g id="infographic-container"><g><g><g transform="translate(310.5, 150)"><ellipse x="0" y="0" width="80" height="80" fill="url(##1783ff-icon)" cx="40" cy="40" rx="40" ry="40" /><ellipse x="60" width="24" height="24" fill="url(##ff6b6b-badge)" cx="72" cy="12" rx="12" ry="12" /><g transform="translate(60, 0)"><text width="24" height="24" x="12" y="12" fill="#ffffff" font-size="10" text-anchor="middle" dominant-baseline="central" font-weight="bold" /></g><g transform="translate(0, 88)"><foreignObject height="17" width="80" y="0" x="0" overflow="visible"><span style="overflow:visible;color:#262626;font-weight:bold;font-size:12px;line-height:1.4;width:100%;height:100%;display:flex;flex-wrap:wrap;word-break:break-word;white-space:pre-wrap;text-align:center;justify-content:center;align-content:flex-start;align-items:flex-start" xmlns="http://www.w3.org/1999/xhtml">건설산업 DX</span></foreignObject></g></g><g transform="translate(160.5, 300)"><ellipse x="0" y="0" width="80" height="80" fill="url(##00c9c9-icon)" cx="40" cy="40" rx="40" ry="40" /><ellipse x="60" width="24" height="24" fill="url(##ff6b6b-badge)" cx="72" cy="12" rx="12" ry="12" /><g transform="translate(60, 0)"><text width="24" height="24" x="12" y="12" fill="#ffffff" font-size="10" text-anchor="middle" dominant-baseline="central" font-weight="bold" /></g><g transform="translate(0, 88)"><foreignObject height="17" width="80" y="0" x="0" overflow="visible"><span style="overflow:visible;color:#262626;font-weight:bold;font-size:12px;line-height:1.4;width:100%;height:100%;display:flex;flex-wrap:wrap;word-break:break-word;white-space:pre-wrap;text-align:center;justify-content:center;align-content:flex-start;align-items:flex-start" xmlns="http://www.w3.org/1999/xhtml">GIS</span></foreignObject></g></g><g transform="translate(10.5, 150.00000000000003)"><ellipse x="0" y="0" width="80" height="80" fill="url(##f0884d-icon)" cx="40" cy="40" rx="40" ry="40" /><ellipse x="60" width="24" height="24" fill="url(##ff6b6b-badge)" cx="72" cy="12" rx="12" ry="12" /><g transform="translate(60, 0)"><text width="24" height="24" x="12" y="12" fill="#ffffff" font-size="10" text-anchor="middle" dominant-baseline="central" font-weight="bold" /></g><g transform="translate(0, 88)"><foreignObject height="17" width="80" y="0" x="0" overflow="visible"><span style="overflow:visible;color:#262626;font-weight:bold;font-size:12px;line-height:1.4;width:100%;height:100%;display:flex;flex-wrap:wrap;word-break:break-word;white-space:pre-wrap;text-align:center;justify-content:center;align-content:flex-start;align-items:flex-start" xmlns="http://www.w3.org/1999/xhtml">BIM</span></foreignObject></g></g><g transform="translate(160.49999999999997, 0)"><ellipse x="0" y="0" width="80" height="80" fill="url(##d580ff-icon)" cx="40" cy="40" rx="40" ry="40" /><ellipse x="60" width="24" height="24" fill="url(##ff6b6b-badge)" cx="72" cy="12" rx="12" ry="12" /><g transform="translate(60, 0)"><text width="24" height="24" x="12" y="12" fill="#ffffff" font-size="10" text-anchor="middle" dominant-baseline="central" font-weight="bold" /></g><g transform="translate(0, 88)"><foreignObject height="17" width="80" y="0" x="0" overflow="visible"><span style="overflow:visible;color:#262626;font-weight:bold;font-size:12px;line-height:1.4;width:100%;height:100%;display:flex;flex-wrap:wrap;word-break:break-word;white-space:pre-wrap;text-align:center;justify-content:center;align-content:flex-start;align-items:flex-start" xmlns="http://www.w3.org/1999/xhtml">Digital Twin</span></foreignObject></g></g></g></g></g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 4.7 KiB |
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<?xml-stylesheet href="https://assets.antv.antgroup.com/AlibabaPuHuiTi-Regular/result.css" type="text/css"?>
|
|
||||||
<?xml-stylesheet href="https://assets.antv.antgroup.com/AlibabaPuHuiTi-Bold/result.css" type="text/css"?>
|
|
||||||
<svg height="210" width="460" style="" font-family="Alibaba PuHuiTi" xmlns="http://www.w3.org/2000/svg" viewBox="-20 -20 460 210"><defs /><g id="infographic-container"><g><g><g><g><g><foreignObject height="40" width="140" y="0" x="0" overflow="visible"><span style="overflow:visible;color:#5a5a5a;font-size:14px;line-height:1.4;width:100%;height:100%;display:flex;flex-wrap:wrap;word-break:break-word;white-space:pre-wrap;text-align:center;justify-content:center;align-content:flex-end;align-items:flex-end" xmlns="http://www.w3.org/1999/xhtml">Start</span></foreignObject></g><g transform="translate(0, 40)"><foreignObject height="20" width="140" y="0" x="0" overflow="visible"><span style="overflow:visible;color:#262626;font-weight:bold;font-size:14px;line-height:1.4;width:100%;height:100%;display:flex;flex-wrap:wrap;word-break:break-word;white-space:pre-wrap;text-align:center;justify-content:center;align-content:flex-end;align-items:flex-end" xmlns="http://www.w3.org/1999/xhtml">Step 1</span></foreignObject></g><g transform="translate(0, 70)"><polygon width="140" height="30" fill="#1783ff" points="0,0 130,0 140,15 130,30 0,30 10,15" /><g><text width="140" height="30" x="70" y="15" fill="#ffffff" font-size="16" text-anchor="middle" dominant-baseline="central" font-weight="bold">01</text></g></g></g></g><g transform="translate(140, 0)"><g><g transform="translate(0, 70)"><polygon width="140" height="30" fill="#00c9c9" points="0,0 130,0 140,15 130,30 0,30 10,15" /><g><text width="140" height="30" x="70" y="15" fill="#ffffff" font-size="16" text-anchor="middle" dominant-baseline="central" font-weight="bold">02</text></g></g><g transform="translate(0, 110)"><foreignObject height="20" width="140" y="0" x="0" overflow="visible"><span style="overflow:visible;color:#262626;font-weight:bold;font-size:14px;line-height:1.4;width:100%;height:100%;display:flex;flex-wrap:wrap;word-break:break-word;white-space:pre-wrap;text-align:center;justify-content:center;align-content:flex-start;align-items:flex-start" xmlns="http://www.w3.org/1999/xhtml">Step 2</span></foreignObject></g><g transform="translate(0, 130)"><foreignObject height="40" width="140" y="0" x="0" overflow="visible"><span style="overflow:visible;color:#5a5a5a;font-size:14px;line-height:1.4;width:100%;height:100%;display:flex;flex-wrap:wrap;word-break:break-word;white-space:pre-wrap;text-align:center;justify-content:center;align-content:flex-start;align-items:flex-start" xmlns="http://www.w3.org/1999/xhtml">Progress</span></foreignObject></g></g></g><g transform="translate(280, 0)"><g><g><foreignObject height="40" width="140" y="0" x="0" overflow="visible"><span style="overflow:visible;color:#5a5a5a;font-size:14px;line-height:1.4;width:100%;height:100%;display:flex;flex-wrap:wrap;word-break:break-word;white-space:pre-wrap;text-align:center;justify-content:center;align-content:flex-end;align-items:flex-end" xmlns="http://www.w3.org/1999/xhtml">Complete</span></foreignObject></g><g transform="translate(0, 40)"><foreignObject height="20" width="140" y="0" x="0" overflow="visible"><span style="overflow:visible;color:#262626;font-weight:bold;font-size:14px;line-height:1.4;width:100%;height:100%;display:flex;flex-wrap:wrap;word-break:break-word;white-space:pre-wrap;text-align:center;justify-content:center;align-content:flex-end;align-items:flex-end" xmlns="http://www.w3.org/1999/xhtml">Step 3</span></foreignObject></g><g transform="translate(0, 70)"><polygon width="140" height="30" fill="#f0884d" points="0,0 130,0 140,15 130,30 0,30 10,15" /><g><text width="140" height="30" x="70" y="15" fill="#ffffff" font-size="16" text-anchor="middle" dominant-baseline="central" font-weight="bold">03</text></g></g></g></g></g></g></g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 3.9 KiB |
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<?xml-stylesheet href="https://assets.antv.antgroup.com/AlibabaPuHuiTi-Regular/result.css" type="text/css"?>
|
|
||||||
<?xml-stylesheet href="https://assets.antv.antgroup.com/AlibabaPuHuiTi-Bold/result.css" type="text/css"?>
|
|
||||||
<svg height="210" width="460" style="" font-family="Alibaba PuHuiTi" xmlns="http://www.w3.org/2000/svg" viewBox="-20 -20 460 210"><defs /><g id="infographic-container"><g><g><g><g><g><foreignObject height="40" width="140" y="0" x="0" overflow="visible"><span style="overflow:visible;color:#5a5a5a;font-size:14px;line-height:1.4;width:100%;height:100%;display:flex;flex-wrap:wrap;word-break:break-word;white-space:pre-wrap;text-align:center;justify-content:center;align-content:flex-end;align-items:flex-end" xmlns="http://www.w3.org/1999/xhtml">Start</span></foreignObject></g><g transform="translate(0, 40)"><foreignObject height="20" width="140" y="0" x="0" overflow="visible"><span style="overflow:visible;color:#262626;font-weight:bold;font-size:14px;line-height:1.4;width:100%;height:100%;display:flex;flex-wrap:wrap;word-break:break-word;white-space:pre-wrap;text-align:center;justify-content:center;align-content:flex-end;align-items:flex-end" xmlns="http://www.w3.org/1999/xhtml">Step 1</span></foreignObject></g><g transform="translate(0, 70)"><polygon width="140" height="30" fill="#1783ff" points="0,0 130,0 140,15 130,30 0,30 10,15" /><g><text width="140" height="30" x="70" y="15" fill="#ffffff" font-size="16" text-anchor="middle" dominant-baseline="central" font-weight="bold">01</text></g></g></g></g><g transform="translate(140, 0)"><g><g transform="translate(0, 70)"><polygon width="140" height="30" fill="#00c9c9" points="0,0 130,0 140,15 130,30 0,30 10,15" /><g><text width="140" height="30" x="70" y="15" fill="#ffffff" font-size="16" text-anchor="middle" dominant-baseline="central" font-weight="bold">02</text></g></g><g transform="translate(0, 110)"><foreignObject height="20" width="140" y="0" x="0" overflow="visible"><span style="overflow:visible;color:#262626;font-weight:bold;font-size:14px;line-height:1.4;width:100%;height:100%;display:flex;flex-wrap:wrap;word-break:break-word;white-space:pre-wrap;text-align:center;justify-content:center;align-content:flex-start;align-items:flex-start" xmlns="http://www.w3.org/1999/xhtml">Step 2</span></foreignObject></g><g transform="translate(0, 130)"><foreignObject height="40" width="140" y="0" x="0" overflow="visible"><span style="overflow:visible;color:#5a5a5a;font-size:14px;line-height:1.4;width:100%;height:100%;display:flex;flex-wrap:wrap;word-break:break-word;white-space:pre-wrap;text-align:center;justify-content:center;align-content:flex-start;align-items:flex-start" xmlns="http://www.w3.org/1999/xhtml">Progress</span></foreignObject></g></g></g><g transform="translate(280, 0)"><g><g><foreignObject height="40" width="140" y="0" x="0" overflow="visible"><span style="overflow:visible;color:#5a5a5a;font-size:14px;line-height:1.4;width:100%;height:100%;display:flex;flex-wrap:wrap;word-break:break-word;white-space:pre-wrap;text-align:center;justify-content:center;align-content:flex-end;align-items:flex-end" xmlns="http://www.w3.org/1999/xhtml">Complete</span></foreignObject></g><g transform="translate(0, 40)"><foreignObject height="20" width="140" y="0" x="0" overflow="visible"><span style="overflow:visible;color:#262626;font-weight:bold;font-size:14px;line-height:1.4;width:100%;height:100%;display:flex;flex-wrap:wrap;word-break:break-word;white-space:pre-wrap;text-align:center;justify-content:center;align-content:flex-end;align-items:flex-end" xmlns="http://www.w3.org/1999/xhtml">Step 3</span></foreignObject></g><g transform="translate(0, 70)"><polygon width="140" height="30" fill="#f0884d" points="0,0 130,0 140,15 130,30 0,30 10,15" /><g><text width="140" height="30" x="70" y="15" fill="#ffffff" font-size="16" text-anchor="middle" dominant-baseline="central" font-weight="bold">03</text></g></g></g></g></g></g></g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 3.9 KiB |
@@ -1,402 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ko">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>DX와 BIM의 개념적 구분과 재정립</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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Design Agent — 기본 슬라이드 스타일 */
|
|
||||||
|
|
||||||
@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css');
|
|
||||||
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 슬라이드 컨테이너: 16:9 고정 비율 */
|
|
||||||
.slide {
|
|
||||||
width: 1280px;
|
|
||||||
height: 720px;
|
|
||||||
aspect-ratio: 16 / 9;
|
|
||||||
overflow: hidden;
|
|
||||||
background: var(--color-bg);
|
|
||||||
font-family: 'Pretendard Variable', 'Pretendard', 'Noto Sans KR', sans-serif;
|
|
||||||
color: var(--color-text);
|
|
||||||
font-size: var(--font-body);
|
|
||||||
line-height: var(--line-height-ko);
|
|
||||||
word-break: keep-all;
|
|
||||||
padding: var(--spacing-page);
|
|
||||||
display: grid;
|
|
||||||
gap: var(--spacing-block);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 슬라이드 제목 */
|
|
||||||
.slide-title {
|
|
||||||
font-size: var(--font-title);
|
|
||||||
font-weight: var(--weight-black);
|
|
||||||
color: var(--color-primary);
|
|
||||||
border-bottom: var(--accent-border) solid var(--color-accent);
|
|
||||||
padding-bottom: var(--spacing-small);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 섹션 제목 */
|
|
||||||
.section-title {
|
|
||||||
font-size: var(--font-subtitle);
|
|
||||||
font-weight: var(--weight-bold);
|
|
||||||
color: var(--color-primary);
|
|
||||||
margin-bottom: var(--spacing-small);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 본문 */
|
|
||||||
.body-text {
|
|
||||||
font-size: var(--font-body);
|
|
||||||
color: var(--color-text);
|
|
||||||
line-height: var(--line-height-ko);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 캡션/출처 */
|
|
||||||
.caption {
|
|
||||||
font-size: var(--font-caption);
|
|
||||||
color: var(--color-text-light);
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 강조 텍스트 */
|
|
||||||
.highlight {
|
|
||||||
color: var(--color-accent);
|
|
||||||
font-weight: var(--weight-bold);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 경고/문제 강조 */
|
|
||||||
.danger {
|
|
||||||
color: var(--color-danger);
|
|
||||||
font-weight: var(--weight-bold);
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
<style>
|
|
||||||
|
|
||||||
.slide-1 {
|
|
||||||
grid-template-areas: 'header header' 'left right' 'footer footer';
|
|
||||||
grid-template-columns: 6.5fr 3.5fr;
|
|
||||||
grid-template-rows: auto 1fr auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-1 .area-header {
|
|
||||||
grid-area: header;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-1 .area-left {
|
|
||||||
grid-area: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-1 .area-right {
|
|
||||||
grid-area: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-1 .area-footer {
|
|
||||||
grid-area: footer;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* 다중 페이지: 페이지 간 간격 */
|
|
||||||
.slide + .slide {
|
|
||||||
margin-top: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 인쇄 시 페이지 분리 */
|
|
||||||
@media print {
|
|
||||||
.slide {
|
|
||||||
page-break-after: always;
|
|
||||||
}
|
|
||||||
.slide + .slide {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="slide slide-1">
|
|
||||||
|
|
||||||
<div class="slide-title" style="grid-area: header;">DX와 BIM의 개념적 구분과 재정립</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="area-header">
|
|
||||||
<!-- 강조 인용 블록: 문제 제기, 핵심 메시지 -->
|
|
||||||
<div class="block-quote">
|
|
||||||
<div class="quote-text">건설산업의 디지털 전환 논의에서 DX와 BIM이 개념적으로 명확히 정립되지 않은 채 혼용되어 사용되고 있으며, BIM 기술의 도입을 DX의 완성으로 오인하거나 DX를 BIM 기술 도입 수준으로 한정하는 인식이 확산되고 있다.</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>
|
|
||||||
|
|
||||||
<div class="area-left">
|
|
||||||
<!-- 카드 그리드 블록: 2~4열 카드 배열 -->
|
|
||||||
<div class="block-card-grid" style="--card-count: 2">
|
|
||||||
|
|
||||||
<div class="card" style="border-top-color: None">
|
|
||||||
|
|
||||||
<div class="card-title">제7차 건설기술진흥 기본계획</div>
|
|
||||||
<span class="card-category">국토교통부, 2023</span>
|
|
||||||
<div class="card-description">추진방향: 디지털 전환을 통한 스마트 건설 확산
|
|
||||||
추진과제: BIM 도입으로 건설산업 디지털화</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card" style="border-top-color: None">
|
|
||||||
|
|
||||||
<div class="card-title">스마트 건설 활성화 방안</div>
|
|
||||||
<span class="card-category">국토교통부, 2022</span>
|
|
||||||
<div class="card-description">추진과제: 건설산업 디지털화
|
|
||||||
세부내용: BIM 전면 도입 및 제도 정비, BIM 전문인력 양성</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>
|
|
||||||
|
|
||||||
<div class="area-right">
|
|
||||||
<!-- 카드 그리드 블록: 2~4열 카드 배열 -->
|
|
||||||
<div class="block-card-grid" style="--card-count: 3">
|
|
||||||
|
|
||||||
<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">건설산업 BIM 기본지침, 국토교통부, 2020</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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">DX</div>
|
|
||||||
<span class="card-category">산업 패러다임 변화</span>
|
|
||||||
<div class="card-description">디지털 기술 기반으로 업무방식과 가치 창출 구조를 전환하는 과정 및 결과</div>
|
|
||||||
<div class="card-source">IBM, 2011 / Agile Elephant, 2015</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>
|
|
||||||
|
|
||||||
<div class="area-footer">
|
|
||||||
<!-- 결론 바 블록: 하단 핵심 한 줄 -->
|
|
||||||
<div class="block-conclusion">
|
|
||||||
<div class="conclusion-label">핵심 요약</div>
|
|
||||||
<div class="conclusion-text">BIM은 건설산업의 디지털전환(DX)을 수행하는 과정에서 가장 기초가 되는 일부분이다</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.block-conclusion {
|
|
||||||
background: var(--color-primary);
|
|
||||||
color: white;
|
|
||||||
padding: var(--spacing-inner) var(--spacing-block);
|
|
||||||
border-radius: var(--radius);
|
|
||||||
text-align: center;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
.conclusion-label {
|
|
||||||
font-size: var(--font-caption);
|
|
||||||
color: var(--color-text-light);
|
|
||||||
font-weight: var(--weight-medium);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 1px;
|
|
||||||
}
|
|
||||||
.conclusion-text {
|
|
||||||
font-size: var(--font-subtitle);
|
|
||||||
font-weight: var(--weight-bold);
|
|
||||||
line-height: var(--line-height-ko);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
123
figma_to_html_agent/CLAUDE.md
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
# Figma → HTML Agent
|
||||||
|
|
||||||
|
Figma 프레임을 **수학적으로 정확하게** HTML/CSS로 변환하고, 변환물을 **재사용 가능한 블록 라이브러리**로 축적하는 에이전트.
|
||||||
|
|
||||||
|
## 목적
|
||||||
|
|
||||||
|
사용자가 Figma 파일에서 프레임을 선택하면:
|
||||||
|
1. 그 프레임을 16:9 슬라이드(1280×720) 안의 HTML 블록으로 100% 동일하게 변환한다
|
||||||
|
2. 변환물의 "변형 가능 축"을 기록한다 (원 개수, 색상, 라벨 등)
|
||||||
|
3. 같은 패턴이 반복되면 Jinja2 템플릿으로 추상화하여 design_agent의 블록 라이브러리에 편입한다
|
||||||
|
|
||||||
|
## 핵심 원칙 (절대 어기지 않음)
|
||||||
|
|
||||||
|
1. **수학적 계산만 허용** — 시행착오 px 조정 금지. Figma 좌표 → 스케일 → CSS 값 수학적 도출
|
||||||
|
2. **Bottom-up 프로세스** — leaf 노드 플래튼 → 2개씩 묶기 → 계층 쌓기. top-down하면 누락
|
||||||
|
3. **이상 탐지 필수** — 모든 노드에 bbox 비율 검사, 회전 감지, 중복 감지 수행
|
||||||
|
4. **AI가 먼저 발견** — 디테일(1px, 1° 차이)을 사용자 피드백 전에 AI가 스스로 찾음
|
||||||
|
5. **하드코딩 금지** — 결과물을 수동으로 고치지 말고 프로세스를 고친다
|
||||||
|
6. **AI 역할 분담** — AI는 분류(고르기)만, 구성(만들기)은 코드. LLM은 px을 못 본다
|
||||||
|
7. **컨텍스트 관리는 compact로** — 한 세션에서 여러 프레임을 연속 작업할 수 있다. 컨텍스트가 무거워지면 `/compact` 로 핵심만 요약하고 계속 진행. 이유: 핵심 결정/구조/규칙은 모두 파일(CLAUDE.md, PROCESS.md, RULES.md, blocks_index.md, 산출물)에 박혀있어 compact 후에도 보존됨. 손실되는 건 시행착오/디버깅 과정 뿐이며, 이건 잃어도 OK. 매 프레임마다 새 세션을 강제하면 누적 학습이 silo되어 R13 같은 sub-pattern 발견의 즉시 적용이 불가능해짐.
|
||||||
|
8. **순수 CSS 우선, SVG는 곡선/필터에만** — 동적 재구성 위해 가능한 한 HTML div + linear-gradient 사용
|
||||||
|
9. **프로모션 게이트는 사용자 전용** — 에이전트는 절대 `design_agent/templates/` 에 직접 쓰지 않는다. 모든 작업은 `figma_to_html_agent/` 안에서 끝나며, 본체 라이브러리 이전은 사용자 수동 검수 후 사용자 본인이 수행한다.
|
||||||
|
10. **시맨틱 우선, Figma 평면 레이어 그대로 옮기지 말 것** — Figma의 평면 레이어 구조는 디자인 도구의 한계일 뿐, 의미 구조가 아니다. 마커+텍스트는 list item, 카드 묶음은 column unit, 등 시맨틱하게 재그룹핑하여 작성한다. RULES.md R13 (Custom-marker bullet list) 참조. 새로 발견되는 sub-pattern은 [blocks_index.md](blocks_index.md) "디자인 인사이트" 섹션에 누적한다.
|
||||||
|
11. **모든 슬롯은 기본 optional** — 1:1 단계에서 모든 슬롯이 채워져 있다고 해서 "이 블록은 필수" 로 해석하지 않는다. 같은 블록이 사진 없는/짧은/긴 mdx에 모두 매칭되어야 한다는 가정으로 설계한다.
|
||||||
|
|
||||||
|
## 변환 프로세스 (10단계)
|
||||||
|
|
||||||
|
전체 절차는 [PROCESS.md](PROCESS.md) 참조.
|
||||||
|
|
||||||
|
```
|
||||||
|
0-A. 에이전트: blocks_index.md 한 번 읽기 (지난 변환 패턴 확인)
|
||||||
|
0-B. 사용자: Figma에서 프레임 선택
|
||||||
|
1. get_metadata ← 구조 + bbox
|
||||||
|
2. get_design_context ← gradient/filter/text 정보
|
||||||
|
3. get_screenshot ← Figma 원본 (검증 비교용)
|
||||||
|
4. 자산 → block-tests/assets/shared/{hash} 캐시
|
||||||
|
5. flat.md 작성 ← bottom-up + 이상 탐지 + 변형 축 메모
|
||||||
|
6. 그라데이션 수학 변환 ← scripts/gradient_math.py 호출
|
||||||
|
7. HTML 작성 ← 순수 CSS 우선, transform: scale() 균일 축소
|
||||||
|
8. Selenium 스크린샷 ← Figma 프리뷰와 사람 눈 비교
|
||||||
|
9. block-tests/{slug}.html + flat.md 저장
|
||||||
|
10. blocks_index.md 1줄 업데이트
|
||||||
|
```
|
||||||
|
|
||||||
|
**패턴 발견 트리거:** 동일 구조의 프레임이 **2번째** 등장하는 순간 → `templates_staging/{pattern_id}.html.j2` 로 Jinja2화. 이게 staging 종착점.
|
||||||
|
|
||||||
|
**프로모션 게이트:** staging까지가 에이전트 책임. 그 다음은 사용자가 직접 검수하고 [design_agent/templates/blocks/](../templates/blocks/) 로 이전 + [catalog.yaml](../templates/catalog.yaml) 등록. **에이전트는 design_agent/templates/ 를 절대 건드리지 않는다.**
|
||||||
|
|
||||||
|
## 도구
|
||||||
|
|
||||||
|
| 도구 | 용도 |
|
||||||
|
|------|------|
|
||||||
|
| Figma MCP `get_metadata` | 프레임 구조 + 절대 좌표 |
|
||||||
|
| Figma MCP `get_design_context` | gradient/filter/font 등 stylable 데이터 |
|
||||||
|
| Figma MCP `get_screenshot` | Figma 원본 PNG (눈 검증용) |
|
||||||
|
| `scripts/gradient_math.py` | SVG `<linearGradient>` → CSS `linear-gradient(...)` 수학 변환 |
|
||||||
|
| Selenium (headless Chrome) | HTML 렌더링 + 검증 스크린샷 |
|
||||||
|
| Pillow | 스크린샷 자르기/비교 |
|
||||||
|
|
||||||
|
## 입출력
|
||||||
|
|
||||||
|
**입력:** Figma 파일 + 노드 ID (또는 현재 선택 노드)
|
||||||
|
**출력:**
|
||||||
|
- `block-tests/{slug}.html` — 변환 결과
|
||||||
|
- `block-tests/{slug}_flat.md` — 플래튼/이상 탐지/변형 축 메모
|
||||||
|
- `assets/shared/...` — 공유 자산 캐시
|
||||||
|
- `blocks_index.md` 한 줄 추가
|
||||||
|
|
||||||
|
## 폴더 구조
|
||||||
|
|
||||||
|
```
|
||||||
|
figma_to_html_agent/ ← 에이전트 작업 영역 (staging)
|
||||||
|
├── CLAUDE.md ← 이 파일 (에이전트 명세)
|
||||||
|
├── PROCESS.md ← 10단계 운영 절차 (변환 핸드북)
|
||||||
|
├── MATH.md ← 수학 공식 레퍼런스
|
||||||
|
├── RULES.md ← CSS 보정 규칙 (R1~R12)
|
||||||
|
├── PROCESS-CONTROL.md ← "찍어맞추기 금지" 규칙
|
||||||
|
├── PLAN.md ← 현재 진행 현황
|
||||||
|
├── blocks_index.md ← 변환 완료 도서관
|
||||||
|
│
|
||||||
|
├── scripts/
|
||||||
|
│ ├── __init__.py ← 빈 파일 (패키지 인식용)
|
||||||
|
│ └── gradient_math.py ← SVG→CSS 그라데이션 변환 함수
|
||||||
|
│
|
||||||
|
├── block-tests/ ← Stage 1: 정적 1:1 변환물
|
||||||
|
│ ├── {slug}.html
|
||||||
|
│ ├── {slug}_flat.md
|
||||||
|
│ ├── _renders/ ← Selenium 검증 스크린샷
|
||||||
|
│ └── assets/
|
||||||
|
│ ├── shared/ ← 해시 기반 자산 캐시 (재사용)
|
||||||
|
│ └── frame_{id}/ ← 프레임 전용 자산 (legacy)
|
||||||
|
│
|
||||||
|
└── templates_staging/ ← Stage 2: Jinja2 추상화
|
||||||
|
├── {pattern_id}.html.j2
|
||||||
|
└── {pattern_id}.meta.yaml ← when/slots/min_size_px 초안
|
||||||
|
|
||||||
|
────────────────────────────────────────────────────────
|
||||||
|
🚧 프로모션 게이트 (사용자 수동 작업) 🚧
|
||||||
|
────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
design_agent/ ← 본체 라이브러리 (에이전트 접근 금지)
|
||||||
|
└── templates/
|
||||||
|
├── blocks/{category}/
|
||||||
|
│ └── {pattern_id}.html.j2 ← 사용자가 staging에서 이전
|
||||||
|
└── catalog.yaml ← 사용자가 when/slots 등록
|
||||||
|
```
|
||||||
|
|
||||||
|
**중요:** 에이전트는 위 구분선 아래(`design_agent/templates/`)를 **절대 수정하지 않는다.** 그 영역은 사용자가 staging 결과물을 검수한 뒤 본인이 직접 프로모션한다.
|
||||||
|
|
||||||
|
## 금지 사항
|
||||||
|
|
||||||
|
- 시행착오 px 조정 (1씩 늘려보기 등)
|
||||||
|
- 사용자에게 "맞나요?" 반복 질문 (스스로 검증)
|
||||||
|
- line-height 등 CSS 속성을 감으로 보정 (폰트 메트릭에서 수학적 도출)
|
||||||
|
- 흰 텍스트 스트로크 (`-webkit-text-stroke: white`) 사용
|
||||||
|
- 블록 배경을 검정으로 표시 (미리보기는 항상 흰색 배경)
|
||||||
|
- **이미지 해석으로 gradient 방향 판단** (멀티모달 금지, 데이터로만 판단 — PROCESS-CONTROL.md 참조)
|
||||||
|
- **한 번에 여러 값 동시 수정** (gradient 각도와 border-radius 동시 변경 금지)
|
||||||
|
- **여러 프레임을 한 세션에 변환** (1세션 1프레임 원칙)
|
||||||
|
- **plus-darker 블렌드 사용** (Safari 전용 → multiply로 교체, RULES.md R10)
|
||||||
|
- **Figma 인벤토리/지문/군집 같은 사전 분류** (work-creating-work, 패턴은 bottom-up으로 발견)
|
||||||
|
- **`design_agent/templates/` 직접 수정** (프로모션 게이트는 사용자 전용. 에이전트는 staging까지만)
|
||||||
|
- **사용자에게 "templates/ 에 옮겨드릴까요?" 같은 제안** (월권. 사용자가 알아서 함)
|
||||||
150
figma_to_html_agent/FIGMA-CONVERSION-REVIEW.md
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
# Figma → HTML 변환 프로세스 리뷰
|
||||||
|
|
||||||
|
> 2026-04-08~09 테스트 세션 결과. 기존 FIGMA-EXTRACTION.md / FIGMA-DESIGN-LANGUAGE.md는 그대로 유지.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 테스트 경과
|
||||||
|
|
||||||
|
### 1.1 테스트 대상
|
||||||
|
|
||||||
|
| 순서 | 프레임 | 노드수 | 성격 | 결과 |
|
||||||
|
|------|--------|-------|------|------|
|
||||||
|
| 1 | Frame 1171281214 (37:231) | ~15 | 단일 카드 (H/W 탭+라벨+본문) | 부분 성공 (둥근 모서리 누락) |
|
||||||
|
| 2 | Frame 1171281215 (39:239) | 149 | 시스템 구성 (H/W 7항목 + 중앙원 + S/W 6항목) | 부분 성공 (색상 차이 다수 누락) |
|
||||||
|
| 3 | Frame 1171280278 (17:3403) | 43 | 실제 디자인 (사진, 3D지형, 자유배치) | 미시도 (구조 분석 한계) |
|
||||||
|
|
||||||
|
### 1.2 발견된 누락 사항 (시간순)
|
||||||
|
|
||||||
|
| # | 누락 | 원인 | Figma 필드 |
|
||||||
|
|---|------|------|-----------|
|
||||||
|
| 1 | 평행사변형 우측 상단 Bezier curve | `vectorNetwork.vertices`만 봄, `fillGeometry.path`의 `C` 명령어 미확인 | `fillGeometry[].path` |
|
||||||
|
| 2 | 그라디언트 바 우측 pill-shape | `cornerRadius` 단일값만 체크 | `rectangleCornerRadii` (예: `[0,40,40,0]`) |
|
||||||
|
| 3 | S/W 그라디언트 바 색상 (크림→주황) | H/W 바 색상을 S/W에도 일괄 적용 | `fills[].gradientStops` — 인스턴스별 확인 필요 |
|
||||||
|
| 4 | S/W 아이콘 색상 (주황 vs H/W 올리브) | 같은 imageRef라고 동일 취급 | `fills[].filters` (tint, highlights, shadows) |
|
||||||
|
| 5 | 텍스트 위치 오류, 겹침 | 149노드를 플랫하게 absolute 배치 | 트리 계층 무시가 근본 원인 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 근본 원인 분석
|
||||||
|
|
||||||
|
### 2.1 작업 방식의 문제
|
||||||
|
|
||||||
|
```
|
||||||
|
현재 방식:
|
||||||
|
curl API → 3.6MB JSON 덤프 → 임시 Python 스크립트로 파싱
|
||||||
|
→ 필요해 보이는 필드만 선택적으로 읽음
|
||||||
|
→ 좌표를 눈으로 읽고 HTML에 하드코딩
|
||||||
|
→ 사용자 지적 → 수정 → 또 지적 → 또 수정...
|
||||||
|
|
||||||
|
문제:
|
||||||
|
1. 임시 스크립트가 매번 다르고, 추출 범위가 일정하지 않음
|
||||||
|
2. "중요해 보이는" 필드만 골라 읽으니 형상/필터/개별반지름 등을 놓침
|
||||||
|
3. 같은 패턴 반복 요소의 속성 차이를 대조하지 않음
|
||||||
|
4. 트리 계층을 무시하고 플랫하게 절대좌표 배치
|
||||||
|
5. 전체를 한번에 만들어서 오류 발견이 늦음
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 "배치 우선" 편향
|
||||||
|
|
||||||
|
```
|
||||||
|
AI의 파싱 우선순위 (잘못됨):
|
||||||
|
1. 어디에 있나 (x, y, width, height) ← 먼저 봄
|
||||||
|
2. 무슨 색이나 (fills, color) ← 그다음
|
||||||
|
3. 무슨 글자나 (characters, fontSize) ← 그다음
|
||||||
|
4. 어떤 모양이나 (path, cornerRadius) ← 마지막... 놓침
|
||||||
|
5. 인스턴스 간 차이 (filters, gradient) ← 아예 안 봄
|
||||||
|
|
||||||
|
디자인에서는 4, 5가 핵심임
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. FIGMA-EXTRACTION.md 보강 필요 사항
|
||||||
|
|
||||||
|
> 기존 MD는 유지. 아래 항목들은 방향 확정 후 반영.
|
||||||
|
|
||||||
|
### 3.1 섹션 2.4 "추출해야 하는 핵심 데이터" 추가 필드
|
||||||
|
|
||||||
|
| 추가 필드 | 용도 |
|
||||||
|
|----------|------|
|
||||||
|
| `rectangleCornerRadii` | 꼭짓점별 다른 반지름 (예: `[0,40,40,0]` = 우측만 둥글게) |
|
||||||
|
| `fillGeometry[].path` | SVG path에 `C`/`Q` 곡선 명령어가 있으면 직선이 아닌 형상 |
|
||||||
|
| `arcData` | 호/부채꼴 형상 |
|
||||||
|
| `fills[].filters` | 같은 이미지라도 노드별 필터(tint, highlights, shadows)로 색상 변경 |
|
||||||
|
|
||||||
|
### 3.2 워크플로우 개선 방향 (미확정)
|
||||||
|
|
||||||
|
**소분 → 단계적 조립**:
|
||||||
|
```
|
||||||
|
한번에 전체를 만들지 않는다.
|
||||||
|
|
||||||
|
트리 leaf부터 올라감:
|
||||||
|
→ leaf 변환 (개별 확인)
|
||||||
|
→ 부모 그룹으로 조립
|
||||||
|
→ 다음 레벨로 조립
|
||||||
|
→ 최종 프레임
|
||||||
|
|
||||||
|
각 단계에서 반드시:
|
||||||
|
- 같은 패턴의 인스턴스끼리 속성 대조
|
||||||
|
- 이미지 노드의 filters 확인
|
||||||
|
- 텍스트 겹침 없는지 좌표 간격 확인
|
||||||
|
```
|
||||||
|
|
||||||
|
**단, Figma 트리가 깔끔한 건 사용자가 수작업으로 정리했기 때문.**
|
||||||
|
원본 Figma는 자동 이름 + 의미 없는 중첩이 겹겹이 있는 상태.
|
||||||
|
→ AI가 뒤죽박죽인 구조를 정확히 읽으려면 **Figma MCP 활용이 필수**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 다음 세션에서 이어갈 것
|
||||||
|
|
||||||
|
### 4.1 Figma MCP 테스트
|
||||||
|
|
||||||
|
- **작업 디렉토리를 `D:\ad-hoc\kei\design_agent\`로 열어야** `.mcp.json`의 Figma MCP가 인식됨
|
||||||
|
- 현재 세션은 `D:\`에서 열려서 MCP 미인식
|
||||||
|
- API 키는 업데이트 완료: `figd_-eLtFZz5itRec7N60iJFB1njw1nKH8T_X_PM205T`
|
||||||
|
|
||||||
|
### 4.2 테스트 대상
|
||||||
|
|
||||||
|
```
|
||||||
|
Figma URL: https://www.figma.com/design/9S6LsQyO6zlRxtiqZccOUM/Untitled?node-id=18-8204
|
||||||
|
대상: Frame 1171281172
|
||||||
|
목표: MCP로 구조를 읽고 → HTML로 변환 → 정확도 확인
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 검증 포인트
|
||||||
|
|
||||||
|
- MCP가 트리 구조를 계층적으로 탐색할 수 있는지
|
||||||
|
- 노드별 시각 속성 (fills, filters, gradientStops, cornerRadii 등)을 빠짐없이 읽는지
|
||||||
|
- 읽은 결과를 기반으로 소분→조립 방식으로 HTML 변환이 가능한지
|
||||||
|
|
||||||
|
### 4.4 성공 기준
|
||||||
|
|
||||||
|
MCP 기반으로 변환했을 때, 사용자가 지적하기 전에 다음을 스스로 잡아낼 수 있어야 함:
|
||||||
|
- 둥근 모서리 / 곡선 형상
|
||||||
|
- 인스턴스별 색상 차이 (그라디언트, 필터)
|
||||||
|
- 텍스트 겹침 / 위치 오류
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 사용자가 공유한 참고 자료
|
||||||
|
|
||||||
|
| 자료 | URL | 비고 |
|
||||||
|
|------|-----|------|
|
||||||
|
| Figma→HTML 플러그인 | https://www.figma.com/community/plugin/1421932899298722297 | Convert Figma Design to HTML CSS |
|
||||||
|
| Hubannero 플러그인 | https://www.figma.com/community/plugin/1527963216001787676 | Figma to HTML/MP4/GIFs |
|
||||||
|
| GitHub Copilot MCP | https://github.com/webmaxru/figma-to-webpage-github-copilot-mcp | Figma→Webpage via MCP |
|
||||||
|
| SKT UX MCP | https://github.com/banil-la/figma-mcp-skt-ux | Figma MCP for UX |
|
||||||
|
| MCP Market | https://mcpmarket.com/ko/server/figma-to-ai-html-converter | Figma→AI HTML Converter |
|
||||||
|
| LobeHub Skills | https://lobehub.com/skills/skill.md | Skill definitions |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 기존 MD와의 관계
|
||||||
|
|
||||||
|
| 문서 | 역할 | 상태 |
|
||||||
|
|------|------|------|
|
||||||
|
| `FIGMA-DESIGN-LANGUAGE.md` | 디자인 토큰, 색상, 타이포, 레이아웃 패턴 | 유지 (변경 없음) |
|
||||||
|
| `FIGMA-EXTRACTION.md` | 추출 워크플로우, 수학적 계산, 체크리스트 | 유지 (보강 예정, 미반영) |
|
||||||
|
| `FIGMA-CONVERSION-REVIEW.md` | **이 문서**. 테스트 결과, 인사이트, 다음 세션 이어갈 지점 | 신규 |
|
||||||
164
figma_to_html_agent/FIGMA-DESIGN-LANGUAGE.md
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
# Figma Design Language Analysis
|
||||||
|
|
||||||
|
> Phase 1 결과 문서 (2026-04-07)
|
||||||
|
> Figma Source: `9S6LsQyO6zlRxtiqZccOUM` / Page 1
|
||||||
|
|
||||||
|
## 1. 스코프
|
||||||
|
|
||||||
|
| 프레임 | 역할 | 판정 |
|
||||||
|
|--------|------|------|
|
||||||
|
| Frame 1 (1:3) | 3D 수렴 화살표 | 서브 컴포넌트 (장식 이미지) |
|
||||||
|
| Frame 2 (1:5) | Solution 제작 목표 | **블록화** → hero-icon-cards |
|
||||||
|
| Frame 3 (1:35) | 정책 달성 (Engn.Solution vs DfMA) | **블록화** → compare-2col-badge |
|
||||||
|
| Frame 4 (1:49) | 과정 vs 결과의 혁신 | **블록화** → compare-detail-gradient |
|
||||||
|
| Frame 5 (1:74) | 상세보기 버튼 | 서브 컴포넌트 (CTA) |
|
||||||
|
| Frame 6 (1:80) | 정책방향 (세로 문서) | **제외** (1280×720 부적합) |
|
||||||
|
|
||||||
|
## 2. 스케일 변환
|
||||||
|
|
||||||
|
Figma 캔버스 → 슬라이드(1280px) 변환 비율:
|
||||||
|
- Frame 2, 3: ×0.71 (1808px → 1280px)
|
||||||
|
- Frame 4: ×0.33 (3848px, 양쪽 합쳐서 2패널)
|
||||||
|
|
||||||
|
| Figma | 슬라이드 환산 | 역할 |
|
||||||
|
|-------|-------------|------|
|
||||||
|
| 70px | 28-35px | 대섹션 헤더 |
|
||||||
|
| 60px | 24-28px | Hero 메시지 |
|
||||||
|
| 50px | 22-26px | 섹션 제목, 배지 |
|
||||||
|
| 45px | 20-22px | 카드 타이틀 (EN) |
|
||||||
|
| 40px | 16-20px | 본문 |
|
||||||
|
| 35px | 14-18px | 부제, 한국어 서브 |
|
||||||
|
| 32px | 12-14px | 버튼 |
|
||||||
|
|
||||||
|
## 3. 색상 팔레트 (Warm Theme)
|
||||||
|
|
||||||
|
기존 블루/슬레이트 테마와 **병존**하는 새 팔레트:
|
||||||
|
|
||||||
|
| 토큰 | Hex | Figma 원본 | 용도 |
|
||||||
|
|------|-----|-----------|------|
|
||||||
|
| `--color-warm-brown` | `#5C3714` | rgba(92,55,20) | 과정/프로세스 섹션 제목 |
|
||||||
|
| `--color-dark-teal` | `#084C56` | rgba(8,76,86) | 결과/디지털 섹션 제목 |
|
||||||
|
| `--color-teal` | `#227582` | rgba(34,117,130) | 설명 텍스트 |
|
||||||
|
| `--color-forest` | `#548235` | rgba(84,130,53) | 배경 그라디언트 |
|
||||||
|
| `--color-beige` | `#E4D9C0` | rgba(228,217,192) | 서브틀 배경/버튼 |
|
||||||
|
| `--color-warm-yellow` | `#FAEDCB` | rgba(250,237,203) | 하이라이트 바 |
|
||||||
|
|
||||||
|
### 그라디언트 패턴
|
||||||
|
- 왼쪽(과정): `rgba(165,161,150,0.10) → rgba(57,50,30,1.00)` (베이지→브라운)
|
||||||
|
- 오른쪽(결과): `rgba(41,107,85,0.10) → rgba(3,33,24,1.00)` (틸→다크)
|
||||||
|
- 버튼: `rgba(255,255,255,0.00) → rgba(228,217,192,1.00)` (투명→베이지)
|
||||||
|
- 배경: `rgba(84,130,53,1.00) → rgba(37,62,31,0.00)` (그린→투명)
|
||||||
|
|
||||||
|
## 4. 타이포그래피
|
||||||
|
|
||||||
|
- **폰트**: Pretendard Variable 유지 (Noto Sans KR은 이미 fallback)
|
||||||
|
- **핵심은 크기/굵기 위계**
|
||||||
|
|
||||||
|
| 레벨 | 크기 (슬라이드) | Weight | 스트로크 | 정렬 |
|
||||||
|
|------|---------------|--------|---------|------|
|
||||||
|
| Hero Statement | 24-28px | 700 | white 1.5px | center |
|
||||||
|
| Section Header | 28-35px | 900 | white 5px | center/left |
|
||||||
|
| Badge Title | 22-26px | 700 | 없음 | center |
|
||||||
|
| Card Title (EN) | 20-22px | 900 | white 5px | center |
|
||||||
|
| Card Subtitle (KR) | 14-18px | 500 | white 1.5px | center |
|
||||||
|
| Body Text | 16-20px | 700 | white 1px | left |
|
||||||
|
| Section Sub-title | 22-26px | 900 | 없음 | left |
|
||||||
|
|
||||||
|
### 텍스트 스트로크 기법
|
||||||
|
Figma 디자인의 특징: 다양한 배경 위에서 가독성 확보를 위해 **흰색 스트로크** 사용
|
||||||
|
```css
|
||||||
|
-webkit-text-stroke: 1.5px white; /* 일반 텍스트 */
|
||||||
|
-webkit-text-stroke: 5px white; /* 강조 텍스트 */
|
||||||
|
paint-order: stroke fill; /* 스트로크가 텍스트 뒤로 */
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. 레이아웃 패턴
|
||||||
|
|
||||||
|
### A. Badge Header
|
||||||
|
- 이미지/그라디언트 배경 위 `border-radius: 20px` 바
|
||||||
|
- 중앙 흰색 텍스트 (50px/700 → 22-26px/700)
|
||||||
|
- 높이: ~88px (Figma) → ~44-50px (슬라이드)
|
||||||
|
|
||||||
|
### B. Hero Statement
|
||||||
|
- 전체 폭 중앙 정렬
|
||||||
|
- 큰 텍스트 (60px/700) + 흰색 스트로크
|
||||||
|
- 키워드 **굵은 강조** 가능
|
||||||
|
|
||||||
|
### C. Icon Card Row
|
||||||
|
- N개 카드 수평 배치, 세로 구분선
|
||||||
|
- 각 카드: 아이콘 이미지 + 영문 제목(900) + 한국어 부제(500)
|
||||||
|
- 흰색 둥근 컨테이너 (borderRadius: 20)
|
||||||
|
|
||||||
|
### D. Two-Col Comparison
|
||||||
|
- 좌/우 그라디언트 배경
|
||||||
|
- 각 열: 헤더 바 + (섹션 제목 + 본문) × N개
|
||||||
|
- 색상으로 좌/우 구분 (브라운 vs 틸)
|
||||||
|
|
||||||
|
### E. CTA Button
|
||||||
|
- 그라디언트 바 (투명→베이지) + 둥근 버튼 (r:7)
|
||||||
|
- 흰색 텍스트
|
||||||
|
|
||||||
|
## 6. 디자인 시스템 vs 콘텐츠 전용 경계
|
||||||
|
|
||||||
|
### 디자인 시스템 (블록에 포함)
|
||||||
|
- 색상 팔레트, 그라디언트 패턴
|
||||||
|
- 타이포그래피 위계, 텍스트 스트로크
|
||||||
|
- 둥근 모서리 컨테이너 (r:20)
|
||||||
|
- Badge Header, 2열 비교, N열 카드 레이아웃 구조
|
||||||
|
|
||||||
|
### 콘텐츠 전용 (블록에 포함하지 않음)
|
||||||
|
- 3D 화살표 이미지 (Frame 1) → 콘텐츠가 제공
|
||||||
|
- 특정 아이콘 이미지들 (brain, thunder 등) → 콘텐츠가 제공
|
||||||
|
- 도메인 텍스트 → 슬롯으로 처리
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 추가 블록 (Page 2, 3, 4)
|
||||||
|
|
||||||
|
> 2026-04-08 추가
|
||||||
|
|
||||||
|
### Page 2 (15:2) — 프레젠테이션 슬라이드
|
||||||
|
|
||||||
|
| 블록 | 출처 | 설명 |
|
||||||
|
|------|------|------|
|
||||||
|
| `category-strip-table` | 001_개요 우측 하단 | 컬러 스트립 N열 테이블 (기술/사람/자연) |
|
||||||
|
|
||||||
|
- 다크 배경, 좌측 색상 바(세로 라벨) + 제목/본문 M행 반복
|
||||||
|
- N열 동적 (2~5), 색상 바 색상은 열마다 지정
|
||||||
|
- scale = 1200/2123 = 0.5652
|
||||||
|
|
||||||
|
### Page 3 (18:8204) — 컴포넌트
|
||||||
|
|
||||||
|
| 블록 | 출처 | 설명 |
|
||||||
|
|------|------|------|
|
||||||
|
| `checklist-dark` | f5 (1770×553) | 체크 아이콘 + 제목:설명 N행 리스트 |
|
||||||
|
| `system-2col-center` | f8 (2446×1943) | 좌/우 항목 + 중앙 원형 라벨 |
|
||||||
|
|
||||||
|
- checklist-dark: 다크 배경, 주황 체크(☑), 제목:설명 한 줄 구조
|
||||||
|
- system-2col-center: 3열 Grid (좌 항목 + 중앙 원 + 우 항목), 색상 탭
|
||||||
|
|
||||||
|
### Page 4 (29:439) — 순환 다이어그램
|
||||||
|
|
||||||
|
| 블록 | 출처 | 설명 |
|
||||||
|
|------|------|------|
|
||||||
|
| `cycle-orbit` | Frame 1 (1076×292) | 3D 원 투영 순환 궤도 다이어그램 |
|
||||||
|
|
||||||
|
핵심 수학:
|
||||||
|
- **3D 원 → Z축 기울임(80°) → 2D 투영** (토성 고리 원리)
|
||||||
|
- `project(α) = (cx + R×cos(α), cy + R×sin(α)×cos(80°))`
|
||||||
|
- N개 노드: `360°/N` 간격, 사이각 2/3로 축소 (앞쪽 가까워짐)
|
||||||
|
- 하단 중심(90°) 기준 좌/우 대칭 배치
|
||||||
|
- 설명 텍스트: 좌측 노드 → 이름 좌측에, 우측/상단 노드 → 이름 우측에
|
||||||
|
- 화살표: 호 위 1/3, 2/3 지점에 접선 방향 회전
|
||||||
|
|
||||||
|
## 8. 전체 블록 목록 (7개)
|
||||||
|
|
||||||
|
| # | 블록 ID | 카테고리 | 출처 | 핵심 특징 |
|
||||||
|
|---|--------|---------|------|----------|
|
||||||
|
| 1 | `hero-icon-cards` | cards | Page 1 | 3D 리본 배지 + 빨간 테두리 박스 + N열 카드 |
|
||||||
|
| 2 | `compare-2col-badge` | cards | Page 1 | 3D 리본 탭 + 틸 테두리 2열 비교 |
|
||||||
|
| 3 | `compare-detail-gradient` | cards | Page 1 | 비대칭 라운드 헤더 + Grid 행 정렬 + As-Is/To-Be |
|
||||||
|
| 4 | `category-strip-table` | cards | Page 2 | 컬러 스트립 바 + 다크 배경 N열 테이블 |
|
||||||
|
| 5 | `checklist-dark` | emphasis | Page 3 | 체크 아이콘 + 제목:설명 다크 리스트 |
|
||||||
|
| 6 | `system-2col-center` | cards | Page 3 | 중앙 원형 라벨 + 좌/우 항목 Grid |
|
||||||
|
| 7 | `cycle-orbit` | visuals | Page 4 | 3D 원 투영 SVG 순환 궤도 |
|
||||||
674
figma_to_html_agent/FIGMA-EXTRACTION.md
Normal file
@@ -0,0 +1,674 @@
|
|||||||
|
# Figma → HTML 블록 변환 프로세스
|
||||||
|
|
||||||
|
> 2026-04-07 확립. Figma 디자인을 design_agent 블록으로 변환하는 정확한 방법론.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 전체 워크플로우
|
||||||
|
|
||||||
|
```
|
||||||
|
[Step 1] Figma API로 파일 구조 추출
|
||||||
|
↓
|
||||||
|
[Step 2] 프레임별 렌더링 이미지(PNG) 다운로드
|
||||||
|
↓
|
||||||
|
[Step 3] 노드별 상세 데이터 추출 (좌표, 색상, 폰트, 크기)
|
||||||
|
↓
|
||||||
|
[Step 4] 디자인 언어 분석 (공통 패턴 vs 콘텐츠 전용 구분)
|
||||||
|
↓
|
||||||
|
[Step 5] 블록 설계 (슬롯, 동적 규칙, schema)
|
||||||
|
↓
|
||||||
|
[Step 6] 수학적 계산 (Figma 좌표 → 스케일 → CSS값)
|
||||||
|
↓
|
||||||
|
[Step 7] HTML/CSS 구현
|
||||||
|
↓
|
||||||
|
[Step 8] 비교 리뷰 (Figma PNG vs HTML, 같은 폭으로 위/아래 배치)
|
||||||
|
↓
|
||||||
|
[Step 9] 피드백 반영 → Step 6~8 반복
|
||||||
|
↓
|
||||||
|
[Step 10] Jinja2 템플릿화 + catalog.yaml 등록
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Figma API 사용법
|
||||||
|
|
||||||
|
### 2.1 파일 구조 가져오기
|
||||||
|
```bash
|
||||||
|
curl -s -H "X-Figma-Token: {TOKEN}" \
|
||||||
|
"https://api.figma.com/v1/files/{FILE_KEY}" \
|
||||||
|
| python -m json.tool
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 특정 노드 상세 데이터
|
||||||
|
```bash
|
||||||
|
curl -s -H "X-Figma-Token: {TOKEN}" \
|
||||||
|
"https://api.figma.com/v1/files/{FILE_KEY}/nodes?ids={NODE_IDS}&geometry=paths"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 노드 이미지 렌더링 (PNG)
|
||||||
|
```bash
|
||||||
|
curl -s -H "X-Figma-Token: {TOKEN}" \
|
||||||
|
"https://api.figma.com/v1/images/{FILE_KEY}?ids={NODE_IDS}&format=png&scale=2"
|
||||||
|
```
|
||||||
|
- `scale=2`: 2배 해상도로 다운로드 (선명도 확보)
|
||||||
|
- 응답의 `images` 객체에 각 노드 ID별 S3 URL 제공
|
||||||
|
|
||||||
|
### 2.4 추출해야 하는 핵심 데이터
|
||||||
|
|
||||||
|
| 데이터 | API 필드 | 용도 |
|
||||||
|
|-------|---------|------|
|
||||||
|
| 위치 | `absoluteBoundingBox.x, .y` | 요소 간 관계 계산 |
|
||||||
|
| 크기 | `absoluteBoundingBox.width, .height` | 스케일 계산 |
|
||||||
|
| 텍스트 | `characters` | 콘텐츠 확인 |
|
||||||
|
| 폰트 | `style.fontFamily, .fontSize, .fontWeight` | 타이포그래피 |
|
||||||
|
| 색상 | `fills[].color` | 색상 팔레트 |
|
||||||
|
| 테두리 | `strokes[], strokeWeight` | 박스 스타일 |
|
||||||
|
| 라운드 (단일) | `cornerRadius` | 4꼭짓점 동일 border-radius |
|
||||||
|
| **라운드 (개별)** | **`rectangleCornerRadii`** | **[TL, TR, BR, BL] 꼭짓점별 다른 반지름. pill-shape 등 비대칭 라운드 감지** |
|
||||||
|
| **형상 경로** | **`fillGeometry[].path`** | **SVG path 문자열. `C`/`Q`/`A` 명령어 존재 시 곡선 형상 → `clip-path: path()` 또는 SVG로 구현** |
|
||||||
|
| **호/부채꼴** | **`arcData`** | **원호, 부채꼴 등 호 형상 파라미터** |
|
||||||
|
| 이미지 | `fills[].imageRef` | 이미지 자산 식별 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 수학적 계산 (핵심)
|
||||||
|
|
||||||
|
### 3.1 스케일 팩터
|
||||||
|
|
||||||
|
```
|
||||||
|
슬라이드 콘텐츠 폭 = 1280px - padding(40px × 2) = 1200px
|
||||||
|
|
||||||
|
scale = 1200 / figma_frame_width
|
||||||
|
```
|
||||||
|
|
||||||
|
| Figma 프레임 | 폭 | 스케일 |
|
||||||
|
|-------------|-----|--------|
|
||||||
|
| Frame 2 | 1808px | 0.6637 |
|
||||||
|
| Frame 3 | 1807px | 0.6641 |
|
||||||
|
| Frame 4 | 3848px | 0.3118 |
|
||||||
|
|
||||||
|
### 3.2 요소 간 정렬 계산
|
||||||
|
|
||||||
|
**절대 원칙: Figma 좌표 차이값 → 스케일 적용 → CSS값**
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 예: 리본 접힘선과 박스 테두리 정렬
|
||||||
|
badge_y = 1431 # Figma에서 badge 이미지 top Y
|
||||||
|
box_y = 1449 # Figma에서 box top Y
|
||||||
|
fold_offset = box_y - badge_y # = 18px (Figma 기준)
|
||||||
|
|
||||||
|
# 스케일 적용
|
||||||
|
fold_offset_css = round(fold_offset * scale) # = 12px (CSS)
|
||||||
|
```
|
||||||
|
|
||||||
|
**금지: "좀 더 올려볼게요" 식의 시행착오 px 조정**
|
||||||
|
|
||||||
|
### 3.3 이미지 자산 크기 계산
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Figma 원본 크기에 스케일 적용
|
||||||
|
ribbon_width_css = round(badge_img_width * scale)
|
||||||
|
ribbon_height_css = round(badge_img_height * scale)
|
||||||
|
|
||||||
|
# 비율 계산 (CSS에서 width만 지정하면 height는 자동)
|
||||||
|
aspect_ratio = badge_img_width / badge_img_height
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 패딩/여백 계산
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 리본이 박스 안에 들어오는 높이 = 리본 전체 높이 - 접힘선 오프셋
|
||||||
|
ribbon_inside_box = ribbon_height_css - fold_offset_css
|
||||||
|
|
||||||
|
# 박스 상단 패딩 = 리본 침입 높이 + 여유
|
||||||
|
box_padding_top = ribbon_inside_box + 6 # 6px 여유
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.5 실제 계산 예시 (Frame 2)
|
||||||
|
|
||||||
|
```
|
||||||
|
입력 (Figma 원본):
|
||||||
|
badge 이미지: 508×94px, y=1431
|
||||||
|
box: y=1449
|
||||||
|
frame width: 1808px
|
||||||
|
|
||||||
|
계산:
|
||||||
|
scale = 1200/1808 = 0.6637
|
||||||
|
ribbon_w = 508 × 0.6637 = 337px
|
||||||
|
ribbon_h = 94 × 0.6637 = 62px
|
||||||
|
fold_offset = (1449-1431) × 0.6637 = 12px
|
||||||
|
ribbon_below_fold = 62 - 12 = 50px
|
||||||
|
box_padding_top = 50 + 6 = 56px
|
||||||
|
|
||||||
|
CSS 출력:
|
||||||
|
.ribbon { width: 337px; top: -12px; }
|
||||||
|
.box { padding-top: 56px; }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 이미지 자산 처리
|
||||||
|
|
||||||
|
### 4.1 CSS로 만들면 안 되는 것
|
||||||
|
|
||||||
|
| 요소 | 이유 | 처리 |
|
||||||
|
|------|------|------|
|
||||||
|
| 3D 리본/두루마리 | 입체감, 그림자, 곡면 → CSS 불가 | Figma에서 PNG 추출 |
|
||||||
|
| 복잡한 그라디언트 배경 | 다중 정지점, 비선형 → CSS 근사 불가 | 이미지 사용 |
|
||||||
|
| 아이콘 이미지 | 디자이너가 만든 고유 자산 | 원본 이미지 사용 |
|
||||||
|
|
||||||
|
### 4.2 CSS로 만들 수 있는 것
|
||||||
|
|
||||||
|
| 요소 | CSS 구현 |
|
||||||
|
|------|---------|
|
||||||
|
| 단색/2색 그라디언트 배경 | `linear-gradient()` |
|
||||||
|
| 둥근 모서리 테두리 박스 | `border + border-radius` |
|
||||||
|
| 텍스트 스타일 | `font-size, font-weight, color` |
|
||||||
|
| 그리드/플렉스 레이아웃 | `display: grid / flex` |
|
||||||
|
| 구분선 | `border` or `background` |
|
||||||
|
|
||||||
|
### 4.3 이미지 추출 및 저장
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Figma API로 특정 노드 이미지 추출
|
||||||
|
curl -s -H "X-Figma-Token: {TOKEN}" \
|
||||||
|
"https://api.figma.com/v1/images/{FILE_KEY}?ids={NODE_ID}&format=png&scale=2"
|
||||||
|
|
||||||
|
# 다운로드 → static/figma-assets/ 에 저장
|
||||||
|
curl -s -o static/figma-assets/{name}.png "{S3_URL}"
|
||||||
|
```
|
||||||
|
|
||||||
|
저장 위치: `static/figma-assets/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 비교 리뷰 페이지 작성법
|
||||||
|
|
||||||
|
### 5.1 레이아웃
|
||||||
|
|
||||||
|
```
|
||||||
|
같은 폭으로 위/아래 배치 (좌/우 아님 — 크기 차이 문제)
|
||||||
|
|
||||||
|
┌─ 빨간 테두리 ──────────────┐
|
||||||
|
│ Figma Original (PNG) │
|
||||||
|
└─────────────────────────────┘
|
||||||
|
─ 구분선 ─
|
||||||
|
┌─ 초록 테두리 ──────────────┐
|
||||||
|
│ HTML Block │
|
||||||
|
└─────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 HTML 스케일링
|
||||||
|
|
||||||
|
```css
|
||||||
|
.html-inner {
|
||||||
|
width: 1280px; /* 슬라이드 원본 크기 */
|
||||||
|
transform-origin: top left;
|
||||||
|
transform: scale(0.74); /* 960px 컨테이너에 맞춤: 960/1280 */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 비교 리뷰 파일 위치
|
||||||
|
|
||||||
|
`data/figma_ref/comparison.html`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Jinja2 템플릿 변환 규칙
|
||||||
|
|
||||||
|
### 6.1 고정값 → 변수
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Figma 원본의 텍스트 → Jinja2 변수 -->
|
||||||
|
<span>정책 달성</span> → <span>{{ badge_title }}</span>
|
||||||
|
<span>Engn. Solution</span> → <span>{{ left_title }}</span>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 반복 요소 → 루프
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- N개 카드 → for loop -->
|
||||||
|
{% for card in cards %}
|
||||||
|
<div class="card">{{ card.title }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 이미지 자산 → 슬롯
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- 리본 이미지: 색상에 따라 다른 자산 사용 가능 -->
|
||||||
|
<img src="{{ ribbon_image | default('figma-assets/badge_solution.png') }}">
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.4 계산된 CSS → CSS 변수
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- 수학적 계산 결과를 CSS 변수로 -->
|
||||||
|
<div style="--ribbon-width: {{ ribbon_width }}px; --fold-offset: {{ fold_offset }}px;">
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 디자인 언어 vs 콘텐츠 전용 구분
|
||||||
|
|
||||||
|
### 디자인 언어 (블록에 포함, 재사용 가능)
|
||||||
|
- 색상 팔레트 (warm 테마: 브라운, 틸, 베이지)
|
||||||
|
- 타이포그래피 위계 (크기, 굵기 단계)
|
||||||
|
- 레이아웃 구조 (2열 비교, N열 카드 등)
|
||||||
|
- 장식 요소 (3D 리본, 둥근 컨테이너)
|
||||||
|
|
||||||
|
### 콘텐츠 전용 (블록에 포함하지 않음)
|
||||||
|
- 특정 텍스트 ("디지털전환은 사용자...")
|
||||||
|
- 특정 아이콘 이미지 (brain, thunder 등)
|
||||||
|
- 도메인 전문 용어 (DfMA, Engn. Solution)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 파일 구조
|
||||||
|
|
||||||
|
```
|
||||||
|
design_agent/
|
||||||
|
├── static/figma-assets/ ← Figma에서 추출한 이미지 자산
|
||||||
|
│ ├── badge_policy.png (틸 3D 리본)
|
||||||
|
│ ├── badge_solution.png (빨간 3D 리본)
|
||||||
|
│ ├── box_policy_container.png
|
||||||
|
│ ├── box_solution_cards.png
|
||||||
|
│ └── arrow_asis_tobe.png (As-Is→To-Be 화살표)
|
||||||
|
├── data/figma_ref/ ← 비교 리뷰용
|
||||||
|
│ ├── comparison.html (Figma vs HTML 비교 페이지 — 전체 블록)
|
||||||
|
│ ├── frame2_1-5.png (Page 1 Figma 원본)
|
||||||
|
│ ├── frame3_1-35.png
|
||||||
|
│ ├── frame4_1-49.png
|
||||||
|
│ ├── strip_table.png (Page 2 필수조건 테이블)
|
||||||
|
│ ├── checklist_dark.png (Page 3 체크리스트)
|
||||||
|
│ ├── system_2col.png (Page 3 시스템 구성)
|
||||||
|
│ └── cycle_orbit.png (Page 4 순환 궤도)
|
||||||
|
├── templates/blocks/cards/ ← 카드 블록 (Figma 신규 5개)
|
||||||
|
│ ├── hero-icon-cards.html (Page 1 — 히어로 + N열 아이콘 카드)
|
||||||
|
│ ├── compare-2col-badge.html (Page 1 — 3D 리본 배지 + 2열 비교)
|
||||||
|
│ ├── compare-detail-gradient.html (Page 1 — 그라디언트 상세 2열 비교)
|
||||||
|
│ ├── category-strip-table.html (Page 2 — 컬러 스트립 N열 테이블)
|
||||||
|
│ └── system-2col-center.html (Page 3 — 중앙 라벨 + 좌/우 항목)
|
||||||
|
├── templates/blocks/emphasis/ ← 강조 블록 (Figma 신규 1개)
|
||||||
|
│ └── checklist-dark.html (Page 3 — 체크 아이콘 + 제목:설명 리스트)
|
||||||
|
├── templates/blocks/visuals/ ← 비주얼 블록 (Figma 신규 1개)
|
||||||
|
│ └── cycle-orbit.html (Page 4 — 3D 원 투영 순환 궤도 다이어그램)
|
||||||
|
├── FIGMA-DESIGN-LANGUAGE.md ← 디자인 언어 분석 결과
|
||||||
|
├── FIGMA-EXTRACTION.md ← 이 문서
|
||||||
|
└── PHASE-FIGMA-BLOCKS.md ← 블록 설계 명세
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 고급 레이아웃 패턴
|
||||||
|
|
||||||
|
### 9.1 좌/우 열 섹션 Y선 정렬 (CSS Grid 행 공유)
|
||||||
|
|
||||||
|
2열 비교에서 좌/우 섹션 제목이 같은 Y선에 있어야 할 때:
|
||||||
|
|
||||||
|
**문제**: 각 열을 독립 flex-column으로 만들면, 좌측 섹션 본문이 길면 우측 다음 섹션이 밀림.
|
||||||
|
```
|
||||||
|
flex-column (잘못):
|
||||||
|
좌: [제목1] [긴본문] [제목2]
|
||||||
|
우: [제목1] [짧은본문] [제목2] ← 제목2가 좌측과 Y가 다름
|
||||||
|
```
|
||||||
|
|
||||||
|
**해결**: CSS Grid 2열 × N행으로 행을 공유하면 자동 정렬.
|
||||||
|
```css
|
||||||
|
.block {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr; /* 2열 */
|
||||||
|
grid-template-rows: auto auto auto auto; /* 헤더 + N행 */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```
|
||||||
|
Grid (올바름):
|
||||||
|
[좌 헤더] [우 헤더] ← Row 0
|
||||||
|
[좌 섹션1] [우 섹션1] ← Row 1 (행 높이 = max(좌,우))
|
||||||
|
[좌 섹션2] [우 섹션2] ← Row 2 (Y선 자동 정렬!)
|
||||||
|
```
|
||||||
|
|
||||||
|
**실제 계산 (Frame 4)**:
|
||||||
|
```
|
||||||
|
Figma Y좌표:
|
||||||
|
Row 1: 좌 1166, 우 1166 → 0px 차이 (이미 정렬)
|
||||||
|
Row 2: 좌 1529, 우 1467 → 62px 차이 (Grid가 해결)
|
||||||
|
Row 3: 좌 1845, 우 1845 → 0px 차이 (이미 정렬)
|
||||||
|
원인: Row 1 좌측에 As-Is→To-Be 구조가 있어서 본문이 62px 더 높음
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 As-Is → To-Be 수평 서브 레이아웃
|
||||||
|
|
||||||
|
한 섹션 안에서 변환 전/후를 수평 배치할 때:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="asis-tobe">
|
||||||
|
<div class="asis">
|
||||||
|
<div class="bullet">이전 상태 1</div>
|
||||||
|
<div class="bullet">이전 상태 2</div>
|
||||||
|
</div>
|
||||||
|
<img src="arrow.png" class="arrow" alt="→">
|
||||||
|
<div class="tobe">
|
||||||
|
<div class="bullet">변환 후 1</div>
|
||||||
|
<div class="bullet">변환 후 2</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
```css
|
||||||
|
.asis-tobe { display: flex; align-items: center; gap: 8px; }
|
||||||
|
.asis, .tobe { flex: 1; }
|
||||||
|
.arrow { width: 60px; height: auto; flex-shrink: 0; }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Figma 좌표로 검증**:
|
||||||
|
```
|
||||||
|
As-Is: x=2737, w=539
|
||||||
|
Arrow: x=3375, w=252
|
||||||
|
To-Be: x=3687, w=672
|
||||||
|
→ 세 요소가 같은 Y(1269)에 수평 배치됨을 좌표로 확인
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.3 3D 리본/두루마리 배지 정렬 공식
|
||||||
|
|
||||||
|
리본 이미지의 접힘선(fold-back)이 박스 테두리와 정확히 일치해야 할 때:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌── 리본 이미지 ──────────────┐
|
||||||
|
│ 접힘 삼각형 (fold) │ ← fold_offset (이미지 top에서)
|
||||||
|
│ 리본 본체 │
|
||||||
|
│ │
|
||||||
|
└──────────────────────────────┘
|
||||||
|
════════════════════════════════ ← 박스 top border (여기에 fold가 일치해야 함)
|
||||||
|
┌── 박스 ──────────────────────┐
|
||||||
|
│ padding-top = ribbon_below │
|
||||||
|
│ 콘텐츠 시작 │
|
||||||
|
|
||||||
|
계산:
|
||||||
|
fold_offset = (box_y - badge_y) × scale → CSS: top 값
|
||||||
|
ribbon_below = ribbon_height - fold_offset → 박스 안 침입 높이
|
||||||
|
box_padding_top = ribbon_below + 여유(6px) → 콘텐츠 겹침 방지
|
||||||
|
```
|
||||||
|
|
||||||
|
**핵심**: 리본을 올리거나 내리는 게 아니라, **박스의 위치를 계산**하는 것.
|
||||||
|
- `top: -fold_offset` → 리본 접힘선 = 박스 top border
|
||||||
|
- 리본은 그대로, 박스와의 관계만 수학적으로 결정
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 순환 궤도 다이어그램 (cycle-orbit) 수학 공식
|
||||||
|
|
||||||
|
### 10.1 핵심 개념: 3D 원 → Z축 기울임 → 2D 투영
|
||||||
|
|
||||||
|
타원이 아니라 **3D 원을 Z축으로 기울인 것**. 토성의 고리와 같은 원리.
|
||||||
|
|
||||||
|
```
|
||||||
|
3D 공간: 2D 투영 (화면):
|
||||||
|
y y
|
||||||
|
| / z |
|
||||||
|
| / |
|
||||||
|
| / θ=80° (기울임) |
|
||||||
|
|/______ x |______ x
|
||||||
|
|
||||||
|
원 (R=400) 타원 (rx=400, ry=69)
|
||||||
|
→ Z축으로 80° 뒤로 눕힘 → y축이 cos(80°)=0.1736으로 압축
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.2 기본 공식
|
||||||
|
|
||||||
|
```python
|
||||||
|
import math
|
||||||
|
|
||||||
|
R = 400 # 3D 원 반지름
|
||||||
|
cx, cy = 500, 200 # 원 중심 (SVG 좌표)
|
||||||
|
theta = 80 # Z축 기울임 각도 (°)
|
||||||
|
tilt = math.radians(theta)
|
||||||
|
|
||||||
|
# 투영된 타원 파라미터
|
||||||
|
rx = R # x축은 변하지 않음
|
||||||
|
ry = R * math.cos(tilt) # y축만 cos(θ)로 압축
|
||||||
|
|
||||||
|
# 원 위의 한 점 (각도 α)을 2D로 투영
|
||||||
|
def project(alpha_deg):
|
||||||
|
a = math.radians(alpha_deg)
|
||||||
|
x = cx + R * math.cos(a)
|
||||||
|
y = cy + R * math.sin(a) * math.cos(tilt)
|
||||||
|
return round(x), round(y)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.3 N개 노드 배치
|
||||||
|
|
||||||
|
```python
|
||||||
|
N = 3 # 노드 개수 (3, 4, 5, 6 모두 가능)
|
||||||
|
start_angle = 270 # 상단부터 시작
|
||||||
|
|
||||||
|
# 기본 간격: 360°/N
|
||||||
|
base_gap = 360 / N # 3개→120°, 4개→90°, 5개→72°
|
||||||
|
|
||||||
|
# 사이각 축소 (2/3): 양끝이 너무 벌어지는 것 방지
|
||||||
|
gap = base_gap * 2 / 3
|
||||||
|
|
||||||
|
# 각 노드 각도 계산 (상단 노드 고정, 나머지가 원 위에서 이동)
|
||||||
|
angles = []
|
||||||
|
for i in range(N):
|
||||||
|
if i == 0:
|
||||||
|
angles.append(start_angle) # 상단 고정
|
||||||
|
else:
|
||||||
|
# 상단 기준 좌/우 대칭 배치
|
||||||
|
# 홀수 인덱스: 좌측 (반시계)
|
||||||
|
# 짝수 인덱스: 우측 (시계)
|
||||||
|
if i % 2 == 1: # 좌측
|
||||||
|
step = (i + 1) // 2
|
||||||
|
angles.append(start_angle - gap * step)
|
||||||
|
else: # 우측
|
||||||
|
step = i // 2
|
||||||
|
angles.append(start_angle + gap * step)
|
||||||
|
|
||||||
|
# 각 노드의 2D 좌표
|
||||||
|
for i, angle in enumerate(angles):
|
||||||
|
x, y = project(angle)
|
||||||
|
print(f'Node {i}: angle={angle:.0f}°, pos=({x}, {y})')
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.4 N별 계산 예시
|
||||||
|
|
||||||
|
| N | 기본 간격 | 축소 간격 (2/3) | 노드 각도 |
|
||||||
|
|---|---------|--------------|---------|
|
||||||
|
| 3 | 120° | 80° | 270°, 190°, 350° |
|
||||||
|
| 4 | 90° | 60° | 270°, 210°, 330°, 150° |
|
||||||
|
| 5 | 72° | 48° | 270°, 222°, 318°, 174°, 366° |
|
||||||
|
| 6 | 60° | 40° | 270°, 230°, 310°, 190°, 350°, 150° |
|
||||||
|
|
||||||
|
### 10.5 화살표 >> 위치 계산
|
||||||
|
|
||||||
|
화살표는 **두 노드 사이 호의 1/3, 2/3 지점**에 배치. 방향은 **접선 방향**.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def arrow_positions(angle1, angle2):
|
||||||
|
"""두 노드 사이 호에 화살표 2개 배치"""
|
||||||
|
mid1 = angle1 + (angle2 - angle1) * 0.35
|
||||||
|
mid2 = angle1 + (angle2 - angle1) * 0.65
|
||||||
|
|
||||||
|
pos1 = project(mid1)
|
||||||
|
pos2 = project(mid2)
|
||||||
|
|
||||||
|
# 접선 방향 (화살표 회전각)
|
||||||
|
def tangent_angle(alpha):
|
||||||
|
a = math.radians(alpha)
|
||||||
|
tx = -math.sin(a)
|
||||||
|
ty = math.cos(a) * math.cos(tilt)
|
||||||
|
return math.degrees(math.atan2(ty, tx))
|
||||||
|
|
||||||
|
rot1 = tangent_angle(mid1)
|
||||||
|
rot2 = tangent_angle(mid2)
|
||||||
|
|
||||||
|
return (pos1, rot1), (pos2, rot2)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.6 설명 텍스트 배치 규칙
|
||||||
|
|
||||||
|
**Figma 원본 분석 결과**: 설명은 원 바깥 방향(Q꼬리)이 아니라, **노드 이름 옆에 수평으로** 배치.
|
||||||
|
|
||||||
|
```
|
||||||
|
규칙:
|
||||||
|
원 중심 기준 좌측 노드 (angle > 90° and < 270°):
|
||||||
|
→ 설명이 노드 이름의 좌측에 (text-anchor: end)
|
||||||
|
|
||||||
|
원 중심 기준 우측 또는 상단 노드 (나머지):
|
||||||
|
→ 설명이 노드 이름의 우측에 (text-anchor: start)
|
||||||
|
|
||||||
|
텍스트 구조:
|
||||||
|
[설명 제목] ← 이름과 같은 Y선, 옆에 수평
|
||||||
|
[노드 아이콘 원] • 불릿 1 ← 제목 아래 들여쓰기
|
||||||
|
[라벨] (원 아래) • 불릿 2
|
||||||
|
[서브라벨] (라벨 아래)
|
||||||
|
```
|
||||||
|
|
||||||
|
**실제 배치 (3노드 예시)**:
|
||||||
|
```
|
||||||
|
👥 사람(역량) 혁신적 사고방식 ← 우측
|
||||||
|
• 창의적 문제 해결
|
||||||
|
• 사용자 중심 접근
|
||||||
|
|
||||||
|
Digital 기술과... 🖥 기술(디지털) 자연(여건) 📋 지속적 투자 의지 ← 우측
|
||||||
|
• 건설 전문 지식 ↑ • 실행 추진력
|
||||||
|
• 최신 기술 좌측 • 변화를 통한 가치 창출
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
def desc_position(node_x, node_y, node_angle, cx):
|
||||||
|
"""설명 텍스트 위치 계산"""
|
||||||
|
if node_x < cx: # 왼쪽 노드
|
||||||
|
desc_x = node_x - 36 # 노드 좌측
|
||||||
|
anchor = 'end'
|
||||||
|
else: # 오른쪽 또는 상단 노드
|
||||||
|
desc_x = node_x + 36 # 노드 우측
|
||||||
|
anchor = 'start'
|
||||||
|
|
||||||
|
desc_y = node_y + 37 # 라벨과 같은 높이 (아이콘 아래)
|
||||||
|
return desc_x, desc_y, anchor
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.7 SVG 구조
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<svg viewBox="0 0 1000 380">
|
||||||
|
<!-- 1. 타원 궤도 -->
|
||||||
|
<ellipse cx="500" cy="200" rx="400" ry="69"/>
|
||||||
|
|
||||||
|
<!-- 2. 화살표 >> (N개 구간 × 2개씩) -->
|
||||||
|
<text transform="rotate(각도)">»</text>
|
||||||
|
|
||||||
|
<!-- 3. 노드 (원 + 아이콘 + 라벨) -->
|
||||||
|
<circle cx="x" cy="y" r="26"/>
|
||||||
|
<text>아이콘</text>
|
||||||
|
<text>라벨</text>
|
||||||
|
|
||||||
|
<!-- 4. 설명 텍스트 -->
|
||||||
|
<text>설명 제목</text>
|
||||||
|
<text>• 불릿</text>
|
||||||
|
</svg>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.8 중요: 파이프라인에서 좌표 계산
|
||||||
|
|
||||||
|
이 블록은 **Jinja2 템플릿에 좌표를 하드코딩하면 안 됨**.
|
||||||
|
파이프라인(Python)에서 N, R, θ를 받아 좌표를 계산한 뒤 템플릿에 전달해야 함.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# pipeline에서 호출
|
||||||
|
def calculate_orbit(n_nodes, radius=400, tilt_deg=80):
|
||||||
|
"""N개 노드의 SVG 좌표와 화살표 위치를 계산"""
|
||||||
|
cx, cy = 500, 200
|
||||||
|
tilt = math.radians(tilt_deg)
|
||||||
|
gap = (360 / n_nodes) * 2 / 3
|
||||||
|
|
||||||
|
nodes = []
|
||||||
|
arrows = []
|
||||||
|
# ... 위 공식 적용
|
||||||
|
|
||||||
|
return {
|
||||||
|
'ellipse': {'cx': cx, 'cy': cy, 'rx': radius, 'ry': round(radius * math.cos(tilt))},
|
||||||
|
'nodes': nodes, # [{x, y, angle}, ...]
|
||||||
|
'arrows': arrows, # [{x, y, rotation}, ...]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 실수 방지 (Anti-patterns)
|
||||||
|
|
||||||
|
### 11.1 절대 하면 안 되는 것
|
||||||
|
|
||||||
|
| Anti-pattern | 왜 안 되는지 | 올바른 방법 |
|
||||||
|
|-------------|------------|-----------|
|
||||||
|
| px 시행착오 조정 ("좀 더 올려볼게") | 3번 이상 실패, 시간 낭비 | Figma 좌표에서 수학적 계산 |
|
||||||
|
| 3D 효과를 CSS로 재현 | 평면적이라 품질 차이 심각 | Figma에서 PNG 추출 |
|
||||||
|
| 비교 리뷰를 좌/우 배치 | 크기 차이로 비교 불가 | 위/아래 같은 폭으로 배치 |
|
||||||
|
| Jinja2 템플릿을 브라우저에서 직접 열기 | 변수 미렌더, 이미지 경로 깨짐 | comparison.html 또는 FastAPI로 확인 |
|
||||||
|
| 독립 flex-column으로 2열 비교 | 행 정렬 안 됨 | CSS Grid 행 공유 |
|
||||||
|
| 느낌으로 폰트/색상 설정 | Figma와 다른 결과물 | Figma API에서 정확한 값 추출 |
|
||||||
|
|
||||||
|
### 11.2 반드시 해야 하는 것
|
||||||
|
|
||||||
|
| 원칙 | 이유 |
|
||||||
|
|------|------|
|
||||||
|
| CSS 주석에 계산 근거 기록 | 나중에 왜 이 값인지 추적 가능 |
|
||||||
|
| 비교 리뷰 후 진행 | 디자인 차이를 사전에 발견 |
|
||||||
|
| 이미지 자산은 `static/figma-assets/`에 저장 | FastAPI가 서빙, 경로 일관성 |
|
||||||
|
| `comparison.html`에 모든 프레임 포함 | 한 페이지에서 전체 리뷰 가능 |
|
||||||
|
| Figma 노드 ID 기록 | 나중에 업데이트된 디자인 재추출 가능 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Figma 소스 정보
|
||||||
|
|
||||||
|
### 현재 등록된 Figma 파일
|
||||||
|
|
||||||
|
| 항목 | 값 |
|
||||||
|
|------|---|
|
||||||
|
| File Key | `9S6LsQyO6zlRxtiqZccOUM` |
|
||||||
|
|
||||||
|
**Page 1 (0:1)** — 기본 디자인
|
||||||
|
| 블록 | Node ID | 설명 |
|
||||||
|
|------|---------|------|
|
||||||
|
| hero-icon-cards | `1:5` | Frame 2 (Solution 제작 목표) |
|
||||||
|
| compare-2col-badge | `1:35` | Frame 3 (정책 달성) |
|
||||||
|
| compare-detail-gradient | `1:49` | Frame 4 (과정 vs 결과 혁신) |
|
||||||
|
| Badge 빨간 리본 | `1:33` | image 4019 |
|
||||||
|
| Badge 틸 리본 | `1:43` | image 2197 |
|
||||||
|
| Arrow As-Is→To-Be | `1:67` | image 2645 |
|
||||||
|
| Box 빨간 테두리 | `1:12` | Rectangle 42894 |
|
||||||
|
| Box 틸 테두리 | `1:37` | Rectangle 42598 |
|
||||||
|
|
||||||
|
**Page 2 (15:2)** — 프레젠테이션 슬라이드
|
||||||
|
| 블록 | Node ID | 설명 |
|
||||||
|
|------|---------|------|
|
||||||
|
| category-strip-table | `17:1264` | 001_개요 우측 하단 (필수조건 3열) |
|
||||||
|
|
||||||
|
**Page 3 (18:8204)** — 컴포넌트
|
||||||
|
| 블록 | Node ID | 설명 |
|
||||||
|
|------|---------|------|
|
||||||
|
| checklist-dark | `18:8351` | f5 (체크리스트 6행) |
|
||||||
|
| system-2col-center | `18:8405` | f8 (System 구성 H/W vs S/W) |
|
||||||
|
|
||||||
|
**Page 4 (29:439)** — 순환 다이어그램
|
||||||
|
| 블록 | Node ID | 설명 |
|
||||||
|
|------|---------|------|
|
||||||
|
| cycle-orbit | `29:439` | DX 시행 필수 요건 (3노드 순환) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. 체크리스트
|
||||||
|
|
||||||
|
새 Figma 프레임을 블록으로 변환할 때:
|
||||||
|
|
||||||
|
- [ ] Figma API로 노드 데이터 추출 (좌표, 크기, 색상, 폰트)
|
||||||
|
- [ ] PNG 렌더링 다운로드 (scale=2)
|
||||||
|
- [ ] 복잡한 비주얼 요소 식별 → 이미지로 추출 (CSS로 만들지 않음)
|
||||||
|
- [ ] 스케일 팩터 계산 (1200 / frame_width)
|
||||||
|
- [ ] 핵심 정렬 포인트 수학적 계산 (좌표 차이 × 스케일)
|
||||||
|
- [ ] CSS 값 도출 (계산 근거를 주석으로 기록)
|
||||||
|
- [ ] 비교 리뷰 페이지에 추가 (위/아래 같은 폭)
|
||||||
|
- [ ] 사용자 피드백 확인
|
||||||
|
- [ ] Jinja2 템플릿 변환 (고정값→변수, 반복→루프)
|
||||||
|
- [ ] catalog.yaml 등록
|
||||||
97
figma_to_html_agent/INSIGHT-GRADIENT.md
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
# Figma 도형 + 그라데이션 처리
|
||||||
|
|
||||||
|
## 핵심 원리
|
||||||
|
|
||||||
|
Figma에서 도형 작업 방식:
|
||||||
|
1. 박스(프레임/컨테이너) 안에 도형을 만든다 (border-radius + gradient)
|
||||||
|
2. **박스를 회전**시킨다 (도형 자체가 아니라)
|
||||||
|
3. 박스가 돌면 그 안의 도형도 같이 돌아간다 → border-radius와 gradient가 함께 회전
|
||||||
|
|
||||||
|
## CSS 구현 구조
|
||||||
|
|
||||||
|
**도형 자체에 transform 적용 금지. 반드시 래퍼(wrapper) 컨테이너에 적용한다.**
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="wrapper"> <!-- 이게 회전한다 -->
|
||||||
|
<div class="shape"></div> <!-- 이건 건드리지 않는다 -->
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
.wrapper {
|
||||||
|
position: absolute;
|
||||||
|
/* pre-rotation position/size */
|
||||||
|
transform: rotate(X deg); /* Figma gradient 각도의 부호 반대 */
|
||||||
|
}
|
||||||
|
.shape {
|
||||||
|
width: 100%; height: 100%;
|
||||||
|
border-radius: <Figma 값 그대로>;
|
||||||
|
background: linear-gradient(90deg, <Figma 색상 그대로>);
|
||||||
|
/* CSS 90deg = Figma 0deg = 왼→오, 이것이 "기본 상태" */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 프로세스
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Figma gradient 각도 확인 (예: 90°)
|
||||||
|
2. 기본 상태(Figma 0°) 정의
|
||||||
|
- shape: border-radius = Figma 값, gradient = CSS 90deg (left→right) + Figma 색상
|
||||||
|
3. 래퍼의 pre-rotation 위치/크기 계산
|
||||||
|
- 90° 회전이면 width/height 교환
|
||||||
|
- 래퍼 중심 = 최종 중심 (Figma position + size/2)
|
||||||
|
4. 래퍼에 transform: rotate() 적용
|
||||||
|
- Figma +90° → CSS rotate(-90deg) (부호 반대, CSS는 CW 기준)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 예시: 42335
|
||||||
|
|
||||||
|
Figma 데이터:
|
||||||
|
- 위치: (574, 45), 크기: 205×424 (tall, 최종 상태)
|
||||||
|
- border-radius: 102 0 0 102 (왼쪽 둥근, 기본 상태 기준)
|
||||||
|
- gradient: 90deg (시계 반대 방향 90도 회전됨)
|
||||||
|
|
||||||
|
Pre-rotation 계산:
|
||||||
|
- 최종 중심: (676.5, 257)
|
||||||
|
- Pre-rotation 크기: 424×205 (swap)
|
||||||
|
- Pre-rotation top-left: (464.5, 154.5)
|
||||||
|
|
||||||
|
CSS:
|
||||||
|
```html
|
||||||
|
<div class="wrapper-42335">
|
||||||
|
<div class="shape-42335"></div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
```css
|
||||||
|
.wrapper-42335 {
|
||||||
|
position: absolute;
|
||||||
|
left: 464.5px; top: 154.5px;
|
||||||
|
width: 424px; height: 205px;
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
.shape-42335 {
|
||||||
|
width: 100%; height: 100%;
|
||||||
|
border-radius: 102px 0 0 102px;
|
||||||
|
background: linear-gradient(90deg, rgba(217,162,104,1) 37%, rgba(220,103,14,0) 89%);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Figma gradient 각도 → CSS transform 변환
|
||||||
|
|
||||||
|
Figma는 시계방향이 양수, CSS transform은 시계방향이 양수이지만:
|
||||||
|
- Figma gradient 각도는 **도형 내부 방향** 기준
|
||||||
|
- 박스를 회전시키는 관점에서는 부호가 반대
|
||||||
|
|
||||||
|
```
|
||||||
|
Figma gradient 0° → CSS transform: rotate(0deg) (회전 없음)
|
||||||
|
Figma gradient +90° → CSS transform: rotate(-90deg) (반시계)
|
||||||
|
Figma gradient -90° → CSS transform: rotate(+90deg) (시계)
|
||||||
|
Figma gradient ±180° → CSS transform: rotate(180deg)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 주의사항
|
||||||
|
|
||||||
|
- Figma 데이터가 유일한 소스. PNG는 픽셀 분석으로만 교차 검증.
|
||||||
|
- 이미지를 눈으로 보고 방향 판단 금지 (멀티모달 해석 불안정).
|
||||||
|
- border-radius와 gradient를 각각 수동 계산하지 않는다. **래퍼를 회전**시킨다.
|
||||||
|
- 작동하는 값은 건드리지 않는다. 사용자가 지적한 것만 수정한다.
|
||||||
362
figma_to_html_agent/MATH.md
Normal file
@@ -0,0 +1,362 @@
|
|||||||
|
# 수학 공식 레퍼런스
|
||||||
|
|
||||||
|
Figma → HTML 변환에서 사용하는 모든 수학 공식. 이 문서의 공식만 사용하고, 직관/감으로 보정하지 않는다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §1. 스케일 팩터
|
||||||
|
|
||||||
|
### 정의
|
||||||
|
|
||||||
|
```
|
||||||
|
S = 1280 / W_원본_프레임
|
||||||
|
```
|
||||||
|
|
||||||
|
`1280`은 16:9 슬라이드 가로 폭. 모든 프레임은 가로 1280에 맞춰 축소된다.
|
||||||
|
|
||||||
|
### 적용 방법: CSS transform scale (권장)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="block">
|
||||||
|
<div class="inner"> <!-- 원본 W × H 좌표계 그대로 -->
|
||||||
|
... 모든 요소 (Figma 원본 px) ...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
.block {
|
||||||
|
width: 1280px;
|
||||||
|
height: {H × S}px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.inner {
|
||||||
|
position: absolute;
|
||||||
|
left: 0; top: 0;
|
||||||
|
width: {W}px; /* 원본 그대로 */
|
||||||
|
height: {H}px;
|
||||||
|
transform: scale({S});
|
||||||
|
transform-origin: top left;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**왜 transform이 좋은가:**
|
||||||
|
- 위치/크기/폰트/그림자/스트로크/blur radius 모두 한 번에 균일 축소
|
||||||
|
- 매 값 수동 곱셈하면 누적 오차 + 검증 어려움
|
||||||
|
- transform은 GPU 가속, 계산 정확
|
||||||
|
|
||||||
|
### 적용 대상
|
||||||
|
|
||||||
|
| 적용 | 미적용 |
|
||||||
|
|------|------|
|
||||||
|
| 위치 (x, y) | 색상 |
|
||||||
|
| 크기 (width, height) | 그라데이션 방향 (각도 그대로) |
|
||||||
|
| 폰트 크기 | 그라데이션 stop 퍼센트 (그대로) |
|
||||||
|
| 스트로크 너비 | 폰트 굵기 |
|
||||||
|
| 간격 (gap, padding) | line-height 비율 (1.5 등) |
|
||||||
|
| 그림자 (blur, offset) | border-radius 비율 (50% 등) |
|
||||||
|
| border-radius (px) | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §2. SVG `<linearGradient>` → CSS `linear-gradient()`
|
||||||
|
|
||||||
|
### 입력
|
||||||
|
|
||||||
|
SVG에서:
|
||||||
|
```xml
|
||||||
|
<linearGradient id="..." gradientUnits="userSpaceOnUse"
|
||||||
|
x1="..." y1="..." x2="..." y2="...">
|
||||||
|
<stop offset="0" stop-color="..."/>
|
||||||
|
<stop offset="1" stop-color="..."/>
|
||||||
|
</linearGradient>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 변환 공식
|
||||||
|
|
||||||
|
```
|
||||||
|
1. dx = x2 - x1
|
||||||
|
dy = y2 - y1
|
||||||
|
L_svg = √(dx² + dy²)
|
||||||
|
|
||||||
|
2. SVG 벡터 각도 (y-down 좌표계, 0°=오른쪽, +CW):
|
||||||
|
svg_angle = atan2(dy, dx) (단위: 라디안)
|
||||||
|
|
||||||
|
3. CSS 각도 (12시 방향=0°, +CW):
|
||||||
|
css_angle = degrees(svg_angle) + 90
|
||||||
|
css_angle = css_angle mod 360
|
||||||
|
|
||||||
|
4. CSS 그라데이션 선 길이 (W×H 박스 안):
|
||||||
|
α = radians(css_angle)
|
||||||
|
L_css = |W × sin(α)| + |H × cos(α)|
|
||||||
|
|
||||||
|
5. 박스 중심의 t 파라미터 (SVG 벡터 위, 0=시작, 1=끝):
|
||||||
|
t_center = ((W/2 - x1)·dx + (H/2 - y1)·dy) / L_svg²
|
||||||
|
|
||||||
|
6. CSS 0% / 100%가 SVG t-space의 어디에 매핑되는지:
|
||||||
|
half = (L_css / 2) / L_svg
|
||||||
|
t0 = t_center - half ← CSS 0%
|
||||||
|
t1 = t_center + half ← CSS 100%
|
||||||
|
|
||||||
|
7. SVG 각 stop offset (0~1)을 CSS percent로:
|
||||||
|
pct = (offset - t0) / (t1 - t0) × 100
|
||||||
|
```
|
||||||
|
|
||||||
|
### 예시
|
||||||
|
|
||||||
|
SVG:
|
||||||
|
```xml
|
||||||
|
<linearGradient x1="110.833" y1="18.2292" x2="219.479" y2="175"
|
||||||
|
gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#FDC69E"/>
|
||||||
|
<stop offset="1" stop-color="#E0782C"/>
|
||||||
|
</linearGradient>
|
||||||
|
```
|
||||||
|
|
||||||
|
박스 W=H=350일 때:
|
||||||
|
|
||||||
|
```
|
||||||
|
dx = 108.65, dy = 156.77
|
||||||
|
L_svg = √(108.65² + 156.77²) = 190.74
|
||||||
|
svg_angle = atan2(156.77, 108.65) = 0.9646 rad = 55.27°
|
||||||
|
css_angle = 55.27 + 90 = 145.27°
|
||||||
|
α = 2.535 rad
|
||||||
|
L_css = 350 × |sin 145.27°| + 350 × |cos 145.27°|
|
||||||
|
= 350 × 0.5696 + 350 × 0.8220
|
||||||
|
= 487.06
|
||||||
|
t_center = ((175 - 110.833)·108.65 + (175 - 18.229)·156.77) / 190.74²
|
||||||
|
= (6971.7 + 24577.3) / 36382
|
||||||
|
= 0.8672
|
||||||
|
half = (487.06 / 2) / 190.74 = 1.2767
|
||||||
|
t0 = 0.8672 - 1.2767 = -0.4095
|
||||||
|
t1 = 0.8672 + 1.2767 = 2.1439
|
||||||
|
SVG offset 0 → pct = (0 - (-0.4095)) / 2.5534 × 100 = 16.04%
|
||||||
|
SVG offset 1 → pct = (1 - (-0.4095)) / 2.5534 × 100 = 55.20%
|
||||||
|
```
|
||||||
|
|
||||||
|
CSS:
|
||||||
|
```css
|
||||||
|
background: linear-gradient(145.27deg, #FDC69E 16.04%, #E0782C 55.20%);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 코드: `scripts/gradient_math.py`
|
||||||
|
|
||||||
|
```python
|
||||||
|
from scripts.gradient_math import svg_to_css
|
||||||
|
|
||||||
|
svg_to_css(W=350, H=350,
|
||||||
|
x1=110.833, y1=18.2292, x2=219.479, y2=175,
|
||||||
|
stops=[(0, '#FDC69E'), (1, '#E0782C')])
|
||||||
|
# → "linear-gradient(145.27deg, #FDC69E 16.04%, #E0782C 55.20%)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §3. 회전 감지 (bbox 비율 검사)
|
||||||
|
|
||||||
|
Figma MCP는 `rotation` 속성을 출력하지 않으므로 bbox 비율로 추론:
|
||||||
|
|
||||||
|
```
|
||||||
|
단일 문자 텍스트:
|
||||||
|
width > height × 1.5 → 90° 회전 (가로로 누움)
|
||||||
|
|
||||||
|
일반 텍스트:
|
||||||
|
width < fontSize × 0.8 → 좁은 박스 세로 배치 (writing-mode 아님, <br>로 줄바꿈)
|
||||||
|
```
|
||||||
|
|
||||||
|
CSS 적용:
|
||||||
|
```css
|
||||||
|
.rotated {
|
||||||
|
transform: rotate(90deg); /* 또는 -90deg */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §4. Descender 보정 (padding-bottom)
|
||||||
|
|
||||||
|
CSS `line-height: 1`이거나 `< font_content_area_ratio`이면 글리프 하강부(g, y, p, 쉼표)가 잘림.
|
||||||
|
|
||||||
|
### 폰트별 메트릭
|
||||||
|
|
||||||
|
| 폰트 | UPM | typoAscender | typoDescender | content_area_ratio |
|
||||||
|
|------|-----|------|------|------|
|
||||||
|
| Noto Sans KR | 1000 | 1160 | 288 | 1.448 |
|
||||||
|
| Pretendard | 1000 | 1100 | 300 | 1.400 |
|
||||||
|
|
||||||
|
### 공식
|
||||||
|
|
||||||
|
```
|
||||||
|
content_area_ratio = (typoAscender + |typoDescender|) / UPM
|
||||||
|
half_leading = (line_height_ratio - content_area_ratio) / 2
|
||||||
|
↑ 음수면 잘림 발생
|
||||||
|
clipped_px = |half_leading| × font_size
|
||||||
|
padding-bottom = ceil(clipped_px)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 예시 (Noto Sans KR, font 27.1px, lh 1)
|
||||||
|
|
||||||
|
```
|
||||||
|
half_leading = (1.0 - 1.448) / 2 = -0.224
|
||||||
|
clipped = 0.224 × 27.1 = 6.07 px
|
||||||
|
→ padding-bottom: 7px
|
||||||
|
```
|
||||||
|
|
||||||
|
### 예시 (Noto Sans KR, font 30px, lh 35px → ratio 1.167)
|
||||||
|
|
||||||
|
```
|
||||||
|
half_leading = (1.167 - 1.448) / 2 = -0.1405
|
||||||
|
clipped = 0.1405 × 30 = 4.215 px
|
||||||
|
→ padding-bottom: 5px
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §5. SVG viewBox padding → CSS box-sizing 매핑
|
||||||
|
|
||||||
|
SVG가 drop-shadow blur 여백을 위해 viewBox를 확장해놓은 경우 (예: 280×280 fill을 310 viewBox에 넣음):
|
||||||
|
|
||||||
|
### 케이스 A — Stroke가 fill 외부 (안전과 품질 ring 같은 케이스)
|
||||||
|
|
||||||
|
```
|
||||||
|
SVG: viewBox 310, fill r=140 (d=280), stroke r=142.5 width=5 (extends r=140 to 145)
|
||||||
|
visible: 290×290 (fill 280 + 5px stroke 외부 확장)
|
||||||
|
viewBox padding: 310 - 290 = 20 (각 변 10이 drop-shadow blur 패딩, 추가 5는 stroke)
|
||||||
|
|
||||||
|
CSS:
|
||||||
|
div W=H=290
|
||||||
|
border: 5px solid white
|
||||||
|
box-sizing: border-box
|
||||||
|
→ border-box 290, padding-box 280 ← fill 영역
|
||||||
|
position: Figma fill 위치 - (5, 5) ← stroke 외부 확장 보정
|
||||||
|
```
|
||||||
|
|
||||||
|
### 케이스 B — Stroke가 fill 내부 (생산성/소통 ring 같은 케이스)
|
||||||
|
|
||||||
|
```
|
||||||
|
SVG: viewBox 300, fill r=140, stroke r=137.5 width=5 (extends r=135 to 140 — fill 외곽 5px overlap)
|
||||||
|
visible: 280×280 (stroke가 fill 외곽 5px를 덮음)
|
||||||
|
viewBox padding: 300 - 280 = 20 (전부 drop-shadow blur)
|
||||||
|
|
||||||
|
CSS:
|
||||||
|
div W=H=280
|
||||||
|
border: 5px solid white
|
||||||
|
box-sizing: border-box
|
||||||
|
background-origin: border-box ← gradient를 border-box 280에 매핑
|
||||||
|
background-clip: border-box
|
||||||
|
→ border 5가 외곽 fill을 덮어 그라데이션 가시 영역은 270
|
||||||
|
position: Figma 위치 그대로 (offset 없음)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 그라데이션 좌표 remap
|
||||||
|
|
||||||
|
SVG `<linearGradient>` 좌표는 viewBox 공간 기준. CSS box로 매핑할 때:
|
||||||
|
|
||||||
|
```
|
||||||
|
viewBox padding이 P (예: 15 또는 10)이라면:
|
||||||
|
CSS_x = SVG_x - P
|
||||||
|
CSS_y = SVG_y - P
|
||||||
|
```
|
||||||
|
|
||||||
|
이렇게 보정한 좌표를 §2의 svg_to_css 공식에 W=H=fill_size로 넣는다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §6. Drop shadow: SVG `feGaussianBlur` ↔ CSS `box-shadow`
|
||||||
|
|
||||||
|
SVG:
|
||||||
|
```xml
|
||||||
|
<filter>
|
||||||
|
<feGaussianBlur stdDeviation="5"/>
|
||||||
|
<feColorMatrix .../>
|
||||||
|
</filter>
|
||||||
|
```
|
||||||
|
|
||||||
|
CSS 근사:
|
||||||
|
```css
|
||||||
|
box-shadow: 0 0 {2 × stdDeviation}px {color};
|
||||||
|
```
|
||||||
|
|
||||||
|
`stdDeviation=5` → CSS `box-shadow: 0 0 10px black`
|
||||||
|
|
||||||
|
**주의:** 정확한 픽셀 일치는 아님. 시각적으로 매우 유사하지만 SVG 가우시안과 CSS 블러 알고리즘이 다름. ±2px 차이는 허용.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §7. Blend mode 호환
|
||||||
|
|
||||||
|
### Figma가 사용하는 blend mode → CSS 호환 매핑
|
||||||
|
|
||||||
|
| Figma | CSS 정확 | CSS 호환 (Chrome/Firefox) | 비고 |
|
||||||
|
|-------|---------|----------------------|------|
|
||||||
|
| Normal | normal | normal | 기본 |
|
||||||
|
| Multiply | multiply | multiply | OK |
|
||||||
|
| **Plus darker** | plus-darker | **multiply** | plus-darker는 Safari 전용 |
|
||||||
|
| Darken | darken | darken | OK |
|
||||||
|
| Screen | screen | screen | OK |
|
||||||
|
| Overlay | overlay | overlay | OK |
|
||||||
|
|
||||||
|
### Plus-darker vs Multiply 차이
|
||||||
|
|
||||||
|
```
|
||||||
|
plus-darker(src, dst) = max(0, src + dst - 1)
|
||||||
|
multiply(src, dst) = src × dst
|
||||||
|
```
|
||||||
|
|
||||||
|
- 흰 배경: 둘 다 동일 (효과 없음)
|
||||||
|
- 어두운 배경: multiply가 plus-darker보다 강하게 어두워짐
|
||||||
|
- 밝은 그라데이션 + 흰 배경 조합: 시각적 차이 거의 없음 (이 프로젝트 디자인 대부분 해당)
|
||||||
|
|
||||||
|
→ **Chrome/Firefox 호환 위해 multiply로 통일.** RULES.md R10 참조.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §8. CSS `border-radius` 비율 변환
|
||||||
|
|
||||||
|
Figma `cornerRadius`는 px 단위. CSS도 px 단위 그대로 사용 + scale 적용.
|
||||||
|
|
||||||
|
특수 케이스:
|
||||||
|
- 완전 원: `border-radius: 50%`
|
||||||
|
- 캡슐: `border-radius: {height/2}px`
|
||||||
|
- 한쪽만 둥근 사각: `border-radius: {tl} {tr} {br} {bl}` (개별 4값)
|
||||||
|
|
||||||
|
스케일링 시: scale transform이 자동으로 px 값을 비율 유지하며 축소함. 별도 계산 불필요.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §9. 글자 수 추정 (블록 안에 들어갈 텍스트 양)
|
||||||
|
|
||||||
|
블록 너비/높이에서 들어갈 수 있는 한글 글자 수를 미리 계산:
|
||||||
|
|
||||||
|
```
|
||||||
|
한 줄 글자 수 = 블록 너비(px) / (font_size × 한글_글자_너비_계수)
|
||||||
|
줄 수 = 블록 높이(px) / (font_size × line_height_ratio)
|
||||||
|
총 글자 수 = 한 줄 × 줄 수 × 안전계수(0.85)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pretendard / Noto Sans KR 한글 글자 너비 계수 = 0.97
|
||||||
|
|
||||||
|
| font-size | 한글 글자 너비 | line_height 1.6 줄 높이 |
|
||||||
|
|-----------|-------------|---------------------|
|
||||||
|
| 12px | 11.6px | 19.2px |
|
||||||
|
| 16px | 15.5px | 25.6px |
|
||||||
|
| 20px | 19.4px | 32.0px |
|
||||||
|
| 24px | 23.3px | 38.4px |
|
||||||
|
| 30px | 29.1px | 48.0px |
|
||||||
|
|
||||||
|
이는 design_agent 텍스트 편집 단계에서 사용. 변환 단계에서는 직접 사용하지 않음.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 검증 체크리스트
|
||||||
|
|
||||||
|
변환 후 매번 확인:
|
||||||
|
|
||||||
|
- [ ] §1 스케일 — `transform: scale(S)` 한 번만 사용했는가, 매 값 수동 곱셈은 없는가
|
||||||
|
- [ ] §2 그라데이션 — gradient_math.py로 도출한 값을 그대로 사용했는가, 눈대중 각도/stop은 없는가
|
||||||
|
- [ ] §3 회전 — bbox 비율로 회전 감지했는가
|
||||||
|
- [ ] §4 descender — `line_height < content_area_ratio`인 텍스트에 padding-bottom 추가했는가
|
||||||
|
- [ ] §5 viewBox — stroke 정렬 확인 (외부/내부)에 따라 box-sizing 적용했는가
|
||||||
|
- [ ] §6 shadow — `box-shadow blur = 2 × stdDeviation`인가
|
||||||
|
- [ ] §7 blend — `plus-darker`를 `multiply`로 교체했는가
|
||||||
478
figma_to_html_agent/PHASE-FIGMA-BLOCKS.md
Normal file
@@ -0,0 +1,478 @@
|
|||||||
|
# Phase 2: Figma Block Design Specification
|
||||||
|
|
||||||
|
> 7개 블록 + 2개 서브 컴포넌트 상세 설계
|
||||||
|
> 기준: FIGMA-DESIGN-LANGUAGE.md 분석 결과
|
||||||
|
> 최종 업데이트: 2026-04-08
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Block 1: `hero-icon-cards`
|
||||||
|
|
||||||
|
### 1.1 시각적 구조
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────┐
|
||||||
|
│ [Hero Statement - 큰 텍스트, 중앙] │ ← zone: header or full-width
|
||||||
|
│ │
|
||||||
|
│ ┌─[Badge Title]─┐ │
|
||||||
|
│──────────┤ ├───────────────────│
|
||||||
|
│ ┌─────┐ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
|
||||||
|
│ │icon │ │ │icon │ │icon │ │icon │ │icon │ │ ← N개 카드 (2~6)
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ │ │
|
||||||
|
│ │Title│ ╎ │Title│ │Title│ │Title│ │Title│ │ ← 세로 구분선
|
||||||
|
│ │(sub)│ │ │(sub)│ │(sub)│ │(sub)│ │(sub)│ │
|
||||||
|
│ └─────┘ │ └─────┘ └─────┘ └─────┘ └─────┘ │
|
||||||
|
│ └───────────────────────────────────│
|
||||||
|
└──────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 슬롯 정의
|
||||||
|
|
||||||
|
| 슬롯 | 필수 | 타입 | 설명 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `statement` | O | string | Hero 메시지 (1-2줄) |
|
||||||
|
| `badge_title` | X | string | 배지 바 텍스트 |
|
||||||
|
| `cards[]` | O | array | 카드 배열 |
|
||||||
|
| `cards[].icon` | X | string | 아이콘 이미지 URL 또는 이모지 |
|
||||||
|
| `cards[].title` | O | string | 영문 또는 주제목 |
|
||||||
|
| `cards[].subtitle` | X | string | 한국어 부제 |
|
||||||
|
| `cards[].color` | X | string | 개별 카드 강조색 |
|
||||||
|
|
||||||
|
### 1.3 동적 재구성 규칙
|
||||||
|
|
||||||
|
#### 그리드 계산
|
||||||
|
```
|
||||||
|
입력: N = cards.length, W = container_width_px
|
||||||
|
|
||||||
|
N ≤ 5: 1행 N열
|
||||||
|
col_count = N
|
||||||
|
card_width = (W - padding*2 - gap*(N-1)) / N
|
||||||
|
|
||||||
|
N = 6: 1행 6열 (gap 축소)
|
||||||
|
col_count = 6
|
||||||
|
gap = 8px (기본 16px에서 축소)
|
||||||
|
|
||||||
|
N > 6: 2행
|
||||||
|
col_count = ceil(N / 2)
|
||||||
|
row_count = 2
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 폰트 스케일링
|
||||||
|
```
|
||||||
|
card_width ≥ 200px → title: 20px, subtitle: 14px
|
||||||
|
card_width ≥ 150px → title: 16px, subtitle: 12px
|
||||||
|
card_width < 150px → title: 14px, subtitle: 11px
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 높이 계산
|
||||||
|
```
|
||||||
|
hero_height = statement_lines * line_height + padding
|
||||||
|
badge_height = 44px (고정)
|
||||||
|
card_area_height = icon_height + title_lines * title_lh + subtitle_lh + padding
|
||||||
|
- 1행: card_area_height
|
||||||
|
- 2행: card_area_height * 2 + gap
|
||||||
|
|
||||||
|
total_min_height = hero_height + badge_height + card_area_height + gaps
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.4 catalog.yaml schema
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- id: hero-icon-cards
|
||||||
|
name: 히어로 문구 + 아이콘 카드
|
||||||
|
category: cards
|
||||||
|
template: blocks/cards/hero-icon-cards.html
|
||||||
|
height_cost: xlarge
|
||||||
|
min_height_px: 280
|
||||||
|
relation_types: [definition, flow]
|
||||||
|
min_items: 2
|
||||||
|
max_items: 6
|
||||||
|
visual: >
|
||||||
|
상단에 큰 Hero 메시지(24px bold, 중앙) + 배지 바 +
|
||||||
|
하단에 N열 아이콘 카드(둥근 흰색 컨테이너, 세로 구분선).
|
||||||
|
각 카드는 아이콘 이미지 + 영문 제목(20px/900) + 한국어 부제(14px/500).
|
||||||
|
when: >
|
||||||
|
핵심 목표나 가치를 N개 키워드로 선언할 때.
|
||||||
|
각 키워드에 아이콘이나 이미지가 있을 때.
|
||||||
|
"우리가 추구하는 5가지 가치" 같은 구조.
|
||||||
|
not_for: >
|
||||||
|
비교/대조 구조 → compare-2col-badge.
|
||||||
|
상세 설명이 길 때 → card-icon-desc.
|
||||||
|
순서/단계 → card-step-vertical 또는 process-horizontal.
|
||||||
|
purpose_fit: [핵심전달, 가치선언]
|
||||||
|
zone: full-width-only
|
||||||
|
slots:
|
||||||
|
required: [statement, cards[]]
|
||||||
|
optional: [badge_title, cards[].icon, cards[].subtitle, cards[].color]
|
||||||
|
schema:
|
||||||
|
statement:
|
||||||
|
max_lines: 2
|
||||||
|
font_size: 24
|
||||||
|
ref_chars:
|
||||||
|
body: 60
|
||||||
|
note: "24px bold, 중앙정렬, 흰색 스트로크"
|
||||||
|
badge_title:
|
||||||
|
max_lines: 1
|
||||||
|
font_size: 18
|
||||||
|
ref_chars:
|
||||||
|
body: 20
|
||||||
|
note: "18px bold white, 배지 바 위"
|
||||||
|
card_title:
|
||||||
|
max_lines: 2
|
||||||
|
font_size: 20
|
||||||
|
ref_chars:
|
||||||
|
body: 15
|
||||||
|
note: "20px black/900, 중앙정렬"
|
||||||
|
card_subtitle:
|
||||||
|
max_lines: 1
|
||||||
|
font_size: 14
|
||||||
|
ref_chars:
|
||||||
|
body: 10
|
||||||
|
note: "14px medium, 한국어 부제"
|
||||||
|
padding_overhead_px: 60
|
||||||
|
padding_h_px: 32
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Block 2: `compare-2col-badge`
|
||||||
|
|
||||||
|
### 2.1 시각적 구조
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────┐
|
||||||
|
│ ┌─[Badge Title]─┐ │
|
||||||
|
│────────────┤ ├─────────────────│
|
||||||
|
│ │
|
||||||
|
│ ┌── Left Column ──┐ ╎ ┌── Right Column ──┐ │
|
||||||
|
│ │ │ ╎ │ │ │
|
||||||
|
│ │ [Big Title] │ ╎ │ [Big Title] │ │
|
||||||
|
│ │ │ ╎ │ │ │
|
||||||
|
│ │ body text... │ ╎ │ body text... │ │
|
||||||
|
│ │ body text... │ ╎ │ body text... │ │
|
||||||
|
│ │ │ ╎ │ │ │
|
||||||
|
│ └──────────────────┘ ╎ └──────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ [Optional: Hero Statement] │
|
||||||
|
└──────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 슬롯 정의
|
||||||
|
|
||||||
|
| 슬롯 | 필수 | 타입 | 설명 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `badge_title` | O | string | 배지 바 텍스트 |
|
||||||
|
| `left_title` | O | string | 좌측 열 대제목 |
|
||||||
|
| `left_body` | O | string | 좌측 열 본문 |
|
||||||
|
| `right_title` | O | string | 우측 열 대제목 |
|
||||||
|
| `right_body` | O | string | 우측 열 본문 |
|
||||||
|
| `statement` | X | string | 하단 Hero 메시지 |
|
||||||
|
| `left_color` | X | string | 좌측 강조색 (기본: --color-teal) |
|
||||||
|
| `right_color` | X | string | 우측 강조색 (기본: --color-teal) |
|
||||||
|
|
||||||
|
### 2.3 동적 재구성 규칙
|
||||||
|
|
||||||
|
#### 레이아웃 계산
|
||||||
|
```
|
||||||
|
container_width = 컨테이너 전체 폭
|
||||||
|
padding_h = 32px * 2
|
||||||
|
|
||||||
|
2열 모드 (기본):
|
||||||
|
col_width = (container_width - padding_h - divider_gap) / 2
|
||||||
|
divider_gap = 32px
|
||||||
|
|
||||||
|
1열 모드 (sidebar zone, 폭 < 500px):
|
||||||
|
좌/우가 세로 스택
|
||||||
|
col_width = container_width - padding_h
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 높이 계산
|
||||||
|
```
|
||||||
|
badge_height = 44px
|
||||||
|
left_height = title_height + body_lines * line_height + padding
|
||||||
|
right_height = title_height + body_lines * line_height + padding
|
||||||
|
content_height = max(left_height, right_height)
|
||||||
|
statement_height = statement ? (statement_lines * 28 + 16) : 0
|
||||||
|
|
||||||
|
total = badge_height + content_height + statement_height + gaps
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 텍스트 피팅
|
||||||
|
```
|
||||||
|
col_width에 따른 body 글자수 제한:
|
||||||
|
col_width ≥ 500px → ~40자/줄, font: 16px
|
||||||
|
col_width ≥ 350px → ~28자/줄, font: 14px
|
||||||
|
col_width < 350px → ~20자/줄, font: 13px
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 catalog.yaml schema
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- id: compare-2col-badge
|
||||||
|
name: 배지 헤더 2열 비교
|
||||||
|
category: cards
|
||||||
|
template: blocks/cards/compare-2col-badge.html
|
||||||
|
height_cost: large
|
||||||
|
min_height_px: 200
|
||||||
|
relation_types: [comparison, contrast]
|
||||||
|
visual: >
|
||||||
|
상단 배지 바(이미지/그라디언트 배경 + 흰색 텍스트) 아래
|
||||||
|
2열 비교 레이아웃. 좌/우 각각 대제목(24px/900) + 본문(16px/700).
|
||||||
|
중앙 세로 구분선. 둥근 흰색 컨테이너(r:20).
|
||||||
|
선택적 하단 Hero 메시지.
|
||||||
|
when: >
|
||||||
|
두 개념/방법/전략을 나란히 비교할 때.
|
||||||
|
배지 헤더로 상위 주제를 명시.
|
||||||
|
예: "Engn. Solution vs DfMA", "현재 vs 미래"
|
||||||
|
not_for: >
|
||||||
|
3개 이상 항목 비교 → compare-3col-badge.
|
||||||
|
장/단점 목록 → comparison-2col.
|
||||||
|
상세 내용이 길고 섹션이 많을 때 → compare-detail-gradient.
|
||||||
|
purpose_fit: [비교대조, 개념정의]
|
||||||
|
zone: full-width-only
|
||||||
|
slots:
|
||||||
|
required: [badge_title, left_title, left_body, right_title, right_body]
|
||||||
|
optional: [statement, left_color, right_color]
|
||||||
|
schema:
|
||||||
|
badge_title:
|
||||||
|
max_lines: 1
|
||||||
|
font_size: 18
|
||||||
|
ref_chars:
|
||||||
|
body: 15
|
||||||
|
note: "18px bold white, 배지 바"
|
||||||
|
left_title:
|
||||||
|
max_lines: 1
|
||||||
|
font_size: 24
|
||||||
|
ref_chars:
|
||||||
|
body: 15
|
||||||
|
note: "24px black/900, 흰색 스트로크"
|
||||||
|
left_body:
|
||||||
|
max_lines: 6
|
||||||
|
font_size: 16
|
||||||
|
ref_chars:
|
||||||
|
body: 200
|
||||||
|
note: "16px/700, 틸 색상"
|
||||||
|
right_title:
|
||||||
|
max_lines: 1
|
||||||
|
font_size: 24
|
||||||
|
ref_chars:
|
||||||
|
body: 15
|
||||||
|
note: "24px black/900, 흰색 스트로크"
|
||||||
|
right_body:
|
||||||
|
max_lines: 6
|
||||||
|
font_size: 16
|
||||||
|
ref_chars:
|
||||||
|
body: 200
|
||||||
|
note: "16px/700, 틸 색상"
|
||||||
|
statement:
|
||||||
|
max_lines: 2
|
||||||
|
font_size: 20
|
||||||
|
ref_chars:
|
||||||
|
body: 50
|
||||||
|
note: "20px bold, 중앙정렬"
|
||||||
|
padding_overhead_px: 56
|
||||||
|
padding_h_px: 32
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Block 3: `compare-detail-gradient`
|
||||||
|
|
||||||
|
### 3.1 시각적 구조
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────┐
|
||||||
|
│ ┌───── Left Header Bar (gradient) ─────┐┌── Right ─────┐│
|
||||||
|
│ │ [Left Column Title] ││ [Right Title] ││
|
||||||
|
│ └──────────────────────────────────────┘└───────────────┘│
|
||||||
|
│ ┌─────── Left BG (warm) ──────┐┌──── Right BG (teal) ──┐│
|
||||||
|
│ │ ││ ││
|
||||||
|
│ │ [Section 1 Title] ││ [Section 1 Title] ││
|
||||||
|
│ │ • body text ││ • body text ││
|
||||||
|
│ │ • body text ││ • body text ││
|
||||||
|
│ │ ││ ││
|
||||||
|
│ │ [Section 2 Title] ││ [Section 2 Title] ││
|
||||||
|
│ │ • body text ││ • body text ││
|
||||||
|
│ │ • body text ││ • body text ││
|
||||||
|
│ │ ││ ││
|
||||||
|
│ │ [Section N Title] ││ [Section M Title] ││
|
||||||
|
│ │ • body text ││ • body text ││
|
||||||
|
│ └──────────────────────────────┘└───────────────────────┘│
|
||||||
|
└──────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 슬롯 정의
|
||||||
|
|
||||||
|
| 슬롯 | 필수 | 타입 | 설명 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `left_header` | O | string | 좌측 열 헤더 타이틀 |
|
||||||
|
| `right_header` | O | string | 우측 열 헤더 타이틀 |
|
||||||
|
| `left_sections[]` | O | array | 좌측 섹션 배열 |
|
||||||
|
| `left_sections[].title` | O | string | 섹션 소제목 |
|
||||||
|
| `left_sections[].body` | O | string | 섹션 본문 (줄바꿈 허용) |
|
||||||
|
| `right_sections[]` | O | array | 우측 섹션 배열 |
|
||||||
|
| `right_sections[].title` | O | string | 섹션 소제목 |
|
||||||
|
| `right_sections[].body` | O | string | 섹션 본문 |
|
||||||
|
| `left_color_theme` | X | string | 좌측 테마 (기본: warm) |
|
||||||
|
| `right_color_theme` | X | string | 우측 테마 (기본: teal) |
|
||||||
|
|
||||||
|
### 3.3 동적 재구성 규칙 (★ 가장 수학적으로 복잡)
|
||||||
|
|
||||||
|
#### 그리드 계산
|
||||||
|
```
|
||||||
|
container_width에서 2열 분할:
|
||||||
|
col_width = (container_width - gap) / 2
|
||||||
|
gap = 0px (그라디언트가 맞닿음)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 섹션 높이 계산 (핵심)
|
||||||
|
```
|
||||||
|
header_bar_height = 48px (고정)
|
||||||
|
|
||||||
|
각 섹션의 높이:
|
||||||
|
section_height(s) =
|
||||||
|
title_height(s.title, title_font_size, col_width) +
|
||||||
|
body_height(s.body, body_font_size, col_width) +
|
||||||
|
section_padding
|
||||||
|
|
||||||
|
title_height = ceil(char_count / chars_per_line) * title_line_height
|
||||||
|
body_height = line_count * body_line_height
|
||||||
|
|
||||||
|
chars_per_line = floor(col_width / (font_size * 0.55)) // 한글 평균 0.55em
|
||||||
|
|
||||||
|
좌측 전체:
|
||||||
|
left_total = header_bar + sum(section_height for s in left_sections) + gaps
|
||||||
|
|
||||||
|
우측 전체:
|
||||||
|
right_total = header_bar + sum(section_height for s in right_sections) + gaps
|
||||||
|
|
||||||
|
content_height = max(left_total, right_total)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 오버플로 방지 — Fit 검증
|
||||||
|
```
|
||||||
|
if content_height > container_available_height:
|
||||||
|
|
||||||
|
전략 1: 폰트 축소
|
||||||
|
body_font_size -= 1px (최소 12px)
|
||||||
|
재계산
|
||||||
|
|
||||||
|
전략 2: 섹션 본문 줄 수 제한
|
||||||
|
max_body_lines = floor(
|
||||||
|
(available_per_section - title_height) / body_line_height
|
||||||
|
)
|
||||||
|
available_per_section = (container_height - header*2 - gaps) / max(N_left, N_right)
|
||||||
|
|
||||||
|
전략 3: Kei 에스컬레이션 (기존 파이프라인)
|
||||||
|
content 요약 요청
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 색상 테마 매핑
|
||||||
|
```
|
||||||
|
warm (좌측 기본):
|
||||||
|
header_gradient: rgba(165,161,150,0.10) → rgba(57,50,30,1.00)
|
||||||
|
section_title_color: var(--color-warm-brown)
|
||||||
|
bg: rgba(255,255,255,0.30) → rgba(57,50,30,0.30)
|
||||||
|
|
||||||
|
teal (우측 기본):
|
||||||
|
header_gradient: rgba(41,107,85,0.10) → rgba(3,33,24,1.00)
|
||||||
|
section_title_color: var(--color-dark-teal)
|
||||||
|
bg: rgba(41,107,85,0.30) → rgba(255,255,255,0.30)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 catalog.yaml schema
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- id: compare-detail-gradient
|
||||||
|
name: 그라디언트 상세 2열 비교
|
||||||
|
category: cards
|
||||||
|
template: blocks/cards/compare-detail-gradient.html
|
||||||
|
height_cost: xlarge
|
||||||
|
min_height_px: 300
|
||||||
|
relation_types: [comparison, contrast, process]
|
||||||
|
min_items: 2 # 좌/우 최소 1섹션씩
|
||||||
|
max_items: 10 # 좌+우 합계
|
||||||
|
visual: >
|
||||||
|
좌우 그라디언트 배경(워 브라운 vs 다크틸)으로 나뉜 2열 비교.
|
||||||
|
각 열 상단에 그라디언트 헤더 바 + 큰 제목(28px/900).
|
||||||
|
하단에 N개 섹션(소제목 22px/900 + 본문 16px/700) 반복.
|
||||||
|
좌측은 따뜻한 톤(과정/As-Is), 우측은 차가운 톤(결과/To-Be).
|
||||||
|
when: >
|
||||||
|
두 카테고리를 상세하게 비교할 때.
|
||||||
|
각 카테고리에 여러 하위 항목이 있을 때.
|
||||||
|
과정 vs 결과, As-Is vs To-Be, 문제 vs 해결 구조.
|
||||||
|
not_for: >
|
||||||
|
간단한 2항목 비교(본문 짧을 때) → compare-2col-badge.
|
||||||
|
3열 비교 → compare-3col-badge.
|
||||||
|
비교가 아닌 단독 리스트 → dark-bullet-list.
|
||||||
|
purpose_fit: [비교대조, 구조시각화, 근거사례]
|
||||||
|
zone: full-width-only
|
||||||
|
slots:
|
||||||
|
required: [left_header, right_header, left_sections[], right_sections[]]
|
||||||
|
optional: [left_color_theme, right_color_theme]
|
||||||
|
schema:
|
||||||
|
left_header:
|
||||||
|
max_lines: 1
|
||||||
|
font_size: 28
|
||||||
|
ref_chars:
|
||||||
|
body: 20
|
||||||
|
note: "28px black/900, 그라디언트 바 위"
|
||||||
|
right_header:
|
||||||
|
max_lines: 1
|
||||||
|
font_size: 28
|
||||||
|
ref_chars:
|
||||||
|
body: 20
|
||||||
|
note: "28px black/900, 그라디언트 바 위"
|
||||||
|
section_title:
|
||||||
|
max_lines: 2
|
||||||
|
font_size: 22
|
||||||
|
ref_chars:
|
||||||
|
body: 30
|
||||||
|
note: "22px/900, 색상 테마별 (브라운 or 틸)"
|
||||||
|
section_body:
|
||||||
|
max_lines: 4
|
||||||
|
font_size: 16
|
||||||
|
ref_chars:
|
||||||
|
body: 120
|
||||||
|
note: "16px/700, black"
|
||||||
|
padding_overhead_px: 48
|
||||||
|
padding_h_px: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 서브 컴포넌트
|
||||||
|
|
||||||
|
### S1. 장식 이미지 (3D 화살표 등)
|
||||||
|
- 블록이 아닌 **콘텐츠 이미지**로 처리
|
||||||
|
- `cards[].icon` 또는 별도 `decoration_image` 슬롯으로 전달
|
||||||
|
- 블록은 `<img>` 태그로 렌더링, 크기는 CSS로 컨테이너에 맞춤
|
||||||
|
|
||||||
|
### S2. CTA 버튼
|
||||||
|
- 독립 블록이 아닌 **다른 블록 내 선택적 요소**
|
||||||
|
- `cta_text` 슬롯으로 전달 (없으면 미표시)
|
||||||
|
- CSS: 그라디언트 바 + 둥근 버튼 (r:7)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 구현 결과 (전체 7개 블록)
|
||||||
|
|
||||||
|
| # | 블록 | 카테고리 | 출처 | 상태 | 핵심 수학 |
|
||||||
|
|---|------|---------|------|------|----------|
|
||||||
|
| 1 | `hero-icon-cards` | cards | Page 1/Frame 2 | ✅ 완료 | 3D 리본 fold_offset 계산 (badge_y→box_y×scale) |
|
||||||
|
| 2 | `compare-2col-badge` | cards | Page 1/Frame 3 | ✅ 완료 | 3D 리본 fold_offset + 틸 테두리 |
|
||||||
|
| 3 | `compare-detail-gradient` | cards | Page 1/Frame 4 | ✅ 완료 | CSS Grid 행 공유 + As-Is/To-Be + 연속 그라디언트 |
|
||||||
|
| 4 | `category-strip-table` | cards | Page 2/001_개요 | ✅ 완료 | scale=1200/2123, N열 동적 Grid |
|
||||||
|
| 5 | `checklist-dark` | emphasis | Page 3/f5 | ✅ 완료 | scale=1200/1770, 행 간격 계산 |
|
||||||
|
| 6 | `system-2col-center` | cards | Page 3/f8 | ✅ 완료 | scale=1200/2446, 3열 Grid |
|
||||||
|
| 7 | `cycle-orbit` | visuals | Page 4/Frame 1 | ✅ 완료 | **3D 원 Z축 기울임(80°) → 2D 투영**, 사이각 축소, 접선 회전 |
|
||||||
|
|
||||||
|
### 핵심 교훈
|
||||||
|
|
||||||
|
1. **수학적 계산 필수**: Figma 좌표 → 스케일 → CSS값. 시행착오 금지.
|
||||||
|
2. **3D 리본은 이미지 추출**: CSS로 재현 불가, Figma에서 PNG 추출.
|
||||||
|
3. **CSS Grid 행 공유**: 좌/우 섹션 Y선 정렬 문제 해결.
|
||||||
|
4. **연속 그라디언트**: 셀별 배경 → Grid 전체 배경으로 끊김 방지.
|
||||||
|
5. **3D 원 투영**: `project(α) = (cx+R×cos(α), cy+R×sin(α)×cos(θ))` — N개 노드 자동 배치.
|
||||||
|
6. **텍스트 배치**: 좌측 노드→이름 좌측에, 우측/상단 노드→이름 우측에.
|
||||||
|
7. **비교 리뷰 필수**: Figma PNG vs HTML을 같은 폭으로 위/아래 비교.
|
||||||
98
figma_to_html_agent/PLAN.md
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
# Figma → HTML 변환 파이프라인
|
||||||
|
|
||||||
|
> 핵심 운영 절차는 [PROCESS.md](PROCESS.md), 수학 공식은 [MATH.md](MATH.md), CSS 보정 규칙은 [RULES.md](RULES.md), 변환 완료 목록은 [blocks_index.md](blocks_index.md).
|
||||||
|
|
||||||
|
## 현재 방향 (2026-04 확정)
|
||||||
|
|
||||||
|
**프로세스 우선, 인벤토리 후순위.** 35개 프레임을 사전 분류하지 않고, 1세션 1프레임씩 변환하면서 패턴이 발견되면 그때 템플릿화한다.
|
||||||
|
|
||||||
|
### 폐기된 접근
|
||||||
|
|
||||||
|
| 단계 | 폐기 사유 |
|
||||||
|
|------|---------|
|
||||||
|
| ~~Stage 1: Figma 인벤토리 일괄 추출~~ | work-creating-work. 35개는 사람이 5분이면 훑음 |
|
||||||
|
| ~~Stage 2: 노드 수 기반 지문~~ | leaf 카운트는 약한 시그널. 패턴 분류에 부정확 |
|
||||||
|
| ~~Stage 3: 자동 군집~~ | 약한 지문으로 자동 군집 시 잘못 묶임. 사람 눈이 빠름 |
|
||||||
|
|
||||||
|
**대체:** 매 변환 직후 [blocks_index.md](blocks_index.md)에 1줄 메모. 패턴은 bottom-up으로 발견된다.
|
||||||
|
|
||||||
|
## 활성 단계
|
||||||
|
|
||||||
|
```
|
||||||
|
[루프, 1세션 1프레임]
|
||||||
|
|
||||||
|
A. 1:1 변환 ← PROCESS.md 10단계 실행
|
||||||
|
↓
|
||||||
|
B. 변형 축 메모 ← flat.md에 1~5줄 작성
|
||||||
|
↓
|
||||||
|
C. blocks_index.md 1줄 추가
|
||||||
|
↓
|
||||||
|
D. 패턴 2번째 등장? → 템플릿화 (Jinja2 + catalog.yaml 등록)
|
||||||
|
↓
|
||||||
|
design_agent/templates/blocks/{category}/
|
||||||
|
↓
|
||||||
|
[다음 프레임은 새 세션에서]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 현황 (2026-04-10)
|
||||||
|
|
||||||
|
| 항목 | 상태 |
|
||||||
|
|------|------|
|
||||||
|
| 핵심 문서 (CLAUDE/PROCESS/MATH/RULES/PROCESS-CONTROL) | ✅ 정리 완료 |
|
||||||
|
| 재사용 스크립트 (scripts/gradient_math.py) | ✅ 자체 회귀 테스트 통과 |
|
||||||
|
| 변환 완료 블록 (정적 HTML) | 2 / N |
|
||||||
|
| 템플릿화 (Jinja2) | 0 |
|
||||||
|
| catalog.yaml 등록 (새 패턴) | 0 |
|
||||||
|
| design_agent 본체 통합 | 0 |
|
||||||
|
|
||||||
|
### 변환 완료 블록
|
||||||
|
|
||||||
|
| # | slug | frame | pattern | 상태 |
|
||||||
|
|---|------|-------|---------|------|
|
||||||
|
| 1 | prerequisites-3col | 45:15 | 3-column-comparison | static (이전 작업) |
|
||||||
|
| 2 | bim-goals-3circles | 66:310 | cycle-3way-intersect | static (Pure CSS, 검증 ✓) |
|
||||||
|
|
||||||
|
자세한 내역: [blocks_index.md](blocks_index.md)
|
||||||
|
|
||||||
|
## 대상 Figma 파일
|
||||||
|
|
||||||
|
- 파일키: `9S6LsQyO6zlRxtiqZccOUM` ("Untitled")
|
||||||
|
- 페이지: Page 2
|
||||||
|
- 추정 프레임 수: ~35개 (확정 안 됨, 사용자가 매번 선택)
|
||||||
|
|
||||||
|
## 다음 액션
|
||||||
|
|
||||||
|
1. 사용자가 Figma desktop에서 다음 변환할 프레임 **선택**
|
||||||
|
2. 에이전트가 PROCESS.md의 10단계 그대로 실행
|
||||||
|
3. 변환 후 blocks_index.md 업데이트
|
||||||
|
4. 다음 프레임은 **새 세션에서**
|
||||||
|
|
||||||
|
## 학습된 규칙 (이 프로젝트에서 발견)
|
||||||
|
|
||||||
|
> [RULES.md](RULES.md) R1~R12에 정리됨
|
||||||
|
|
||||||
|
1. Figma MCP는 rotation 미제공 → bbox 비율로 감지 (R2)
|
||||||
|
2. CSS line-height:1 → descender 잘림 → padding-bottom 보정 (R1)
|
||||||
|
3. Figma 세로 텍스트 = 좁은 박스 + 가로 텍스트 → HTML `<br>` 방식 (R3)
|
||||||
|
4. 흰 텍스트 stroke → HTML에서 비주얼 안 좋음 → 제거
|
||||||
|
5. 미리보기 배경은 항상 흰색 (R7)
|
||||||
|
6. 다중 fills → 최상단만 사용 (R5)
|
||||||
|
7. 동일 좌표 중복 노드 → 1개만 렌더링 (R6)
|
||||||
|
8. **순수 CSS 우선, SVG는 곡선/필터에만** (R9, 1171281211 변환에서 확정)
|
||||||
|
9. **plus-darker → multiply 교체** (R10, Safari 외 호환)
|
||||||
|
10. **Stroke inside/outside 구분** → box-sizing 결정 (R11)
|
||||||
|
11. **viewBox padding 그라데이션 좌표 remap** 필수 (R12)
|
||||||
|
12. **Vector 노드 metadata bbox는 회전된 좌표** → React wrapper 좌표 신뢰
|
||||||
|
|
||||||
|
## 블록 라이브러리 통합 경로 (Stage E~G, 미래)
|
||||||
|
|
||||||
|
```
|
||||||
|
figma_to_html_agent/templates/ ← Jinja2 템플릿 임시 저장소
|
||||||
|
↓
|
||||||
|
design_agent/templates/blocks/{category}/{pattern_id}.html.j2
|
||||||
|
design_agent/templates/catalog.yaml ← when/slots/min_size_px 등록
|
||||||
|
↓
|
||||||
|
design_agent Phase Q 블록 선택 단계와 자동 연결
|
||||||
|
↓
|
||||||
|
사용자 콘텐츠 → 존 크기 → 패턴 매칭 → 블록 선택 → 슬롯 채우기 → 렌더
|
||||||
|
```
|
||||||
66
figma_to_html_agent/PROCESS-CONTROL.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# Figma → HTML 프로세스 제어
|
||||||
|
|
||||||
|
## 변경 전 반드시 확인
|
||||||
|
|
||||||
|
### 1. 소스는 Figma 데이터다
|
||||||
|
- gradient 방향: Figma 데이터의 각도에서 CSS 변환 (CSS = 90 - Figma)
|
||||||
|
- border-radius: Figma 데이터 그대로 (스케일만)
|
||||||
|
- PNG를 보고 방향을 판단하지 않는다
|
||||||
|
- PNG는 픽셀 데이터 분석으로만 교차 검증에 사용
|
||||||
|
|
||||||
|
### 2. 이미지 해석 금지
|
||||||
|
- 멀티모달 이미지 해석으로 gradient 방향 판단 불가 (미묘한 alpha에서 틀림)
|
||||||
|
- 방향 확인이 필요하면 픽셀 데이터를 숫자로 분석
|
||||||
|
- "보니까 ~인 것 같다" 금지. 데이터로 확인
|
||||||
|
|
||||||
|
### 3. 작동하는 것은 건드리지 않는다
|
||||||
|
- 사용자가 A만 문제라고 하면 A만 수정
|
||||||
|
- B, C가 "같은 이유로 틀릴 것 같다"고 추측해서 함께 바꾸지 않는다
|
||||||
|
- 변경 전: 현재 값이 뭔지 기록
|
||||||
|
- 변경 후: 변경한 값이 뭔지 기록
|
||||||
|
- 되돌려야 할 때 정확히 어디로 돌아가는지 알아야 한다
|
||||||
|
|
||||||
|
### 4. 한 번에 하나만 바꾼다
|
||||||
|
- gradient 각도와 border-radius를 동시에 바꾸지 않는다
|
||||||
|
- 하나 바꾸고 확인, 맞으면 다음 하나
|
||||||
|
|
||||||
|
### 5. 사용자가 말한 것만 한다
|
||||||
|
- 사용자의 피드백을 자의적으로 해석하지 않는다
|
||||||
|
- "주황색 gradient가 안 맞다" → 주황색 gradient만 수정
|
||||||
|
- 초록, 다른 요소는 건드리지 않는다
|
||||||
|
|
||||||
|
### 6. 찍어맞추기 금지
|
||||||
|
- 0deg 안 되면 180deg, 그것도 안 되면 90deg... 이런 식 금지
|
||||||
|
- 값을 바꾸기 전에 WHY를 먼저 설명할 수 있어야 한다
|
||||||
|
- 설명 못하면 바꾸지 않는다
|
||||||
|
|
||||||
|
### 7. "쉬운 전면 재작성" 절대 금지
|
||||||
|
- 80점 결과물에서 2가지 문제를 고칠 때, 구조를 flex/grid 등으로 **전면 재작성하지 않는다**
|
||||||
|
- 기존에 맞춘 수십 가지(pill 크기, 위치, 비율, border 걸침)가 전부 깨진다
|
||||||
|
- **기존 구조 유지 + 문제만 정확히 수정**이 원칙
|
||||||
|
- 보완이 안 되면 그 방식을 오답노트로 두고 **다른 방식으로 접근**
|
||||||
|
- 점점 나빠지면 **즉시 멈추고 마지막 OK 상태로 복원**
|
||||||
|
- 구조 변경이 불가피하면 **사전에 영향 범위 분석 + 사용자 확인 후** 진행
|
||||||
|
|
||||||
|
## Figma 도형 gradient 처리 프로세스
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Figma gradient 각도 확인
|
||||||
|
2. gradient를 0으로 돌린 기본 상태 파악
|
||||||
|
- border-radius: Figma 값 그대로
|
||||||
|
- gradient: CSS 90deg (Figma 0 = 왼→오 = CSS 90deg)
|
||||||
|
3. CSS로 기본 상태 구현
|
||||||
|
4. Figma gradient 각도 적용: CSS = 90 - Figma각도
|
||||||
|
5. 위치(left, top)와 크기(width, height) 배치
|
||||||
|
```
|
||||||
|
|
||||||
|
## Figma gradient 각도 체계
|
||||||
|
|
||||||
|
```
|
||||||
|
Figma 0° = 왼쪽 진 → 오른쪽 옅 = CSS 90°
|
||||||
|
Figma -90° = 위 진 → 아래 옅 = CSS 180°
|
||||||
|
Figma -180° = 오른쪽 진 → 왼쪽 옅 = CSS 270°
|
||||||
|
Figma 90° = 아래 진 → 위 옅 = CSS 0°
|
||||||
|
```
|
||||||
|
|
||||||
|
변환: **CSS = 90 - Figma**
|
||||||
410
figma_to_html_agent/PROCESS.md
Normal file
@@ -0,0 +1,410 @@
|
|||||||
|
# 변환 절차 (10 STEP)
|
||||||
|
|
||||||
|
Figma 프레임 1개를 HTML+template으로 변환할 때 매번 동일하게 따르는 운영 핸드북.
|
||||||
|
|
||||||
|
> **원칙: 같은 세션에서 여러 프레임 연속 작업 OK.** 컨텍스트가 무거워지면 `/compact` 로 정리하고 계속 진행. 핵심 결정/규칙/산출물은 모두 파일에 박혀있어 compact 후에도 보존됨. 이렇게 해야 누적 학습(R13 등 sub-pattern)이 즉시 적용됨. CLAUDE.md 원칙 7 참조.
|
||||||
|
|
||||||
|
> **원칙: 1 프레임 변환 = 1:1 reference + 템플릿 동시 작성.** 1번째 등장이라도 templates_staging/{pattern}.html.j2 + meta.yaml + example.yaml 까지 작성한다. 정적 HTML만 두는 것은 work-creating-work. 사용자가 final 검수 후 design_agent/templates/ 로 직접 프로모션.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 0 — 준비
|
||||||
|
|
||||||
|
### 0-A. 에이전트: blocks_index.md 한 번 읽기 (필수)
|
||||||
|
|
||||||
|
새 세션은 메모리가 없다. 패턴 발견 트리거(2번째 등장)가 작동하려면 **세션 시작 직후** [blocks_index.md](blocks_index.md)를 한 번 통으로 읽어야 한다.
|
||||||
|
|
||||||
|
```
|
||||||
|
Read figma_to_html_agent/blocks_index.md
|
||||||
|
```
|
||||||
|
|
||||||
|
확인할 것:
|
||||||
|
- "변환 완료 (현행 방법론)" 섹션의 패턴 목록
|
||||||
|
- "패턴 카탈로그" 섹션의 등록 패턴 (등장 횟수)
|
||||||
|
- "templates_staging 대기열" 의 진행 중 패턴
|
||||||
|
|
||||||
|
### 0-B. 사용자: 프레임 선택
|
||||||
|
|
||||||
|
1. Figma desktop에서 변환할 프레임을 **선택** (클릭) 한다
|
||||||
|
2. 에이전트에게 "이 프레임 변환해줘"라고 알린다 (프레임 ID/이름 명시 권장)
|
||||||
|
|
||||||
|
### 0-C. 에이전트: 패턴 비교
|
||||||
|
|
||||||
|
STEP 1~3로 metadata + screenshot 받은 직후, 0-A에서 본 인덱스와 비교:
|
||||||
|
|
||||||
|
- 비슷한 구조 발견 → "이거 X 패턴과 비슷합니다. 두 번째 등장이면 templates_staging/ 로 Jinja2 추출 진행할까요?" 사용자에게 확인
|
||||||
|
- 비슷한 게 없음 → 일반 STEP 4 이하 진행
|
||||||
|
|
||||||
|
**확인사항:**
|
||||||
|
- Figma desktop 앱이 활성 탭에 올바른 파일이 떠 있는가
|
||||||
|
- `.mcp.json`에 figma-desktop SSE 서버가 등록돼있는가 (`http://127.0.0.1:3845/sse`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 1~3 — 데이터 수집 (병렬)
|
||||||
|
|
||||||
|
세 도구를 **단일 메시지에 multiple tool_use 블록**으로 동시 호출한다 (도구 호출 단위 병렬). 순차 호출하면 같은 노드 ID를 두 번 추출하느라 토큰만 낭비됨.
|
||||||
|
|
||||||
|
```
|
||||||
|
[single message, multiple tool_use blocks]
|
||||||
|
1. mcp__figma-desktop__get_metadata nodeId="" (현재 선택 노드)
|
||||||
|
2. mcp__figma-desktop__get_design_context nodeId="" (현재 선택 노드)
|
||||||
|
3. mcp__figma-desktop__get_screenshot nodeId="" (현재 선택 노드)
|
||||||
|
```
|
||||||
|
|
||||||
|
**주의:** nodeId를 비우면 현재 선택 노드를 사용하므로 metadata 응답을 기다릴 필요 없음. 셋 다 동시에 갈 수 있다.
|
||||||
|
|
||||||
|
| 도구 | 얻는 것 | 사용처 |
|
||||||
|
|------|--------|-------|
|
||||||
|
| get_metadata | 모든 leaf 노드의 `id, type, name, x, y, width, height` (XML) | bottom-up 플래튼 |
|
||||||
|
| get_design_context | gradient/filter/font/color (React+Tailwind 코드) | CSS 변환 |
|
||||||
|
| get_screenshot | Figma가 렌더한 PNG | STEP 8 사람 눈 검증 |
|
||||||
|
|
||||||
|
**주의:**
|
||||||
|
- get_metadata 응답이 100KB+ 면 frame이 너무 커서 자르지 않은 상태. 사용자에게 더 작은 단위 선택 요청
|
||||||
|
- get_design_context는 응답이 매우 크므로 한 프레임당 1회만 호출
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 4 — 자산 정리 (block-tests/assets/shared/ 캐시)
|
||||||
|
|
||||||
|
design_context에서 `localhost:3845/assets/{hash}.png|svg` 패턴의 자산 URL 추출.
|
||||||
|
|
||||||
|
각 자산에 대해:
|
||||||
|
1. URL 끝의 hash를 파일명으로 사용
|
||||||
|
2. `block-tests/assets/shared/{hash}.{ext}` 가 **이미 있으면 다운로드 스킵**
|
||||||
|
3. 없으면 curl로 다운로드
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd block-tests/assets/shared
|
||||||
|
for url in $URLS; do
|
||||||
|
hash=$(basename "$url")
|
||||||
|
[ -f "$hash" ] || curl -sSo "$hash" "$url"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
HTML에서 참조 시:
|
||||||
|
```html
|
||||||
|
<img src="assets/shared/{hash}.png">
|
||||||
|
```
|
||||||
|
|
||||||
|
(`block-tests/{slug}.html` 기준으로 상대 경로 `assets/shared/`)
|
||||||
|
|
||||||
|
**효과:**
|
||||||
|
- 동일 자산이 여러 프레임에서 등장해도 한 번만 다운로드 (해시 파일명이라 자동 dedup)
|
||||||
|
- 후속 프레임 변환 시간 단축
|
||||||
|
- 토큰 절약 (이미 있는지 확인만)
|
||||||
|
|
||||||
|
**프레임 매핑 메모:** `block-tests/{slug}_assets.txt`에 사용한 hash 목록 + 의미 라벨 기록 → 추후 재추출 시 빠른 매핑
|
||||||
|
|
||||||
|
```
|
||||||
|
# bim-goals-3circles_assets.txt
|
||||||
|
84965807....png bg_texture
|
||||||
|
f05ebf15....png arc_top
|
||||||
|
2f0f1750....png arc_side
|
||||||
|
```
|
||||||
|
|
||||||
|
**legacy:** 이전에 다운로드한 자산이 `block-tests/assets/frame_{id}/` 에 있다면 그대로 두되, 새 변환부터는 `shared/` 만 사용한다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 5 — flat.md 작성 (분석 + 이상 탐지)
|
||||||
|
|
||||||
|
`block-tests/{slug}_flat.md` 파일 생성. 다음 섹션을 반드시 포함:
|
||||||
|
|
||||||
|
### 섹션 1. 메타
|
||||||
|
```markdown
|
||||||
|
# Frame {ID} — {이름}
|
||||||
|
|
||||||
|
> 원본: {W} × {H} px (node {ID})
|
||||||
|
> Scale: × {S} → {1280} × {H×S} px
|
||||||
|
> 슬라이드 16:9 안 배치
|
||||||
|
```
|
||||||
|
|
||||||
|
### 섹션 2. 계층 경로 (bottom-up)
|
||||||
|
|
||||||
|
모든 leaf 노드를 들여쓰기 트리로 표현. 그룹별 누적 offset 표시.
|
||||||
|
|
||||||
|
```
|
||||||
|
Frame {root} ({W}×{H})
|
||||||
|
├─ Group "X" (offset → 누적)
|
||||||
|
│ ├─ TEXT "..." (abs_x, abs_y) {w}×{h}
|
||||||
|
│ └─ ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 섹션 3. 이상 탐지 결과
|
||||||
|
|
||||||
|
| 검사 | 결과 |
|
||||||
|
|------|------|
|
||||||
|
| 회전 단일문자 (bbox 가로 > 세로 × 1.5) | 발견 노드 ID 또는 "없음" |
|
||||||
|
| 좁은 박스 세로 텍스트 (width < fontSize × 0.8) | ... |
|
||||||
|
| 중복 노드 (동일 좌표 + 동일 내용) | ... |
|
||||||
|
| Vector 좌표 metadata vs design_context 불일치 | ... (있으면 어느 쪽 신뢰) |
|
||||||
|
|
||||||
|
### 섹션 4. 변형 가능 축 메모 + 슬롯 옵션
|
||||||
|
|
||||||
|
이 블록을 템플릿화한다면 무엇이 파라미터가 될지 1~5줄로. **각 슬롯이 required인지 optional인지 표시**:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## 변형 가능 축
|
||||||
|
- columns[N=2~4] (required)
|
||||||
|
- badge (required)
|
||||||
|
- bullet_items[1~12] (required)
|
||||||
|
- bg_image (required)
|
||||||
|
- bottom_photo (optional) ← 사진 없는 mdx도 이 블록 매칭 가능
|
||||||
|
- color_palette[N] (required, N과 일치)
|
||||||
|
```
|
||||||
|
|
||||||
|
이 메모가 STEP 10의 `blocks_index.md` 요약 + 향후 templates_staging meta.yaml 의 초안.
|
||||||
|
|
||||||
|
### 섹션 5. Sub-pattern 식별 (재사용 가능한 atomic 단위)
|
||||||
|
|
||||||
|
이 블록 안에 **다른 블록과 공유 가능한 sub-pattern**이 있는가? RULES.md R13~ 참조.
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Sub-patterns
|
||||||
|
- `bullet-list-with-marker` (R13) — 각 텍스트 앞에 장식 마커
|
||||||
|
- 위치: 각 컬럼 본문 영역
|
||||||
|
- 마커: checkbox PNG
|
||||||
|
- 적용 구조: .bullet-list / .bullet-row / .bullet-icon / .bullet-text
|
||||||
|
```
|
||||||
|
|
||||||
|
Sub-pattern을 **즉시 RULES.md에 등록할 필요는 없다**. 동일 sub-pattern이 2번째 등장하면 그때 R번호 부여해서 정식 등록.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 6 — 그라데이션 수학 변환
|
||||||
|
|
||||||
|
각 SVG `<linearGradient>` 데이터를 [scripts/gradient_math.py](scripts/gradient_math.py)로 CSS로 변환.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python scripts/gradient_math.py \
|
||||||
|
--w 350 --h 350 \
|
||||||
|
--x1 110.833 --y1 18.2292 --x2 219.479 --y2 175 \
|
||||||
|
--stops "0:#FDC69E,1:#E0782C"
|
||||||
|
```
|
||||||
|
|
||||||
|
출력:
|
||||||
|
```
|
||||||
|
linear-gradient(145.28deg, #FDC69E 16.04%, #E0782C 55.20%)
|
||||||
|
```
|
||||||
|
|
||||||
|
수학 원리는 [MATH.md §2 참조](MATH.md).
|
||||||
|
|
||||||
|
**여러 그라데이션을 한 번에 변환할 땐 Python 인라인 스크립트 사용:**
|
||||||
|
|
||||||
|
```python
|
||||||
|
import sys, os
|
||||||
|
# scripts/ 디렉토리를 sys.path에 명시 추가 (작업 디렉토리 무관)
|
||||||
|
sys.path.insert(0, os.path.join('figma_to_html_agent', 'scripts'))
|
||||||
|
from gradient_math import svg_to_css
|
||||||
|
|
||||||
|
svg_to_css(W=350, H=350, x1=110.833, y1=18.2292, x2=219.479, y2=175,
|
||||||
|
stops=[(0, '#FDC69E'), (1, '#E0782C')])
|
||||||
|
```
|
||||||
|
|
||||||
|
**작업 디렉토리가 `figma_to_html_agent/` 인 경우:**
|
||||||
|
```python
|
||||||
|
import sys; sys.path.insert(0, 'scripts')
|
||||||
|
from gradient_math import svg_to_css
|
||||||
|
```
|
||||||
|
|
||||||
|
**또는 정식 패키지로 사용** (`scripts/__init__.py` 가 있으므로):
|
||||||
|
```python
|
||||||
|
# 작업 디렉토리가 figma_to_html_agent/ 일 때
|
||||||
|
from scripts.gradient_math import svg_to_css
|
||||||
|
```
|
||||||
|
|
||||||
|
⚠️ **금지: 함수 코드를 인라인 Python에 복사 붙여넣기**. 한 번 만든 `gradient_math.py`를 항상 import해서 쓴다. 복사하면 버그 수정 시 여러 곳을 동시에 고쳐야 하고 수식이 미세하게 어긋날 위험.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 7 — HTML 작성
|
||||||
|
|
||||||
|
### 7-A. 기본 구조
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="slide"> <!-- 1280×720 흰색 -->
|
||||||
|
<div class="block"> <!-- 1280 × (H×S) -->
|
||||||
|
<div class="inner"> <!-- 원본 W×H, transform: scale(S) -->
|
||||||
|
... 모든 요소 (Figma 원본 좌표 사용) ...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
.inner {
|
||||||
|
position: absolute;
|
||||||
|
left: 0; top: 0;
|
||||||
|
width: {W}px; height: {H}px;
|
||||||
|
transform: scale({S});
|
||||||
|
transform-origin: top left;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**왜 transform: scale을 쓰는가:** 모든 위치/크기/폰트/그림자/스트로크가 한 번의 transform으로 균일하게 축소됨. 매 값을 수동으로 ×S 곱하는 것보다 안전하고 검증 가능. ([MATH.md §1](MATH.md))
|
||||||
|
|
||||||
|
### 7-B. 요소 변환 우선순위
|
||||||
|
|
||||||
|
| 요소 종류 | 구현 방법 | 이유 |
|
||||||
|
|---------|---------|-----|
|
||||||
|
| 원/사각형 + gradient + blend | **HTML div** + `border-radius` + `linear-gradient` + `mix-blend-mode: multiply` | 동적 재구성 위해 |
|
||||||
|
| Stroke (경계선) | `border: Npx solid color` + `box-sizing: border-box` | gradient와 함께 사용 가능 |
|
||||||
|
| Drop shadow blur | `box-shadow: 0 0 {2×stdDev}px {color}` | SVG feGaussianBlur 근사 |
|
||||||
|
| 곡선 (아크, 비원형) | **SVG `<path>`** 또는 미리 export된 PNG | CSS 불가능 |
|
||||||
|
| 텍스트 | HTML `<div>` 절대 배치 | 선택 가능, 접근성 |
|
||||||
|
| 실사 이미지 | `<img>` PNG | 재현 불가 |
|
||||||
|
| 회전된 도형 | 래퍼 div + `transform: rotate()` ([INSIGHT-GRADIENT.md](INSIGHT-GRADIENT.md)) | gradient 동시 회전 |
|
||||||
|
|
||||||
|
### 7-C. 보정 규칙
|
||||||
|
|
||||||
|
[RULES.md](RULES.md) R1~R16 모두 적용:
|
||||||
|
- R1: descender padding-bottom
|
||||||
|
- R2~R3: 회전/세로 텍스트
|
||||||
|
- R4: 그라데이션 텍스트
|
||||||
|
- R5: 다중 fills
|
||||||
|
- R6: 중복 노드
|
||||||
|
- R7: 흰 배경
|
||||||
|
- R8: 스케일 팩터
|
||||||
|
- R9: 순수 CSS 우선
|
||||||
|
- R10: blend mode 호환
|
||||||
|
- R11: stroke 정렬 (inside/outside)
|
||||||
|
- R12: viewBox padding
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 8 — Selenium 렌더링 + 사람 눈 검증
|
||||||
|
|
||||||
|
```python
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.chrome.options import Options
|
||||||
|
from PIL import Image
|
||||||
|
import os, time
|
||||||
|
|
||||||
|
# _renders/ 폴더 없으면 생성
|
||||||
|
os.makedirs('block-tests/_renders', exist_ok=True)
|
||||||
|
|
||||||
|
opts = Options()
|
||||||
|
opts.add_argument('--headless=new')
|
||||||
|
opts.add_argument('--hide-scrollbars')
|
||||||
|
opts.add_argument('--force-device-scale-factor=1')
|
||||||
|
opts.add_argument('--window-size=1600,900')
|
||||||
|
d = webdriver.Chrome(options=opts)
|
||||||
|
|
||||||
|
p = os.path.abspath('block-tests/{slug}.html').replace('\\','/')
|
||||||
|
d.get('file:///' + p)
|
||||||
|
time.sleep(1.5)
|
||||||
|
d.save_screenshot('block-tests/_renders/{slug}_full.png')
|
||||||
|
|
||||||
|
r = d.execute_script(
|
||||||
|
'const r=document.querySelector(".slide").getBoundingClientRect();'
|
||||||
|
'return [r.x,r.y,r.width,r.height];'
|
||||||
|
)
|
||||||
|
Image.open('block-tests/_renders/{slug}_full.png').crop(
|
||||||
|
(int(r[0]), int(r[1]), int(r[0]+r[2]), int(r[1]+r[3]))
|
||||||
|
).save('block-tests/_renders/{slug}.png')
|
||||||
|
d.quit()
|
||||||
|
```
|
||||||
|
|
||||||
|
**검증 방식:**
|
||||||
|
- 자동 픽셀 diff는 하지 않음 (font 렌더 차이로 노이즈만 많음)
|
||||||
|
- Figma `get_screenshot` 응답과 Selenium 결과를 **사람 눈**으로 비교
|
||||||
|
- 차이 발견 시 STEP 5~7로 돌아가서 원인 파악 (값 수정 금지)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 9 — 결과물 저장
|
||||||
|
|
||||||
|
```
|
||||||
|
block-tests/
|
||||||
|
├── {slug}.html ← 변환물
|
||||||
|
├── {slug}_flat.md ← 플래튼/이상/변형 축 메모
|
||||||
|
└── _renders/
|
||||||
|
└── {slug}.png ← 검증 스크린샷
|
||||||
|
```
|
||||||
|
|
||||||
|
`{slug}` 명명 규칙: 의미 기반 kebab-case (예: `bim-goals-3circles`, `cards-3col-icon`).
|
||||||
|
프레임 ID는 metadata로 추적 가능하므로 파일명에 넣지 않음.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 10 — blocks_index.md 1줄 업데이트
|
||||||
|
|
||||||
|
`blocks_index.md` 끝에 한 줄 추가:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
| {slug} | {프레임 ID} | {1줄 변형 축 요약} | {날짜} |
|
||||||
|
```
|
||||||
|
|
||||||
|
이 인덱스가 패턴 발견의 단서가 된다. 다음 변환 시작 전에 이 인덱스를 한 번 훑어서 "이미 비슷한 거 했나?" 확인.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 패턴 → 템플릿화 (1번째부터 즉시)
|
||||||
|
|
||||||
|
**규칙: 1번째 등장부터 templates_staging 작성. 정적 HTML만 두는 것 금지.**
|
||||||
|
|
||||||
|
| 등장 횟수 | 처리 |
|
||||||
|
|---------|------|
|
||||||
|
| **1번째** | `block-tests/{slug}.html` (1:1 reference) + `templates_staging/{pattern_id}.html.j2` (Jinja2 + meta.yaml + example.yaml) **함께 작성** |
|
||||||
|
| 2번째 | 기존 staging 템플릿이 새 데이터로 잘 렌더되는지 확인. 안 되면 템플릿 수정. example 추가. |
|
||||||
|
| 3번째 이후 | 동일 |
|
||||||
|
|
||||||
|
**왜 1번째부터 템플릿화하나?**
|
||||||
|
- 변환의 목적은 **블록 라이브러리 구축**, 단순 HTML 복제가 아님
|
||||||
|
- 1:1 단계에서 발견한 인사이트(R13 등)를 즉시 템플릿에 반영해야 잊지 않음
|
||||||
|
- 사용자가 검수할 때 "이게 블록으로 어떻게 작동할지" 즉시 확인 가능
|
||||||
|
- 2번째 등장을 기다리면 사용자 수동 복제 작업이 누적됨 (work-creating-work)
|
||||||
|
|
||||||
|
**Stage 2 산출물:**
|
||||||
|
```
|
||||||
|
templates_staging/
|
||||||
|
├── {pattern_id}.html.j2 ← Jinja2 템플릿 본체
|
||||||
|
└── {pattern_id}.meta.yaml ← when / slots / min_size_px / 변형 축 초안
|
||||||
|
```
|
||||||
|
|
||||||
|
여기까지가 **에이전트 책임의 끝.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚧 프로모션 게이트 (사용자 전용)
|
||||||
|
|
||||||
|
> 이 게이트 이후 작업은 **에이전트가 절대 수행하지 않는다.** 모든 design_agent/templates/ 변경은 사용자 본인이 직접 한다.
|
||||||
|
|
||||||
|
### 사용자가 수행할 작업
|
||||||
|
|
||||||
|
1. **검수**: `templates_staging/{pattern_id}.html.j2` 를 다양한 파라미터로 렌더 테스트
|
||||||
|
2. **품질 게이트 통과 확인**:
|
||||||
|
- [ ] 1:1 변환물과 시각적으로 동일한가
|
||||||
|
- [ ] 슬롯 파라미터를 바꿔도 깨지지 않는가 (원 4개, 라벨 0개 등 극단 케이스)
|
||||||
|
- [ ] meta.yaml의 when/slots가 design_agent의 다른 블록과 충돌 없는가
|
||||||
|
3. **이동**: `templates_staging/{pattern_id}.html.j2` → `design_agent/templates/blocks/{category}/`
|
||||||
|
4. **등록**: `design_agent/templates/catalog.yaml` 에 when/slots/min_size_px 추가
|
||||||
|
5. **상태 업데이트**: `blocks_index.md` 의 해당 행 상태 → `promoted`
|
||||||
|
|
||||||
|
### 에이전트의 역할
|
||||||
|
|
||||||
|
- staging 작성까지만
|
||||||
|
- 사용자 요청 없이 `design_agent/templates/` 를 절대 읽거나 쓰지 않음
|
||||||
|
- "templates/ 에 옮겨드릴까요?" 같은 제안 금지 (월권)
|
||||||
|
- 사용자가 명시적으로 "이 staging 결과 검토해줘"라고 요청하면 → staging 폴더 내에서만 검토
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 안티 패턴 (하지 말 것)
|
||||||
|
|
||||||
|
| ❌ 하지 말 것 | 이유 |
|
||||||
|
|------------|-----|
|
||||||
|
| 사전에 인벤토리/지문/군집 단계 | work-creating-work, 패턴은 변환하면서 발견됨 |
|
||||||
|
| 1번째 등장은 정적 HTML로만 두기 (templates_staging 미작성) | work-creating-work, 인사이트 잊혀짐. 1번째부터 템플릿 작성 |
|
||||||
|
| 컨텍스트 차면 강제 새 세션 | compact 사용. 핵심 결정은 모두 파일에 박혀있어 손실 없음 |
|
||||||
|
| Figma 데이터 안 보고 멀티모달 이미지로 추측 | 미묘한 alpha/blend에서 틀림 |
|
||||||
|
| "여기 1px 어색하니 다른 곳도 같이 바꾸자" | 사용자 피드백만 정확히 반영 |
|
||||||
|
| 같은 자산을 매번 새로 다운로드 | `block-tests/assets/shared/` 캐시 활용 |
|
||||||
|
| 그라데이션 각도/색을 눈대중으로 | gradient_math.py로 수학 도출 |
|
||||||
|
| gradient_math.py 함수 코드 인라인 복사 | import만 한다. 복사하면 수식 어긋남 |
|
||||||
|
| 세션 시작에 blocks_index.md 안 읽음 | 패턴 발견 트리거 영영 작동 안 함 |
|
||||||
|
| `design_agent/templates/` 직접 수정 | 프로모션은 사용자 전용. 에이전트는 staging까지만 |
|
||||||
|
| "templates/ 옮겨드릴까요?" 제안 | 월권. 사용자가 알아서 함 |
|
||||||
|
| `prerequisites-3col.html` 을 신규 변환 레퍼런스로 사용 | 구 방법론 (R8/R9 미적용). legacy 표시됨 |
|
||||||
466
figma_to_html_agent/RULES.md
Normal file
@@ -0,0 +1,466 @@
|
|||||||
|
# CSS 보정 규칙
|
||||||
|
|
||||||
|
Figma → HTML 변환 시 Figma와 CSS 렌더링 차이를 수학적으로 보정하는 규칙 모음.
|
||||||
|
**모든 규칙은 수학적 근거가 있어야 한다. 감으로 보정하지 않는다.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## R1. Descender 보정 (padding-bottom)
|
||||||
|
|
||||||
|
**문제:** CSS `line-height: 1`이면 글리프 하강부(g, y, p, 쉼표)가 잘림.
|
||||||
|
Figma는 line-height에 관계없이 글리프를 항상 표시하지만, CSS는 line box 밖을 자른다.
|
||||||
|
|
||||||
|
**원인:** 폰트의 content area > line box일 때 half-leading이 음수가 되어 잘림 발생.
|
||||||
|
|
||||||
|
**계산:**
|
||||||
|
```
|
||||||
|
content_area_ratio = (typoAscender + |typoDescender|) / UPM
|
||||||
|
half_leading = (line_height - content_area_ratio) / 2 ← 음수이면 잘림
|
||||||
|
clipped_px = |half_leading| × font_size
|
||||||
|
padding-bottom = ceil(clipped_px)
|
||||||
|
```
|
||||||
|
|
||||||
|
**폰트별 값:**
|
||||||
|
|
||||||
|
| 폰트 | UPM | Ascender | Descender | content_area_ratio |
|
||||||
|
|------|-----|----------|-----------|-------------------|
|
||||||
|
| Noto Sans KR | 1000 | 1160 | 288 | 1.448 |
|
||||||
|
| Pretendard | 1000 | 1100 | 300 | 1.400 |
|
||||||
|
|
||||||
|
**예시 (Noto Sans KR, font-size 27.1px, line-height 1):**
|
||||||
|
```
|
||||||
|
half_leading = (1 - 1.448) / 2 = -0.224
|
||||||
|
clipped = 0.224 × 27.1 = 6.07px
|
||||||
|
→ padding-bottom: 7px
|
||||||
|
```
|
||||||
|
|
||||||
|
**적용:** `line-height < content_area_ratio`인 모든 텍스트 요소에 padding-bottom 추가.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## R2. 회전 감지 (bbox 비율)
|
||||||
|
|
||||||
|
**문제:** Figma MCP는 `rotation`/`transform` 속성을 출력하지 않음.
|
||||||
|
|
||||||
|
**감지 방법:** 바운딩 박스의 가로세로 비율이 해당 글자의 정상 비율과 반대이면 회전.
|
||||||
|
|
||||||
|
```
|
||||||
|
단일 문자 "(" 정상: ~18×50 (세로가 김)
|
||||||
|
Figma bbox: 60×19 (가로가 김)
|
||||||
|
→ 가로:세로 = 3.2:1 → 90° 회전 확정
|
||||||
|
```
|
||||||
|
|
||||||
|
**규칙:**
|
||||||
|
- 단일 문자 텍스트에서 `width > height × 1.5` → 90° 회전
|
||||||
|
- 일반 텍스트에서 `width < fontSize × 0.8` → 세로 배치용 좁은 박스 (writing-mode 아님, <br> 줄바꿈)
|
||||||
|
|
||||||
|
**CSS 구현:**
|
||||||
|
```css
|
||||||
|
.rotated-bracket { transform: rotate(90deg); } /* 여는 괄호 */
|
||||||
|
.rotated-bracket-close { transform: rotate(-90deg); } /* 닫는 괄호 */
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## R3. 세로 텍스트 (좁은 박스)
|
||||||
|
|
||||||
|
**문제:** Figma에서 좁은 박스(width < fontSize) 안에 텍스트를 넣으면 글자가 한 줄에 하나씩 배치됨.
|
||||||
|
|
||||||
|
**감지:** `bbox.width < fontSize × 0.8` + 2글자 이상
|
||||||
|
|
||||||
|
**CSS 구현:** `writing-mode` 사용하지 않음. HTML에서 `<br>`로 글자마다 줄바꿈.
|
||||||
|
```html
|
||||||
|
<span class="vlabel">기<br>술</span>
|
||||||
|
```
|
||||||
|
이유: `writing-mode: vertical-rl`은 Figma 원본과 다른 간격/정렬을 만듦.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## R4. 그라데이션 텍스트
|
||||||
|
|
||||||
|
**Figma:** 텍스트 fills에 GRADIENT_LINEAR이 있으면 그라데이션 텍스트.
|
||||||
|
|
||||||
|
**CSS:**
|
||||||
|
```css
|
||||||
|
.gradient-text {
|
||||||
|
background: linear-gradient(...);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**주의:** 흰 텍스트 스트로크(`-webkit-text-stroke: white`) 사용 금지.
|
||||||
|
HTML에서 보기 불편하므로 제거한다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## R5. 다중 fills 처리
|
||||||
|
|
||||||
|
**Figma:** 하나의 노드에 여러 fill이 쌓일 수 있음 (리스트 순서 = 위에서 아래).
|
||||||
|
|
||||||
|
**규칙:** 첫 번째 fill이 불투명(opacity 1)이면 나머지는 가려짐 → 첫 번째만 사용.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## R6. 중복 노드
|
||||||
|
|
||||||
|
**감지:** 동일 좌표 + 동일 내용 + 동일 크기 → Figma 복사 흔적.
|
||||||
|
|
||||||
|
**처리:** 1개만 렌더링, 나머지 무시. flat 목록에 [중복] 표기.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## R7. 미리보기 배경
|
||||||
|
|
||||||
|
**슬라이드 배경:** 항상 `#ffffff` (흰색)
|
||||||
|
**블록 배경:** 항상 `#ffffff` (미리보기용). 원본 배경색은 주석으로 기록.
|
||||||
|
|
||||||
|
이유: 다크 배경에서 요소가 안 보이는 문제 방지. 위치/크기 확인이 우선.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## R8. 스케일 팩터
|
||||||
|
|
||||||
|
**계산:** `Scale = 1280 / 원본_width`
|
||||||
|
|
||||||
|
**적용 대상:**
|
||||||
|
- 위치 (x, y)
|
||||||
|
- 크기 (width, height)
|
||||||
|
- 폰트 크기 (fontSize)
|
||||||
|
- 스트로크 너비 (strokeWeight)
|
||||||
|
- 간격 (gap, padding)
|
||||||
|
- 그림자 (blur, offset)
|
||||||
|
|
||||||
|
**적용하지 않는 것:**
|
||||||
|
- 색상 (그대로 유지)
|
||||||
|
- 그라데이션 방향/퍼센트 (그대로 유지)
|
||||||
|
- 폰트 굵기 (그대로 유지)
|
||||||
|
- line-height 비율 (그대로 유지)
|
||||||
|
- border-radius 비율 (스케일 적용)
|
||||||
|
|
||||||
|
**구현 권장:** 매 값 수동 곱셈 대신 `transform: scale(S)` 한 번으로 균일 축소. MATH.md §1 참조.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## R9. 순수 CSS 우선, SVG는 곡선/필터에만
|
||||||
|
|
||||||
|
블록 라이브러리의 동적 재구성을 위해 가능한 한 **HTML div + CSS**로 구현한다.
|
||||||
|
|
||||||
|
| 요소 | 구현 |
|
||||||
|
|------|------|
|
||||||
|
| 원/사각형 + linear-gradient | `<div>` + `border-radius` + `background: linear-gradient(...)` |
|
||||||
|
| Stroke (경계선) | `border` + `box-sizing: border-box` |
|
||||||
|
| Drop shadow blur | `box-shadow: 0 0 {2×stdDev}px {color}` |
|
||||||
|
| **곡선 (아크, 비원형 path)** | **SVG `<path>` 또는 PNG** ← CSS 불가능 |
|
||||||
|
| **복잡한 SVG filter chain** | **SVG `<filter>`** ← CSS 근사 불가 시 |
|
||||||
|
| 텍스트 | HTML `<div>` 절대 배치 |
|
||||||
|
|
||||||
|
**이유:** SVG `<img src="...svg">`는 정적 파일. 색상/개수/위치 변경 시 매번 재export 필요. CSS는 변수/Jinja로 즉시 파라미터화 가능.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## R10. Blend mode 호환 (plus-darker → multiply)
|
||||||
|
|
||||||
|
**문제:** Figma의 `plus darker` blend mode는 Apple CoreGraphics 전용. CSS 스펙엔 `plus-darker`가 있지만 **Safari/WebKit만 지원**, Chrome/Firefox에서는 무시되어 효과 사라짐.
|
||||||
|
|
||||||
|
**규칙:**
|
||||||
|
1. SVG/CSS에 `mix-blend-mode: plus-darker` 발견 시 → **`multiply`로 교체**
|
||||||
|
2. SVG 파일 내부의 `style="mix-blend-mode:plus-darker"`도 함께 교체
|
||||||
|
3. 시각 차이 검증: 흰 배경 위 밝은 그라데이션은 거의 동일. 어두운 영역은 multiply가 더 강함
|
||||||
|
|
||||||
|
```
|
||||||
|
plus-darker(src, dst) = max(0, src + dst - 1) [Safari only]
|
||||||
|
multiply(src, dst) = src × dst [모든 브라우저]
|
||||||
|
```
|
||||||
|
|
||||||
|
자세한 비교: MATH.md §7
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## R11. Stroke 정렬: viewBox padding 처리
|
||||||
|
|
||||||
|
SVG는 stroke가 fill의 안/밖으로 확장될 수 있어 viewBox에 padding이 들어감. CSS 변환 시 두 케이스로 나뉨:
|
||||||
|
|
||||||
|
### 케이스 A — Stroke가 fill **외부**
|
||||||
|
|
||||||
|
예: `r=140 fill` + `r=142.5 stroke-width=5` → stroke가 r=140~145 (외부)
|
||||||
|
|
||||||
|
```css
|
||||||
|
.ring {
|
||||||
|
width: 290px; height: 290px; /* fill 280 + 외부 stroke 5×2 */
|
||||||
|
border: 5px solid white;
|
||||||
|
box-sizing: border-box; /* border 안쪽 padding-box = 280 = fill */
|
||||||
|
background: linear-gradient(...); /* default origin: padding-box 280 */
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
/* 위치: Figma fill 위치에서 (-5, -5) 오프셋 */
|
||||||
|
```
|
||||||
|
|
||||||
|
### 케이스 B — Stroke가 fill **내부** (overlap)
|
||||||
|
|
||||||
|
예: `r=140 fill` + `r=137.5 stroke-width=5` → stroke가 r=135~140 (fill 외곽 overlap)
|
||||||
|
|
||||||
|
```css
|
||||||
|
.ring {
|
||||||
|
width: 280px; height: 280px; /* fill 280 그대로 */
|
||||||
|
border: 5px solid white;
|
||||||
|
box-sizing: border-box; /* padding-box 270 */
|
||||||
|
background: linear-gradient(...);
|
||||||
|
background-origin: border-box; /* gradient는 280 영역에 매핑 */
|
||||||
|
background-clip: border-box;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
/* 위치: Figma fill 위치 그대로 */
|
||||||
|
```
|
||||||
|
|
||||||
|
판별: SVG 안의 stroke `r` 값이 fill `r`보다 **크면** 외부 (케이스 A), **작거나 같으면** 내부 (케이스 B).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## R12. viewBox padding gradient remap
|
||||||
|
|
||||||
|
viewBox padding이 있는 SVG의 그라데이션 좌표는 viewBox 공간 기준이므로, CSS 박스로 매핑할 때 **각 좌표에서 padding 만큼 빼야** 한다.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# SVG viewBox 310, 실제 fill 280, padding 15
|
||||||
|
css_x1 = svg_x1 - 15
|
||||||
|
css_y1 = svg_y1 - 15
|
||||||
|
css_x2 = svg_x2 - 15
|
||||||
|
css_y2 = svg_y2 - 15
|
||||||
|
# 그 다음 svg_to_css(W=280, H=280, ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
또는 `scripts/gradient_math.py`의 `svg_to_css_remap()` 사용:
|
||||||
|
|
||||||
|
```python
|
||||||
|
svg_to_css_remap(css_W=280, css_H=280, viewbox_padding=15,
|
||||||
|
x1=..., y1=..., x2=..., y2=..., stops=[...])
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## R14. 한글 줄바꿈은 word-break: keep-all (전역 default)
|
||||||
|
|
||||||
|
**문제:** Chrome 기본 동작은 한글을 글자 단위로 wrap (예: "수행공정의 쉬운이해로 관리 편의성 증" / "진"). Figma는 단어 단위 wrap이라 시각이 다름.
|
||||||
|
|
||||||
|
**규칙:** 모든 변환물의 base CSS에 `word-break: keep-all` 적용.
|
||||||
|
|
||||||
|
```css
|
||||||
|
body {
|
||||||
|
font-family: 'Noto Sans KR', sans-serif;
|
||||||
|
...
|
||||||
|
word-break: keep-all; /* 한글 단어 단위 wrap (Figma matching) */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
또는 텍스트 컨테이너 단위로:
|
||||||
|
```css
|
||||||
|
.bullet-text, .left-text, .right-text, .body-text {
|
||||||
|
word-break: keep-all;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**언제 빼나:**
|
||||||
|
- `white-space: nowrap` 단일 라인 텍스트 (영향 없음, 안 빼도 무방)
|
||||||
|
- 코드/숫자 등 단어 경계가 없는 콘텐츠
|
||||||
|
|
||||||
|
**예외:** 영문/기호 혼합 텍스트는 `word-break: keep-all` 만으로는 부족할 수 있음. 그 경우 `overflow-wrap: anywhere` 또는 `<br>` 명시 split.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## R15. 박스 vertical center align (Figma flex justify-center 모방)
|
||||||
|
|
||||||
|
**문제:** Figma React 코드에서 자주 보이는 패턴:
|
||||||
|
```jsx
|
||||||
|
<div className="-translate-y-1/2 absolute flex flex-col h-[71px] justify-center top-[243.5px]">
|
||||||
|
```
|
||||||
|
|
||||||
|
이는 **컨테이너 박스의 vertical center에 텍스트를 정렬**한다는 의미. 단순히 `top` 값만 받아서 박는 건 잘못 — 텍스트가 박스 top에 붙어 다른 요소(예: cat pill의 vertical center)와 어긋남.
|
||||||
|
|
||||||
|
**올바른 변환:**
|
||||||
|
|
||||||
|
```css
|
||||||
|
.text-box {
|
||||||
|
position: absolute;
|
||||||
|
top: <visual_top>; /* Figma top - height/2 */
|
||||||
|
height: <figma_height>;
|
||||||
|
width: <figma_width>;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center; /* vertical center */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**또는 인접 박스(예: 옆에 있는 cat pill)와 동일한 top + height를 박고 flex justify-center 적용**하면 자동으로 가운데 align. 1:1 변환에서 가장 안전.
|
||||||
|
|
||||||
|
**검증:** 인접 박스 center y 와 텍스트 박스 center y 가 같은지 측정. 차이 > 5px이면 잘못된 것.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## R13. Custom-Marker Bullet List 패턴 (sub-pattern)
|
||||||
|
|
||||||
|
**감지 조건 (3가지 모두 충족):**
|
||||||
|
1. 여러 텍스트 항목이 세로로 나열됨
|
||||||
|
2. 각 항목 앞에 **장식 마커**가 있음 (체크박스 아이콘, 점, 화살표, 숫자, 원, PNG 등)
|
||||||
|
3. 마커는 인터랙티브하지 않고 순수 시각 요소 (실제 `<input type="checkbox">` 가 아님)
|
||||||
|
|
||||||
|
**Figma 원본에서는** 마커와 텍스트가 별도 요소로 평면 배치돼있을 수 있다. 그래도 **시맨틱적으로는 하나의 list item**으로 봐야 한다.
|
||||||
|
|
||||||
|
### 구조 (CSS Flex Pair Pattern)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="bullet-list" style="--icon-gap: ...;">
|
||||||
|
<div class="bullet-row">
|
||||||
|
<span class="bullet-icon"><img src="marker.png"></span>
|
||||||
|
<span class="bullet-text">텍스트 항목</span>
|
||||||
|
</div>
|
||||||
|
<div class="bullet-row compact">
|
||||||
|
<span class="bullet-icon"><img src="marker.png"></span>
|
||||||
|
<span class="bullet-text">긴 텍스트가<br>두 줄로</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### CSS
|
||||||
|
|
||||||
|
```css
|
||||||
|
.bullet-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
/* 동일 top/bottom 정렬을 위해 컨테이너에 fixed height + space-between */
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.bullet-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
--lh: 85px; /* 기본 라인 높이 */
|
||||||
|
}
|
||||||
|
.bullet-row.compact {
|
||||||
|
--lh: 50px; /* 2-line 항목용 타이트 lh */
|
||||||
|
}
|
||||||
|
.bullet-icon {
|
||||||
|
flex: none;
|
||||||
|
width: var(--icon-w);
|
||||||
|
height: var(--icon-h);
|
||||||
|
/* 핵심: 아이콘 vertical center를 첫 줄 vertical center에 align */
|
||||||
|
margin-top: calc(var(--lh) / 2 - var(--icon-h) / 2);
|
||||||
|
/* 컬럼별 figma gap (text_left − icon_left − icon_w) */
|
||||||
|
margin-right: var(--icon-gap);
|
||||||
|
}
|
||||||
|
.bullet-text {
|
||||||
|
flex: 1;
|
||||||
|
line-height: var(--lh);
|
||||||
|
white-space: normal;
|
||||||
|
word-break: keep-all; /* 한글: 단어 단위 줄바꿈 */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 핵심 수학
|
||||||
|
|
||||||
|
```
|
||||||
|
icon margin-top = lh / 2 − icon_h / 2 (첫 줄 vertical center)
|
||||||
|
icon margin-right = text_left − icon_left − icon_w (Figma 데이터)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 절대 하지 말 것
|
||||||
|
|
||||||
|
- 마커와 텍스트를 별도 요소로 절대 배치 (`<div class="checkbox" style="left:..; top:..">` × N)
|
||||||
|
- row에 fixed `height` 설정 (wrap 시 overlap)
|
||||||
|
- `white-space: nowrap` (텍스트가 컨테이너 밖으로 overflow)
|
||||||
|
- 모든 row에 동일한 top/bottom margin 강제 (텍스트 길이가 결정해야 함)
|
||||||
|
|
||||||
|
### 정렬 원칙
|
||||||
|
|
||||||
|
3개 이상의 평행한 컬럼이 있을 때:
|
||||||
|
- **모든 컬럼은 동일한 top + 동일한 height** 로 시작
|
||||||
|
- 컬럼별 자연 콘텐츠 합 중 **가장 큰 값**을 height로 사용
|
||||||
|
- `justify-content: space-between` 으로 내부 균등 분포
|
||||||
|
- 결과: 컬럼별 spacing은 다르지만 vertical extent는 동일
|
||||||
|
|
||||||
|
### 적용 사례
|
||||||
|
|
||||||
|
| 프레임 | 사용 | 비고 |
|
||||||
|
|--------|------|------|
|
||||||
|
| 1171281191 (cards-3col-persona) | 3 컬럼 × 6~7 마커-text 페어 | 첫 적용 |
|
||||||
|
| (앞으로 비슷한 패턴 발견 시 추가) | | |
|
||||||
|
|
||||||
|
### 1:1 변환 단계의 임시 보정 (템플릿화 시 제거)
|
||||||
|
|
||||||
|
다음은 1:1 시각 fidelity를 위한 **임시 보정**이며, 템플릿화 시 모두 제거해야 한다 (자연 wrap이 처리):
|
||||||
|
|
||||||
|
- `letter-spacing: -1.5px` 등 — Chrome Noto Sans KR 너비가 Figma보다 약간 넓어 wrap이 일어나는 것을 방지하기 위한 보정
|
||||||
|
- `<br>` 명시적 줄바꿈 — Figma의 의도된 split 위치 보존용. 템플릿화 시 자연 wrap이 알아서 처리
|
||||||
|
- `class="compact"` 수동 지정 — 어떤 항목이 2-line인지 1:1 단계에선 수동, 템플릿화 시 텍스트 길이 자동 판정
|
||||||
|
|
||||||
|
이 보정들은 HTML 코멘트로 `<!-- TEMP: 1:1 fidelity, 템플릿화 시 제거 -->` 표시한다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## R16. 이미지 프레임 배치 — overflow:hidden으로 부분 표시
|
||||||
|
|
||||||
|
**상황:** 하나의 원본 이미지에 양쪽 끝 모두 디자인 요소(곡선, 말림, 장식 등)가 있고, Figma에서 프레임(컨테이너)보다 이미지를 크게 배치하여 **한쪽만 보이게** 하는 경우.
|
||||||
|
|
||||||
|
**Figma가 하는 것:**
|
||||||
|
- 프레임: 457.96px (표시 영역)
|
||||||
|
- 이미지: 664px (원본, 프레임보다 큼)
|
||||||
|
- 이미지를 프레임 안에서 `left`, `width`로 위치/크기 지정
|
||||||
|
- 프레임에 `overflow: hidden` → 프레임 밖으로 나간 부분 안 보임
|
||||||
|
- 결과: 이미지의 **원하는 쪽만** 프레임 안에 보임
|
||||||
|
|
||||||
|
**Figma가 주는 값의 의미:**
|
||||||
|
```
|
||||||
|
left: -45.3%; width: 145.3%
|
||||||
|
→ 이미지를 좌측으로 45.3% 밀어서 배치
|
||||||
|
→ 좌측 끝이 프레임 밖으로 나감 → 좌측 디자인 요소 안 보임
|
||||||
|
→ 우측 디자인 요소만 프레임 안에 보임
|
||||||
|
|
||||||
|
left: 0; width: 151.25%
|
||||||
|
→ 이미지를 좌측 정렬, 우측이 프레임 밖으로 넘침
|
||||||
|
→ 우측 디자인 요소 안 보임
|
||||||
|
→ 좌측 디자인 요소만 프레임 안에 보임
|
||||||
|
```
|
||||||
|
|
||||||
|
**이것은 crop이 아니다.** 이미지를 자르는 것이 아니라, 프레임 안에서 이미지의 **위치**를 조절하는 것. 이미지 원본은 그대로 유지.
|
||||||
|
|
||||||
|
**CSS 구현:**
|
||||||
|
```css
|
||||||
|
.pill-frame {
|
||||||
|
position: relative; /* 또는 absolute */
|
||||||
|
width: 457.96px; /* 프레임 크기 */
|
||||||
|
height: 95.62px;
|
||||||
|
overflow: hidden; /* 핵심: 프레임 밖 숨김 */
|
||||||
|
}
|
||||||
|
.pill-frame img {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -45.3%; /* Figma 값 그대로 */
|
||||||
|
width: 145.3%; /* Figma 값 그대로 */
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**절대 하지 말 것:**
|
||||||
|
- `width: 100%; object-fit: fill` — 이미지가 찌그러져 양쪽 디자인 요소가 다 보임
|
||||||
|
- `scaleX(-1)` 임의 추가 — Figma에 없는 변환
|
||||||
|
- `object-fit: cover/contain` — 이미지 비율/위치가 달라짐
|
||||||
|
- "crop"이라 부르기 — 이미지를 자르는 게 아니라 위치를 조절하는 것
|
||||||
|
|
||||||
|
**rotate(180deg) + 이미지 배치 주의:**
|
||||||
|
|
||||||
|
부모에 `rotate(180deg)`가 적용된 경우 (예: 하단 pill), 이미지가 상하좌우 모두 뒤집힘. 이때 **이미지 배치(left/width)를 상단과 반대로** 적용해야 최종 결과가 올바른 방향이 됨.
|
||||||
|
|
||||||
|
```
|
||||||
|
상단 left-pill: left: -45.3%; width: 145.3% → 우측 보임
|
||||||
|
하단 left-pill: rotate(180) + left: 0; width: 151.25% → 결과적으로 우측 보임 (뒤집혀서)
|
||||||
|
|
||||||
|
상단 right-pill: left: 0; width: 151.25% → 좌측 보임
|
||||||
|
하단 right-pill: rotate(180) + left: -45.3%; width: 145.3% → 결과적으로 좌측 보임 (뒤집혀서)
|
||||||
|
```
|
||||||
|
|
||||||
|
**검증 방법:** 각 pill을 개별 screenshot으로 뽑아서 Figma 원본 pill screenshot과 **곡선/직선 위치**를 1:1 대조. 양쪽 다 곡선이 보이면 이미지 배치가 잘못된 것.
|
||||||
|
|
||||||
|
**적용 사례:**
|
||||||
|
| 프레임 | 사용 | 비고 |
|
||||||
|
|--------|------|------|
|
||||||
|
| 1171281194 (issues-paired-rows) | 두루마리 pill 8개 | 첫 적용. 상/하 배치 반전 패턴 발견. |
|
||||||
BIN
figma_to_html_agent/block-tests/_full.png
Normal file
|
After Width: | Height: | Size: 269 KiB |
BIN
figma_to_html_agent/block-tests/_renders/_current.png
Normal file
|
After Width: | Height: | Size: 229 KiB |
BIN
figma_to_html_agent/block-tests/_renders/_detail_2x.png
Normal file
|
After Width: | Height: | Size: 352 KiB |
BIN
figma_to_html_agent/block-tests/_renders/_fix_overlap.png
Normal file
|
After Width: | Height: | Size: 229 KiB |
BIN
figma_to_html_agent/block-tests/_renders/_full.png
Normal file
|
After Width: | Height: | Size: 676 KiB |
BIN
figma_to_html_agent/block-tests/_renders/_full2.png
Normal file
|
After Width: | Height: | Size: 711 KiB |
BIN
figma_to_html_agent/block-tests/_renders/_full3.png
Normal file
|
After Width: | Height: | Size: 710 KiB |
BIN
figma_to_html_agent/block-tests/_renders/_full4.png
Normal file
|
After Width: | Height: | Size: 709 KiB |
BIN
figma_to_html_agent/block-tests/_renders/_full5.png
Normal file
|
After Width: | Height: | Size: 714 KiB |
BIN
figma_to_html_agent/block-tests/_renders/_full6.png
Normal file
|
After Width: | Height: | Size: 715 KiB |
BIN
figma_to_html_agent/block-tests/_renders/_full7.png
Normal file
|
After Width: | Height: | Size: 714 KiB |
BIN
figma_to_html_agent/block-tests/_renders/_full8.png
Normal file
|
After Width: | Height: | Size: 714 KiB |
BIN
figma_to_html_agent/block-tests/_renders/_full_180.png
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
figma_to_html_agent/block-tests/_renders/_full_193.png
Normal file
|
After Width: | Height: | Size: 406 KiB |
BIN
figma_to_html_agent/block-tests/_renders/_full_193f.png
Normal file
|
After Width: | Height: | Size: 406 KiB |
BIN
figma_to_html_agent/block-tests/_renders/_full_194.png
Normal file
|
After Width: | Height: | Size: 211 KiB |
BIN
figma_to_html_agent/block-tests/_renders/_full_194_v2.png
Normal file
|
After Width: | Height: | Size: 226 KiB |
BIN
figma_to_html_agent/block-tests/_renders/_full_195.png
Normal file
|
After Width: | Height: | Size: 380 KiB |
BIN
figma_to_html_agent/block-tests/_renders/_full_202.png
Normal file
|
After Width: | Height: | Size: 228 KiB |
BIN
figma_to_html_agent/block-tests/_renders/_full_207.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 134 KiB |