Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfe9225967 | ||
|
|
5210aea00f | ||
|
|
761a43da5e | ||
|
|
8a201337f7 | ||
|
|
aca6a992b9 | ||
|
|
565e6b092e | ||
|
|
b6b9173d2b | ||
|
|
4320f5e838 | ||
|
|
425a3054c1 | ||
|
|
02a6d44944 | ||
|
|
3f843d73f7 | ||
|
|
4144dc4c21 | ||
|
|
2ec8fc5a77 | ||
|
|
e7848b602d | ||
|
|
79f0c55745 | ||
|
|
620da8de13 | ||
|
|
b60a089e6c | ||
|
|
a385ba08c0 | ||
|
|
8e577b3163 | ||
|
|
7a2615fb4b | ||
|
|
46e53beeaf | ||
|
|
1ff6c6cbb2 | ||
|
|
05d43a7999 | ||
|
|
66c00924ed | ||
|
|
51548fdc41 | ||
|
|
360cd8e44c | ||
|
|
c42e01f060 | ||
|
|
d57860578f | ||
|
|
05703c8e72 | ||
|
|
076aeb0403 | ||
|
|
3d1194a562 | ||
|
|
cba2ec2be7 | ||
|
|
b13df8b176 | ||
|
|
bc7c08e575 | ||
|
|
571b057f19 | ||
|
|
51f61012c3 | ||
|
|
b2a49f55ef | ||
|
|
f568e5c95d | ||
|
|
095abdf9af | ||
|
|
028f611070 | ||
|
|
17e77e310f | ||
|
|
82f25caa6e | ||
|
|
d4eaec694c | ||
|
|
ef9bae7711 | ||
|
|
4f0105926d | ||
|
|
42d60e44a5 | ||
|
|
3719704d75 | ||
|
|
6b17f448eb | ||
|
|
56fd9fa71e | ||
|
|
c4d7212ff3 | ||
|
|
a8fe20e08e | ||
|
|
bc7829b08b | ||
|
|
c9677a69f8 |
@@ -0,0 +1,134 @@
|
||||
{
|
||||
"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_*/)",
|
||||
"Bash(python -c \"from src.config import settings; print\\(f'API configured: {bool\\(settings.anthropic_api_key\\)}'\\)\")",
|
||||
"Bash(python run_test.py)",
|
||||
"Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:8000/health)",
|
||||
"Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:8000/)",
|
||||
"Read(//d/ad-hoc/kei/**)",
|
||||
"Bash(curl -s http://localhost:8000/docs)",
|
||||
"Bash(taskkill //F //IM python.exe)",
|
||||
"Bash(python assemble_mdx02_test.py)",
|
||||
"Bash(wc -c data/runs/20260407_*/final.html)",
|
||||
"Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:8000/docs)",
|
||||
"Bash(curl -s http://localhost:8080/docs -o /dev/null -w \"%{http_code}\")",
|
||||
"Bash(curl -s http://localhost:8001/docs -o /dev/null -w \"%{http_code}\")",
|
||||
"Bash(curl -s http://localhost:3000/ -o /dev/null -w \"%{http_code}\")",
|
||||
"Bash(python assemble_mdx02_v3.py)",
|
||||
"Bash(python assemble_mdx02_v4.py)",
|
||||
"Bash(python assemble_mdx02_v5.py)",
|
||||
"Bash(python assemble_mdx02_v6.py)",
|
||||
"Bash(python assemble_mdx02_v7.py)",
|
||||
"Bash(python assemble_mdx02_v8_3plans.py)",
|
||||
"Bash(python assemble_mdx02_v9.py)",
|
||||
"Bash(python build_plan1.py)",
|
||||
"Bash(python build_plan2.py)",
|
||||
"Bash(python build_plan3.py)",
|
||||
"Bash(python build_plan1_v2.py)",
|
||||
"Bash(python build_4plans_final.py)",
|
||||
"Bash(curl -s -o /dev/null -w \"%{http_code}\" http://127.0.0.1:8000/docs)",
|
||||
"Bash(uvicorn backend.main:app --port 8000)",
|
||||
"Bash(python final_plan1.py)",
|
||||
"Bash(python final_plan2.py)",
|
||||
"Bash(python build_all_4plans.py)",
|
||||
"Bash(python build_plan3_kei.py)",
|
||||
"Bash(python build_plan4_kei.py)",
|
||||
"Bash(python make_4plans.py)",
|
||||
"Bash(ls -la \"d:/ad-hoc/kei/design_agent/figma_to_html_agent/block-tests/html_render_final\"*)",
|
||||
"Bash(taskkill //PID 48540 //F)",
|
||||
"Bash(python make_mdx03.py)",
|
||||
"Bash(python run_mdx03_pipeline.py)",
|
||||
"Bash(powershell -Command \"Get-Process python -ErrorAction SilentlyContinue | Select-Object Id,StartTime\")",
|
||||
"Bash(grep \"class FontHierarchy\" src/*.py)",
|
||||
"Bash(ls -ltr /d/ad-hoc/kei/design_agent/data/runs/*/step_*_context.json)",
|
||||
"Bash(awk '{print $2}')",
|
||||
"Bash(stat /d/ad-hoc/kei/design_agent/data/runs/20260414_120225/stage_*_context.json)",
|
||||
"Bash(ls -la /d/ad-hoc/kei/design_agent/data/runs/20260414_120225/stage_*_context.json)",
|
||||
"Bash(awk '{print $6, $7, $8, $9}')",
|
||||
"Bash(ls -lt data/runs/20260414_120225/*_context.json)",
|
||||
"Bash(awk '{print $6,$7,$8,$9}')",
|
||||
"Bash(python -c \" import yaml with open\\('catalog.yaml'\\) as f: data = yaml.safe_load\\(f\\) blocks = data.get\\('blocks', []\\) for b in blocks: print\\(f\\\\\"{b['id']} | {b.get\\('category',''\\)} | items:{b.get\\('min_items','?'\\)}-{b.get\\('max_items','?'\\)}\\\\\"\\) \")",
|
||||
"Bash(python add_tags.py)",
|
||||
"Bash(python -c \"import src.block_reference; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.block_assembler; print\\('OK'\\)\")",
|
||||
"Bash(ls -t d:/ad-hoc/kei/design_agent/docs/history/PHASE-*.md d:/ad-hoc/kei/design_agent/docs/history/IMPROVEMENT-PHASE-*.md)",
|
||||
"Bash(python -c \"import src.pipeline_context; import src.kei_client; import src.pipeline; print\\('모든 import OK'\\)\")",
|
||||
"Bash(python -c \"import src.step_visualizer; import src.pipeline; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.pipeline; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.pipeline; import src.block_assembler; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.block_assembler; import src.pipeline; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.kei_client; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.pipeline; import src.block_assembler; import src.pipeline_context; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.pipeline; import src.validators; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.validators; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.validators; import src.pipeline; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.pipeline; import src.section_parser; import src.block_assembler; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.pipeline; import src.block_assembler; import src.section_parser; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.pipeline; import src.block_assembler; import src.space_allocator; import src.pipeline_context; print\\('OK'\\)\")",
|
||||
"Bash(grep -l \"pp2-grid-wrap\\\\|pp2\" templates/blocks/**/*.html)",
|
||||
"Bash(echo file:///D:/ad-hoc/kei/design_agent/data/runs/20260415_110323/final.html)",
|
||||
"Bash(python -c \"import src.block_reference; import src.section_parser; import src.pipeline; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.block_assembler; import src.section_parser; import src.pipeline; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.block_reference; import src.pipeline; import src.block_assembler; print\\('OK'\\)\")"
|
||||
],
|
||||
"additionalDirectories": [
|
||||
"d:\\ad-hoc\\kei\\design_agent\\templates\\blocks\\new"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,8 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"Framelink Figma MCP": {
|
||||
"command": "cmd",
|
||||
"args": [
|
||||
"/c",
|
||||
"npx",
|
||||
"-y",
|
||||
"figma-developer-mcp",
|
||||
"--figma-api-key=figd_s23TfSDL0hS97DIialy0R2P6QsoZQHfuGx1l_t-k",
|
||||
"--stdio"
|
||||
]
|
||||
"figma-desktop": {
|
||||
"type": "sse",
|
||||
"url": "http://127.0.0.1:3845/sse"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
**목표:** design_agent의 아키텍처, 파이프라인, 코드 구조를 체계적으로 이해
|
||||
|
||||
@@ -12,10 +12,89 @@
|
||||
|
||||
---
|
||||
|
||||
## 아키텍처 (Phase Q 파이프라인)
|
||||
## 위계 + 용어 (Phase Z 정리, 2026-04-28)
|
||||
|
||||
> 매칭 시스템 (V1~V4) 통합 설계 시 정리. 상세는 [`IMPROVEMENT-REDESIGN.md`](IMPROVEMENT-REDESIGN.md) 4 장 참조.
|
||||
>
|
||||
> Phase Z 사전 작업 — Frame Zone 적용 분류 [`docs/architecture/FRAME-INTEGRATION-MAP.md`](docs/architecture/FRAME-INTEGRATION-MAP.md), Frame / Style / Token 인벤토리 [`docs/architecture/PHASE-Z-FRAME-STYLE-INVENTORY.md`](docs/architecture/PHASE-Z-FRAME-STYLE-INVENTORY.md).
|
||||
|
||||
### 슬라이드 위계
|
||||
|
||||
```
|
||||
[ slide ] 1280×720 (전체 슬라이드)
|
||||
│
|
||||
├─ slide-title ← MDX 대목차 제목 (자동 매핑)
|
||||
├─ slide-divider (고정)
|
||||
│
|
||||
├─ slide-body ≈ 1200×590 (콘텐츠 영역 — `templates/blocks/slide-base.html`)
|
||||
│ │
|
||||
│ └─ 레이아웃 (8-preset layout vocabulary; legacy hint = Type A/B/B'/B'')
|
||||
│ │
|
||||
│ └─ Zone (top / bottom_l / bottom_r 등)
|
||||
│ │
|
||||
│ └─ 프레임 (Figma 디자인 단위)
|
||||
│
|
||||
└─ slide-footer ← MDX 대목차 결론 (자동 매핑)
|
||||
```
|
||||
|
||||
### 용어
|
||||
|
||||
| 용어 | 의미 |
|
||||
|---|---|
|
||||
| **슬라이드** | 1280×720 한 장 |
|
||||
| **slide-base** | 모든 슬라이드 공통 그릇 (배경 + 제목 + 구분선 + 결론 pill) — `templates/blocks/slide-base.html` |
|
||||
| **slide-body** | 본문 가용 영역 (≈ 1200×590) |
|
||||
| **레이아웃 (Layout)** | 8-preset layout vocabulary — slide-body 안 zone 분배 형태 (legacy hint = Type A/B/B'/B'') |
|
||||
| **Zone (영역)** | 레이아웃이 결정한 콘텐츠 구역 |
|
||||
| **Internal Region** | Zone 안의 placement / planning unit (Layer A) — content_object 를 배치하는 내부 영역 (SPEC v1 §2) |
|
||||
| **컨테이너 (Container)** | zone 의 px 명세 (코드 레벨) |
|
||||
| **프레임 (Frame)** | Figma 디자인 단위 (= 기존 "블록") — zone 안 |
|
||||
| **Frame Slot** | Frame 안의 declared placement slot (Layer B) — frame contract 가 선언하는 콘텐츠 placement target (SPEC v1 §3) |
|
||||
| **Layer A** | composition planning 의 zone-내 layer — content_object 를 Internal Region 으로 배치하는 단위 (SPEC v1 §1~§2) |
|
||||
| **Layer B** | frame contract 기반 frame-내 layer — 콘텐츠 매핑 대상인 Frame Slot 구조를 선언하는 단위 (SPEC v1 §3) |
|
||||
|
||||
### MDX → 슬라이드 매핑
|
||||
|
||||
| MDX 위치 | 슬라이드 위치 |
|
||||
|---|---|
|
||||
| `# 대목차 제목` | `slide-title` |
|
||||
| 본문 (`##` / `###`) | `slide-body` 안 (레이아웃 + zone + 프레임) |
|
||||
| `# 대목차 결론` | `slide-footer` |
|
||||
| `<details>` 팝업 | 슬라이드 위 별도 레이어 |
|
||||
|
||||
Layer A planning telemetry is active in trace-only mode; render path activation remains the next axis.
|
||||
|
||||
---
|
||||
|
||||
## 아키텍처 — Phase Z 흐름 (5 단계)
|
||||
|
||||
> 상세는 [`IMPROVEMENT-REDESIGN.md`](IMPROVEMENT-REDESIGN.md) 5 장 참조.
|
||||
|
||||
```
|
||||
STAGE 1) MDX 분석 + 레이아웃 매칭 (8-preset layout vocabulary; legacy = Type A/B/B'/B'')
|
||||
STAGE 2) Zone 별 텍스트 1차 배치
|
||||
STAGE 3) Zone 별 프레임 매칭 (V1~V4; B4 frame_selection evidence integration pending)
|
||||
├ 매칭 완벽 → 텍스트 업데이트
|
||||
├ 매칭 어정쩡 → 디자인 참고 재구성
|
||||
└ 매칭 안 됨 → 디자인 컨셉 바탕 재구성
|
||||
STAGE 4) 프레임 검토 + 컨테이너 조정 (5 차 Fallback)
|
||||
STAGE 5) HTML 조립 + 검증 + 출력
|
||||
```
|
||||
|
||||
핵심 원칙 :
|
||||
- **MDX 1 파일 = 슬라이드 1 장** (절대 분할 X)
|
||||
- **텍스트 원문 무손실 보존** (본문 미리보기 + 팝업 원문)
|
||||
- **자유 디자인 금지** (항상 Figma 프레임 DB 참고)
|
||||
- **불일치 시 레이아웃 회귀** (콘텐츠 줄이지 않고 그릇 변경)
|
||||
|
||||
---
|
||||
|
||||
## 아키텍처 (Phase Q 파이프라인 — 기존)
|
||||
|
||||
> Phase P(다후보 렌더링 비교) 실행 결과 20/100점. 업계 조사(Beautiful.ai, Napkin.ai, VASCAR 등) 기반으로 Phase Q에서 재설계.
|
||||
> 핵심 전환: "계산 먼저, AI 판단 나중에, 렌더링은 검증만"
|
||||
>
|
||||
> ⚠️ Phase Z (매칭 시스템 통합) 진행 후 이 섹션의 일부는 새 흐름으로 대체될 예정.
|
||||
|
||||
```
|
||||
[1단계] Kei 실장 (Opus) — AI 사고
|
||||
@@ -65,30 +144,48 @@ reference 꼭지 있음 → sidebar-right
|
||||
나머지 → single-column
|
||||
```
|
||||
|
||||
### 역할 분리 (Phase R')
|
||||
### 역할 분리 (Phase Z)
|
||||
|
||||
> ⚠️ Phase R' 의 역할 분리 (AI 가 HTML 구조 직접 생성) 는 **Phase Z 에서 폐기**. 아래는 Phase Z 기준.
|
||||
|
||||
| 역할 | 담당 | 방식 | 하는 일 | 하지 않는 일 |
|
||||
|------|------|------|---------|------------|
|
||||
| Kei 실장 | Opus (Kei API) | AI | 꼭지 추출, 비중 판단, relation_type + expression_hint 부여, 최종 검수 | HTML 생성, 레이아웃 계산 |
|
||||
| 컨테이너 계산 | 코드 | 결정론적 | Kei 비중 → 역할별 컨테이너 px 확정 | AI 판단 불필요 |
|
||||
| 프리셋 선택 | 코드 | 규칙 | 실장의 role에 따라 프리셋 자동 선택 | AI 판단 불필요 |
|
||||
| **HTML 생성** | **AI (Kei API)** | **AI** | **콘텐츠 전달 의도에 맞는 HTML 구조를 직접 생성. 블록 CSS를 참고하되 구조는 AI가 결정.** | 블록 "선택" 안 함. 슬롯 "채우기" 안 함. |
|
||||
| 검증 | 코드 + AI | Selenium + 비전 모델 | overflow 측정, 시각 품질 평가 | |
|
||||
| Kei 실장 | Opus (Kei API) | AI | MDX 본문 분석 보조, 최종 검수 | HTML 생성, 레이아웃 계산 |
|
||||
| 레이아웃 결정 | 코드 | 룰 매칭 | MDX 콘텐츠 → 8-preset layout vocabulary 기반 slide/zone topology 선택 (legacy hint = Type A/B/B'/B'') | — |
|
||||
| 컨테이너 계산 | 코드 | 결정론적 | zone 별 px 확정 (`space_allocator`) | — |
|
||||
| 프레임 매칭 | 코드 (V1~V4; B4 frame_selection evidence integration pending) | 결정론적 | zone ↔ Figma 프레임 매칭 | — |
|
||||
| **콘텐츠 매핑** | **코드 + AI** | **하이브리드** | **zone 안 콘텐츠 / 텍스트를 프레임 슬롯에 배치 / 다듬기 / 변형** | **HTML 구조 생성 X** |
|
||||
| HTML 조립 | 코드 (Jinja2) | 결정론적 | `slide-base` + 프레임 + 슬롯 콘텐츠 합쳐서 final.html | AI 호출 X |
|
||||
| 검증 | 코드 + AI | Selenium + Vision | overflow 측정, 시각 품질 평가 | — |
|
||||
|
||||
### HTML 생성 원칙 (Phase R')
|
||||
### HTML 생성 원칙 (Phase Z)
|
||||
|
||||
```
|
||||
블록이 구조를 결정 (P=Q=R, 실패) → 콘텐츠가 구조를 결정 (R', 접근 C)
|
||||
블록이 구조를 결정 (P=Q=R, 실패)
|
||||
콘텐츠가 구조를 결정 (R', 폐기 — AI 가 HTML 직접 생성하면 회귀 가능성 큼)
|
||||
→ slide-base + 프레임 DB 가 구조를 결정, AI 는 zone 안 콘텐츠만 (Phase Z)
|
||||
```
|
||||
|
||||
- AI가 콘텐츠 전달 의도(expression_hint)에 맞는 HTML 구조를 직접 생성
|
||||
- 기존 38개 블록의 CSS(색상, 폰트, 배경, radius)를 **스타일 참고**로 활용
|
||||
- 블록을 **"선택"하지 않음**. topic 합침/분리, 포함 관계, 핵심 메시지 분리 가능
|
||||
- 디자인 토큰(CSS 변수)으로 품질 제약 + Selenium 측정 + 비전 모델 검증
|
||||
**Phase Z 의 핵심 원칙**:
|
||||
- **HTML 구조** = `slide-base.html` + 코드 (Jinja2) 가 결정 (절대 AI 가 생성 X)
|
||||
- **AI 의 역할** = zone 안 콘텐츠 매핑 / 텍스트 다듬기 / 디자인 변형 (콘텐츠 단위)
|
||||
- **프레임 DB 참고 필수** — 자유 디자인 금지
|
||||
- **MDX 원문 무손실 보존** (본문 preview, 팝업에 원문)
|
||||
|
||||
⚠️ **Phase R' 회귀 방지**: AI 한테 "HTML 구조 만들어줘" 같은 호출 절대 X. AI 호출은 zone 안 콘텐츠 단위로만.
|
||||
|
||||
---
|
||||
|
||||
## 핵심 프로세스
|
||||
## 핵심 프로세스 (구 Phase Q 기준 — Phase Z 에서 대체됨)
|
||||
|
||||
> ⚠️ **아래 흐름은 구 Phase Q 기준의 5 단계 (Kei 실장 → 디자인 팀장 → 텍스트 편집자 → 디자인 실무자 → 재검토). Phase Z 에서 대체됨.**
|
||||
>
|
||||
> **신규 흐름은 위 "아키텍처 — Phase Z 흐름 (5 단계)" 섹션 또는 [IMPROVEMENT-REDESIGN.md](IMPROVEMENT-REDESIGN.md) 5 장 따름.**
|
||||
>
|
||||
> ⚠️ **특히 Phase Z 와 충돌하므로 무시할 것**:
|
||||
> - "페이지 분리" → Phase Z: MDX 1 파일 = 슬라이드 1 장 (절대 분할 X)
|
||||
> - "텍스트 압축 / 요약" → Phase Z: MDX 원문 무손실 보존 (본문 preview, 팝업 원문)
|
||||
> - "AI 5 단계" → Phase Z: AI 호출은 zone 안 콘텐츠 단위로만 (HTML 구조 / 레이아웃 / 프리셋 결정 X)
|
||||
|
||||
```
|
||||
사용자 콘텐츠 입력 (텍스트/MDX 붙여넣기 또는 파일 업로드)
|
||||
|
||||
@@ -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. 업데이트 전/후 비교
|
||||
@@ -393,6 +393,36 @@ P2-E (누락기능) ── 병렬 │
|
||||
|
||||
---
|
||||
|
||||
## Phase Y: MDX 외부 컴포넌트 인라인 삽입
|
||||
|
||||
> 근거: MDX에서 `import ... from '*.astro'`로 불러오는 외부 컴포넌트(표, 다이어그램 등)가 파이프라인에서 누락됨. import문은 제거되고 `<DxEffect />` 같은 태그는 사라져서 콘텐츠 손실 발생.
|
||||
|
||||
### Y-1: import문 파싱 — 컴포넌트명:파일경로 매핑
|
||||
- **파일:** `src/mdx_normalizer.py`
|
||||
- **내용:** `import Foo from '../../components/foo.astro'` → `{"Foo": 절대경로}` 매핑 추출
|
||||
- **의존성:** base_path (MDX 원본 파일 위치, pipeline.py에서 전달)
|
||||
- **완료 기준:** import문에서 컴포넌트명→절대경로 dict 반환
|
||||
|
||||
### Y-2: .astro 파일 파싱 — HTML + CSS 추출
|
||||
- **파일:** `src/mdx_normalizer.py`
|
||||
- **내용:** .astro 파일에서 `---` frontmatter 제거, HTML 본문 + `<style>` 블록 추출
|
||||
- **의존성:** Y-1
|
||||
- **완료 기준:** dx.astro → `<div class="table-wrapper">...</div>` + `<style>...</style>` 반환
|
||||
|
||||
### Y-3: 셀프클로징 태그 교체 — 인라인 삽입
|
||||
- **파일:** `src/mdx_normalizer.py`
|
||||
- **내용:** `<DxEffect />` 태그를 Y-2에서 추출한 HTML+CSS로 교체
|
||||
- **의존성:** Y-1, Y-2
|
||||
- **완료 기준:** MDX 정규화 결과에 외부 컴포넌트 HTML이 인라인으로 포함
|
||||
|
||||
### Y-4: Astro 특수 문법 정리
|
||||
- **파일:** `src/mdx_normalizer.py`
|
||||
- **내용:** Astro의 멀티라인 태그(`<td class="category-cell">텍스트</td>` 줄바꿈 패턴), `style="letter-spacing: -0.9px"` 등 인라인 스타일 정리
|
||||
- **의존성:** Y-2
|
||||
- **완료 기준:** 추출된 HTML이 브라우저에서 정상 렌더링
|
||||
|
||||
---
|
||||
|
||||
## 의존 관계
|
||||
|
||||
```
|
||||
|
||||
@@ -1,303 +1,124 @@
|
||||
# Kei Design Agent
|
||||
# C.E.L. Slide Pipeline
|
||||
|
||||
콘텐츠를 시각적으로 구조화된 슬라이드 HTML(1280×720px, 16:9)로 변환하는 AI 파이프라인.
|
||||
## 이 프로젝트는 무엇인가
|
||||
|
||||
## 개요
|
||||
MDX 기반 콘텐츠를 입력하면, 1280×720 슬라이드 HTML로 자동 변환하는 파이프라인입니다.
|
||||
|
||||
텍스트/MDX 콘텐츠를 입력하면:
|
||||
1. Kei 실장(Opus)이 정보 구조와 비중을 판단하고
|
||||
2. 코드가 컨테이너 크기를 계산하고
|
||||
3. 블록을 선택하고
|
||||
4. 콘텐츠-컨테이너 적합성을 검증하고
|
||||
5. AI(Sonnet)가 블록 디자인을 참고하여 HTML을 생성하고
|
||||
6. 코드가 슬라이드 프레임에 조립하고
|
||||
7. 측정+비전 모델로 검증합니다
|
||||
텍스트 콘텐츠를 넣으면, 구조를 분석하고 BEPs(Figma) 디자인을 매칭하여 슬라이드를 만들어줍니다.
|
||||
|
||||
---
|
||||
|
||||
## 파이프라인 (10단계)
|
||||
## 어떻게 구성/구현되어 있는가
|
||||
|
||||
### 전체 흐름
|
||||
|
||||
```
|
||||
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 저장 + 팝업 분리 (파일 출력)
|
||||
MDX 입력 → 정규화 → 꼭지 추출(AI) → zone 구분 → BEPs 매칭 → 조립 → 검증 → 출력
|
||||
```
|
||||
|
||||
※ Stage 4 이후의 파일 저장은 별도 Stage가 아닌 후처리입니다.
|
||||
### 구조
|
||||
|
||||
- **slide-base:** 1280×720 슬라이드 프레임. 대목차 + 구분선 + 본문 영역 + 핵심 인사이트(footer)
|
||||
- **zone:** 본문 영역 안에서 중목차(##) 기준으로 나뉘는 영역 (top/bottom 등)
|
||||
- **블록:** zone 안에 들어가는 디자인 단위. Figma에서 추출한 BEPs 디자인을 HTML/CSS로 변환한 것
|
||||
- **catalog:** 블록의 메타 정보 (구조, 슬롯, 매칭 조건)
|
||||
|
||||
### 주요 파일
|
||||
|
||||
| 파일 | 역할 |
|
||||
|------|------|
|
||||
| `src/pipeline.py` | 파이프라인 오케스트레이션 |
|
||||
| `src/section_parser.py` | 중목차 추출, 구조 분류 |
|
||||
| `src/block_reference.py` | BEPs 디자인 매칭 |
|
||||
| `src/block_assembler.py` | 슬라이드 HTML 조립 |
|
||||
| `templates/blocks/slide-base.html` | 슬라이드 프레임 |
|
||||
| `templates/catalog.yaml` | 블록 메타 정보 |
|
||||
|
||||
### 산출물
|
||||
|
||||
각 실행은 `data/runs/{run_id}/` 아래에 저장됩니다.
|
||||
|
||||
| 파일 | 내용 |
|
||||
|------|------|
|
||||
| `final.html` | 최종 슬라이드 |
|
||||
| `final_context.json` | 파이프라인 결과 데이터 |
|
||||
| `steps/*.html` | 단계별 디버그 보드 |
|
||||
| `첨부*_상세*.html` | popup 상세 내용 |
|
||||
|
||||
---
|
||||
|
||||
## 단계별 상세
|
||||
## 무슨 문제가 있는가
|
||||
|
||||
### Stage 0: MDX 정규화
|
||||
|
||||
| 항목 | 내용 |
|
||||
| 문제 | 설명 |
|
||||
|------|------|
|
||||
| **목적** | 원본 MDX에서 JSX/frontmatter를 제거하고, 섹션/팝업/이미지/테이블로 분리 |
|
||||
| **적용기술** | 코드 (`normalize_mdx_content()`) |
|
||||
| **인풋** | 원본 MDX 문자열 |
|
||||
| **아웃풋** | `normalized` — clean_text, title, sections[], popups[], images[], tables[] |
|
||||
| **연계** | → Stage 1A가 clean_text를 Kei에게 전달 |
|
||||
|
||||
### Stage 1A: 꼭지 추출 + 영역 배정
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| **목적** | 콘텐츠에서 핵심 파트(꼭지)를 식별하고, 슬라이드의 어떤 영역(배경/본심/첨부/결론)에 배치할지 결정 |
|
||||
| **적용기술** | 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: 컨셉 구체화
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| **목적** | 각 꼭지에 실제 원본 텍스트(source_data)와 요약(summary)을 매핑 |
|
||||
| **적용기술** | 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: 블록 선택
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| **목적** | 각 꼭지의 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: 적합성 검증 + 재배분 + 보강 + 서브 컨테이너
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| **목적** | 콘텐츠가 컨테이너에 들어가는지 검증 → 안 맞으면 재배분 → 여전히 안 되면 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: 디자인 예산 재계산
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| **목적** | 재배분된 컨테이너 크기 + 선택된 블록 schema 기준으로 영역별 가용 공간 계산 |
|
||||
| **적용기술** | 코드 (`calculate_design_budget()`) |
|
||||
| **인풋** | containers (재배분 후), references (블록 schema) |
|
||||
| **아웃풋** | `containers` 업데이트 — design_budget (available_height_px, available_width_px, fits) |
|
||||
| **연계** | → Stage 2가 design_budgets를 프롬프트에 포함 |
|
||||
|
||||
### Stage 2: HTML 생성 (영역별 개별 호출)
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| **목적** | 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: 렌더링 조립 + 후처리
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| **목적** | 생성된 HTML 조각을 CSS Grid 슬라이드 프레임에 삽입 + 후처리 (폰트 캡핑, overflow 제거, sidebar width 조정, bold 변환) |
|
||||
| **적용기술** | 코드 (`render_slide_from_html()`) |
|
||||
| **인풋** | generated_html, preset (grid_areas, grid_columns), font_hierarchy, container_ratio |
|
||||
| **아웃풋** | `rendered_html` → `final.html` 파일 저장 |
|
||||
| **연계** | → Stage 4가 렌더링 결과를 측정+검증 |
|
||||
|
||||
### Stage 4: 품질 검증
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| **목적** | 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 시 경고 포함하여 진행 |
|
||||
| **블록마다 스타일이 제각각** | 각 블록 HTML 안에 font-size, color, padding이 직접 박혀있어서, 같은 슬라이드 안에서 블록이 섞이면 위계가 안 맞음 |
|
||||
| **slide-base에 구조+스타일 혼재** | 프레임 HTML 안에 전체 CSS가 인라인으로 들어있어서 유지보수가 어려움 |
|
||||
| **블록이 완성 HTML** | 블록이 구조+스타일+값을 모두 포함하고 있어서, 재사용/조합이 안 됨 |
|
||||
| **매칭 안 되면 고정 렌더** | BEPs에 맞는 블록이 없을 때 코드가 1회 고정 렌더하고 끝. 반복 조정 없음 |
|
||||
| **빈 공간/overflow 방치** | 렌더 후 빈 공간이 있어도 조정 안 하고, overflow만 감지 |
|
||||
| **검증이 약함** | overflow 측정만 하고, 정렬/위계/가독성 같은 시각 품질은 미검증 |
|
||||
|
||||
---
|
||||
|
||||
## 중간 산출물
|
||||
## 어떻게 개선하려 하는가
|
||||
|
||||
파이프라인 실행마다 `data/runs/{timestamp}/`에 단계별 결과가 저장된다.
|
||||
핵심은 3가지입니다.
|
||||
|
||||
### JSON Context (Stage별 누적 상태)
|
||||
| 파일 | Stage | 내용 |
|
||||
|------|-------|------|
|
||||
| `stage_0_context.json` | 0 | normalized (섹션, 팝업, 이미지) |
|
||||
| `stage_1a_context.json` | 1A | topics, page_structure |
|
||||
| `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 |
|
||||
1. **블록을 구조 부품화** — 완성 HTML이 아니라, 구조만 담고 스타일은 토큰으로 분리
|
||||
2. **스타일을 토큰으로 통일** — 블록마다 제각각인 폰트/색/여백을 공통 기준으로
|
||||
3. **2경로 파이프라인** — 매칭되면 바로 쓰고(direct-fit), 안 되면 재구성(recipe)
|
||||
|
||||
### HTML 시각화 (`steps/` 폴더)
|
||||
| 파일 | 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 | 측정 결과 + 품질 점수 |
|
||||
|
||||
---
|
||||
|
||||
## 핵심 원칙
|
||||
|
||||
1. **콘텐츠가 구조를 결정** — 블록 CSS는 참고만. AI가 콘텐츠 전달 의도를 보고 HTML 구조 결정 (Phase R')
|
||||
2. **하드코딩 금지** — font-size 외 모든 수치는 동적 계산. 어떤 MDX가 들어와도 동일하게 동작
|
||||
3. **스크롤 절대 금지** — overflow:auto/scroll 어떤 영역에서도 불허
|
||||
4. **Kei API 필수** — fallback 없음. 성공할 때까지 무한 재시도
|
||||
5. **AI가 옵션 생성, Kei가 결정** — 공간 부족 시 하드코딩 대응이 아니라 Kei 판단 요청
|
||||
6. **계산 먼저, AI 판단 나중에, 렌더링은 검증만**
|
||||
7. **overflow 상태에서 출력 금지** — Vision 모델 품질 게이트 통과 필수
|
||||
|
||||
---
|
||||
|
||||
## 블록 라이브러리 (38개)
|
||||
|
||||
6개 카테고리, 38개 블록. 각 블록은 `catalog.yaml`에 용도(when), 금지(not_for), purpose_fit, schema(슬롯 정의)가 있음.
|
||||
|
||||
| 카테고리 | 개수 | 용도 |
|
||||
|---------|------|------|
|
||||
| **headers** | 5 | 타이틀, 꼭지 헤더 |
|
||||
| **cards** | 9 | 항목 나열, 카드 그리드 |
|
||||
| **tables** | 3 | 비교표, 데이터 테이블 |
|
||||
| **visuals** | 6 | SVG 다이어그램, 관계도 |
|
||||
| **emphasis** | 10 | 강조, 인용, 결론, 불릿 |
|
||||
| **media** | 5 | 이미지/사진 |
|
||||
|
||||
---
|
||||
|
||||
## 기술 스택
|
||||
|
||||
| 역할 | 도구 |
|
||||
|------|------|
|
||||
| 서버 | 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 (결정론적) |
|
||||
|
||||
---
|
||||
|
||||
## 설치 및 실행
|
||||
|
||||
```bash
|
||||
# 설치
|
||||
cd design_agent
|
||||
pip install -e .
|
||||
|
||||
# FAISS 인덱스 빌드 (블록 추가/수정 시)
|
||||
python scripts/build_block_index.py
|
||||
|
||||
# .env 설정
|
||||
ANTHROPIC_API_KEY=sk-ant-...
|
||||
KEI_API_URL=http://localhost:8000
|
||||
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
|
||||
cd D:\ad-hoc\kei\design_agent
|
||||
python -m uvicorn src.main:app --host 127.0.0.1 --port 8001 --reload
|
||||
```
|
||||
|
||||
접속: http://localhost:8001
|
||||
|
||||
---
|
||||
|
||||
## 개선 이력
|
||||
|
||||
| Phase | 내용 | 상태 |
|
||||
|-------|------|------|
|
||||
| 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와의 관계
|
||||
### AS-IS → TO-BE
|
||||
|
||||
```
|
||||
Kei Persona Agent (localhost:8000)
|
||||
├── Opus + RAG + 세션 컨텍스트
|
||||
├── 도메인 지식 (건설/DX/BIM)
|
||||
└── 대화/생성/피드백/실행 모드
|
||||
AS-IS:
|
||||
AI가 먼저 꼭지를 추출하고
|
||||
→ 매칭 블록이 있으면 삽입, 없으면 코드가 1회 고정 렌더
|
||||
→ 빈 공간이 있어도 그냥 둠
|
||||
→ 블록마다 font-size, color가 직접 박혀있어서 섞이면 위계 안 맞음
|
||||
|
||||
Design Agent (localhost:8001, 이 프로젝트)
|
||||
├── 슬라이드 생성 전용
|
||||
├── Kei API로 꼭지 추출(1A) + 컨셉 구체화(1B) + 에스컬레이션(1.8) 호출
|
||||
├── Sonnet으로 HTML 생성(Stage 2)
|
||||
├── Opus Vision으로 품질 검증(Stage 4)
|
||||
└── 두 프로젝트는 독립. 코드 공유 없음. API 연동만.
|
||||
TO-BE:
|
||||
중목차 기준으로 zone을 먼저 나누고
|
||||
→ TF-IDF로 BEPs 매칭 시도
|
||||
→ 매칭되면 블록 삽입 + 크기 조절 (direct-fit)
|
||||
→ 안 되면 AI가 꼭지 정리 + 유사 디자인으로 redesign + 반복 조정 (recipe)
|
||||
→ 빈 공간/overflow를 자동 재분배
|
||||
→ 모든 블록이 토큰 기반이라 스타일 통일
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 현재 상태
|
||||
|
||||
| 대상 | 슬라이드 변환 | 파이프라인 자동화 |
|
||||
|------|-------------|----------------|
|
||||
| MDX 03 (3개 목표 + 비교표) | ✅ 완료 | ✅ 파이프라인 연결 |
|
||||
| MDX 02 (목표 + 프로세스 + 상세표) | ✅ 완료 | △ 파이프라인 연결, 시각 품질 개선 중 |
|
||||
| MDX 01 (Type A, sidebar 구조) | ✅ 완료 (개별) | 미연결 |
|
||||
| 토큰 기반 CSS 체계 | - | ✅ 정의 완료, slide-base 적용 |
|
||||
| Figma 블록 추출 | - | 진행 중 (`figma_to_html_agent/blocks/`) |
|
||||
|
||||
---
|
||||
|
||||
## 다음 단계 방향
|
||||
|
||||
| 순서 | 단계 | 내용 |
|
||||
|------|------|------|
|
||||
| 1 | 폴더 구조 정리 | structures/recipes/legacy 분리 |
|
||||
| 2 | 기존 블록 점진 전환 | 분류(direct-fit/recipe/rewrite) → 토큰 기반 전환 |
|
||||
| 3 | catalog 고도화 | 파일명 중심 → 속성 테이블 기반 매칭 |
|
||||
| 4 | 파이프라인 연결 | TF-IDF 매칭 + recipe/composition 경로 |
|
||||
| 5 | fit 루프 확장 | 빈 공간 재분배, preview 축약, 자동 조정 반복 |
|
||||
| 6 | 시각 품질 검증 | 정렬, 위계, 가독성 검증 강화 |
|
||||
|
||||
상세: [IMPROVEMENT-PLAN.md](docs/architecture/IMPROVEMENT-PLAN.md)
|
||||
|
||||
---
|
||||
|
||||
## 참고 문서
|
||||
|
||||
| 문서 | 내용 |
|
||||
|------|------|
|
||||
| [IMPROVEMENT-PLAN.md](docs/architecture/IMPROVEMENT-PLAN.md) | 개선 설계 (목표/방향/6단계 계획) |
|
||||
| [TOKENS-v1.md](docs/architecture/TOKENS-v1.md) | 토큰 위계 기준표 초안 |
|
||||
| [BLOCK-RULES.md](docs/architecture/BLOCK-RULES.md) | 블록 작성 규칙 (에이전트 간 계약서) |
|
||||
|
||||
@@ -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": ""},
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
# 블록 작성 규칙
|
||||
|
||||
Figma 1:1 HTML을 재사용 가능한 블록으로 전환할 때, 이 규칙을 따른다.
|
||||
figma_to_html_agent와 design_agent 간의 **계약서**.
|
||||
|
||||
---
|
||||
|
||||
## 1. HTML 규칙
|
||||
|
||||
### 구조 스타일은 OK
|
||||
블록의 배치/정렬을 위한 CSS는 블록 안에 있어야 한다.
|
||||
```css
|
||||
/* OK */
|
||||
display: flex;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
```
|
||||
|
||||
### 직접값은 금지
|
||||
하드코딩된 폰트/색/여백은 블록 안에 넣지 않는다.
|
||||
```css
|
||||
/* NG */
|
||||
font-size: 11px;
|
||||
color: #475569;
|
||||
padding: 16px;
|
||||
|
||||
/* OK — 토큰 참조 */
|
||||
font-size: var(--font-body);
|
||||
color: var(--color-body);
|
||||
padding: var(--card-padding);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 클래스명 규칙
|
||||
|
||||
### 공통 클래스 (모든 블록에서 사용)
|
||||
| 클래스 | 용도 | 토큰 참조 |
|
||||
|--------|------|-----------|
|
||||
| `.zone-title` | 중목차 제목 | `--font-zone-title`, `--color-zone-title` |
|
||||
| `.sub-title` | 소목차 제목 | `--font-sub-title` |
|
||||
| `.bul` | 본문 블릿 (hanging indent) | `--font-body`, `--bullet-indent` |
|
||||
| `.body-text` | 본문 텍스트 | `--font-body`, `--color-body` |
|
||||
| `.caption` | 캡션/보조 | `--font-caption`, `--color-caption` |
|
||||
|
||||
### 블록 고유 클래스
|
||||
블록별 고유 요소는 블록 prefix를 붙인다.
|
||||
```
|
||||
.p3c-bar (prerequisites-3col의 gradient 바)
|
||||
.pp2-col (process-product-2col의 컬럼)
|
||||
.cid-card (card-icon-desc의 카드)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 슬롯 규칙
|
||||
|
||||
블록이 받아들이는 콘텐츠 슬롯. catalog에 명시.
|
||||
|
||||
| 슬롯 | 용도 | 예시 |
|
||||
|------|------|------|
|
||||
| `zone_title` | 중목차 제목 | "DX의 궁극적 목표" |
|
||||
| `sub_title` | 소목차 제목 | "안전과 품질" |
|
||||
| `body` | 본문 텍스트 | 설명 문장 |
|
||||
| `bullets` | 블릿 리스트 | D2: 항목들 |
|
||||
| `image` | 이미지 | 시각 앵커 |
|
||||
| `preview` | 상세 preview | 표 헤더+행 |
|
||||
| `detail_link` | 자세히보기 링크 | 첨부 파일 연결 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 토큰 참조 방법
|
||||
|
||||
### 토큰 파일 위치
|
||||
```
|
||||
templates/styles/tokens/
|
||||
├── typography.css ← 글자 위계
|
||||
├── spacing.css ← 여백/간격
|
||||
└── colors.css ← 색상
|
||||
```
|
||||
|
||||
### 사용법
|
||||
```css
|
||||
/* 블록 CSS에서 */
|
||||
.my-block-title {
|
||||
font-size: var(--font-sub-title);
|
||||
font-weight: var(--weight-sub-title);
|
||||
line-height: var(--lh-sub-title);
|
||||
color: var(--color-zone-title);
|
||||
margin-bottom: var(--heading-gap);
|
||||
}
|
||||
|
||||
.my-block-body {
|
||||
font-size: var(--font-body);
|
||||
color: var(--color-body);
|
||||
padding-left: var(--bullet-indent);
|
||||
text-indent: calc(var(--bullet-indent) * -1);
|
||||
}
|
||||
```
|
||||
|
||||
### 블록 의미색이 필요할 때
|
||||
공통 테마색으로 안 되는 역할색은 colors.css의 2층 의미색을 참조.
|
||||
```css
|
||||
/* 3열 비교의 열별 색상 */
|
||||
.col-1 .bar { background: linear-gradient(180deg, var(--color-col-1-from), var(--color-col-1-to)); }
|
||||
.col-2 .bar { background: linear-gradient(180deg, var(--color-col-2-from), var(--color-col-2-to)); }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 블록 HTML 예시
|
||||
|
||||
### 좋은 예
|
||||
```html
|
||||
<section class="block block-3col">
|
||||
<div class="col">
|
||||
<div class="sub-title">{{ col.title }}</div>
|
||||
<div class="bul">• {{ col.desc }}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.block-3col {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--card-gap);
|
||||
}
|
||||
.block-3col .col {
|
||||
padding: var(--card-padding);
|
||||
background: var(--color-bg-subtle);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--card-radius);
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
### 나쁜 예
|
||||
```html
|
||||
<!-- NG: 직접값 하드코딩 -->
|
||||
<div style="font-size:12px; color:#1e293b; padding:16px; background:#f8fafc; border-radius:6px;">
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 전환 체크리스트
|
||||
|
||||
Figma 1:1 HTML → 재사용 블록으로 전환할 때:
|
||||
|
||||
- [ ] font-size 직접값 → `var(--font-*)` 교체
|
||||
- [ ] color 직접값 → `var(--color-*)` 교체
|
||||
- [ ] padding/margin 직접값 → semantic spacing 또는 `var(--space-*)` 교체
|
||||
- 카드 내부: `var(--card-padding)`
|
||||
- 블릿 들여쓰기: `var(--bullet-indent)`
|
||||
- 소제목 아래: `var(--heading-gap)`
|
||||
- 일반 여백: `var(--space-sm)`, `var(--space-md)` 등
|
||||
- [ ] gap → `var(--card-gap)`, `var(--flex-gap)` 등
|
||||
- [ ] border-radius → `var(--card-radius)` 교체
|
||||
- [ ] 공통 클래스 사용 (`.bul`, `.sub-title` 등)
|
||||
- [ ] 블록 고유 요소에 prefix 클래스
|
||||
- [ ] catalog에 슬롯/구조/호환 정보 등록
|
||||
@@ -0,0 +1,183 @@
|
||||
# Frame Integration Map — 32 Figma Frame ↔ Phase Z Zone 통합 매핑
|
||||
|
||||
> **핵심 방향 (2026-04-28 정정)**
|
||||
>
|
||||
> Figma frame 은 슬라이드에 **원본 그대로 꽂는 게 아니라**, **디자인 레퍼런스 / 구조 패턴 / 슬롯 힌트**로 본다.
|
||||
> **최종 결과는 항상 현재 slide-body 의 Zone 안에 맞게 재구성한다.**
|
||||
>
|
||||
> - 원본이 full-slide 디자인이어도 → Zone 안 맞게 축약 / 재배치 / 슬롯화해서 사용
|
||||
> - 복합 슬라이드여도 → Zone 안에서는 일부 패턴만 참고해 재구성
|
||||
>
|
||||
> → 분류는 **"원본 크기"** 가 아니라 **"Zone 에 어떻게 적용할지"** 기준으로 한다.
|
||||
|
||||
---
|
||||
|
||||
## 라벨 정의
|
||||
|
||||
### Zone 적용 방식 (zone_application)
|
||||
|
||||
| 값 | 의미 |
|
||||
|---|---|
|
||||
| `zone_direct` | Zone 안에 거의 그대로 적용 가능 (구조 / 사이즈 변환 최소) |
|
||||
| `zone_adapt` | 구조는 맞지만 Zone 크기에 맞게 재구성 필요 |
|
||||
| `zone_extract` | 전체 frame 중 일부 패턴만 추출해서 Zone 에 사용 |
|
||||
| `reference_only` | 직접 구조로 쓰기보다 디자인 톤 / 아이디어만 참고 |
|
||||
| `reject` | Phase Z 에서 사용하지 않음 |
|
||||
|
||||
### Zone 적합성 (zone_fit)
|
||||
|
||||
| 값 | 의미 |
|
||||
|---|---|
|
||||
| `high` | Zone 에 바로 맞추기 쉬움 (단순 리스트 / 표 등) |
|
||||
| `medium` | 조정하면 가능 (3 단 카드 / 다이어그램 등) |
|
||||
| `low` | 많이 재구성해야 함 (복합 구조 / 분할 패널) |
|
||||
| `reference` | 참고용 |
|
||||
|
||||
### 검토 상태 (review_status)
|
||||
|
||||
| 값 | 의미 |
|
||||
|---|---|
|
||||
| `auto_estimated` | 코드가 layout 패턴으로 추정. 사용자 검토 필요 |
|
||||
| `user_confirmed` | 사용자 검토 후 확정 |
|
||||
| `needs_review` | 자동 추정에 의문 — 사람이 Figma 캔버스 직접 확인 필요 |
|
||||
|
||||
---
|
||||
|
||||
## 1차 — 32 Frame Zone 적용 분류
|
||||
|
||||
> ⚠️ **`legacy 스타일 출처(참고)` 컬럼 의미**
|
||||
>
|
||||
> 기존 `templates/blocks/` 는 **Phase Z 의 실제 조립 재료가 아님** (삭제 / 폐기 방향).
|
||||
> 이 컬럼은 **frame ↔ 블록 매핑이 아니라**, frame 의 디자인 / 시각 언어를 만들 때 참고할 수 있는 **스타일 출처** (색감, 여백, 폰트 위계, 표 스타일, 카드 스타일, pill / badge, SVG / CSS 구현 힌트) 만 가리킨다.
|
||||
>
|
||||
> Phase Z 의 실제 실행 기준은 **새 frame / zone catalog**.
|
||||
> 정밀화는 [`PHASE-Z-STYLE-SOURCES.md`](PHASE-Z-STYLE-SOURCES.md) (예정) 에서 별도 진행.
|
||||
|
||||
| Frame | Figma ID | 패턴 | Zone 적합성 | Zone 적용 방식 | 검토 상태 | legacy 스타일 출처(참고) | 비고 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **01** | `1171281172` | `circular-nodes-6` | `medium` | `zone_adapt` | `auto_estimated` | `templates/blocks/visuals/` (다이어그램) | S/W 개발 방향 순환도. 6 노드 — Zone 사이즈에 맞춰 시각 재구성 |
|
||||
| **02** | `1171281173` | `bullet-cards-4-plus-center` | `low` | `zone_extract` | `auto_estimated` | (복합 — 일부 패턴 추출) | 4 카드 + 중앙 강조. Zone 에서는 4 카드 부분만 추출하거나 중앙만 강조로 사용 |
|
||||
| **03** | `1171281174` | `list-numbered-4` | `high` | `zone_direct` | `auto_estimated` | `templates/blocks/cards/` 또는 list 류 | 4 항목 번호 리스트 — 단순 구조, Zone 직접 적용 |
|
||||
| **04** | `1171281175` | `quadrilateral-relations` | `medium` | `zone_extract` | `user_confirmed` | (관계도 — 기존 venn / cycle 로 부족 가능) | 4 actors + 관계도 + hierarchy 단계 결합된 복합형. 큰 Zone 에서는 zone_adapt 가능, 기본은 actor / hierarchy 패턴 추출 |
|
||||
| **05** | `1171281176` | `side-card-with-list` | `medium` | `zone_extract` | `auto_estimated` | (좌우 분할 — 일부 추출) | 좌측 카드 + 우측 리스트. Zone 안에서 좌·우 패턴 추출 |
|
||||
| **06** | `1171281177` | `full-page-map-banner` | `reference` | `reference_only` | `user_confirmed` | (콘텐츠 슬롯형 X — 참고용) | 지도 / 마커 / 현황 시각자료 중심. 일반 텍스트 Zone 구조로 직접 사용 X. 지도형 콘텐츠가 있을 때 디자인 참고 |
|
||||
| **07** | `1171281178` | `2col-paired-list` | `high` | `zone_adapt` | `auto_estimated` | `templates/blocks/tables/` 또는 페어드 | 2 컬럼 페어드 리스트 — Zone 사이즈 맞춰 표 재구성 |
|
||||
| **08** | `1171281179` | `3-section-framework` | `medium` | `zone_extract` | `auto_estimated` | (3 섹션 — 일부 추출) | 효율적 정보 관리 (What/How/When). 3 섹션 패턴 추출 |
|
||||
| **09** | `1171281180` | `list-stacked-vertical` | `high` | `zone_direct` | `auto_estimated` | `templates/blocks/cards/` 또는 list 류 | 5 항목 세로 리스트 — Zone 직접 적용 |
|
||||
| **10** | `1171281181` | `radial-diagram-5` | `medium` | `zone_adapt` | `auto_estimated` | `templates/blocks/visuals/` | 5 way 방사형 다이어그램 — Zone 시각 재구성 |
|
||||
| **11** | `1171281182` | `cards-3-category` | `medium` | `zone_extract` | `auto_estimated` | (3 카드 — 패턴 추출) | 시공단계 BIM 활용 — 3 카드 패턴 추출 |
|
||||
| **12** | `1171281189` | `cycle-3way-intersection` | `medium` | `zone_adapt` | `auto_estimated` | `templates/blocks/visuals/venn-diagram.html` | 3 way 벤다이어그램. Zone 사이즈 맞춰 SVG 재구성 |
|
||||
| **13** | `1171281190` | `3-column` | `medium` | `zone_extract` | `auto_estimated` | (3 단 — 패턴 추출) | 필수조건 (pillar 3). Zone 안에 3 단 패턴 추출. 02-2.2 매칭 실패 정답 frame (3차 대상) |
|
||||
| **14** | `1171281191` | `persona-3col` | `medium` | `zone_extract` | `auto_estimated` | `templates/blocks/cards/card-text-grid.html` (참고) | 주체별 기대효과. Zone 안에서 3 카드 패턴 추출 |
|
||||
| **15** | `1171281192` | `policy-4card-plus-list` | `low` | `zone_extract` | `auto_estimated` | (복합 — 일부 추출) | 4 카드 + 부속 리스트. Zone 안에서 4 카드만 추출하거나 리스트만 사용 |
|
||||
| **16** | `1171281193` | `quadrant-4` | `medium` | `zone_extract` | `auto_estimated` | (4 사분면 — 패턴 추출) | BIM 수행 이슈 4사분면. 4 사분면 패턴 추출 |
|
||||
| **17** | `1171281194` | `paired-rows-2x2` | `high` | `zone_adapt` | `auto_estimated` | `templates/blocks/tables/` | 2x2 페어드 행 — Zone 표 재구성 |
|
||||
| **18** | `1171281195` | `compare-rows` | `high` | `zone_adapt` | `auto_estimated` | `templates/blocks/tables/compare-3col-badge.html` (참고) | BIM·DX 비교. 매칭 시스템 검증된 대표 frame. Zone 안 표 재구성 |
|
||||
| **19** | `1171281197` | `cards-3-compare` | `medium` | `zone_extract` | `auto_estimated` | (3 단 비교 — 패턴 추출) | 설계방식 왜곡 — 3 비교 패턴 추출 |
|
||||
| **20** | `1171281198` | `cards-3-header` | `medium` | `zone_extract` | `auto_estimated` | (3 헤더형 — 패턴 추출) | DX 는 S/W 가 필수 — 3 헤더 패턴 추출 |
|
||||
| **21** | `1171281201` | `split-panel-diagram` | `low` | `zone_extract` | `auto_estimated` | (분할 패널 — 패턴 추출) | Solution Engn. S/W. 분할 패널 패턴 일부 추출 |
|
||||
| **22** | `1171281202` | `split-panel-numbered` | `low` | `zone_extract` | `auto_estimated` | (분할 패널 + 번호 — 패턴 추출) | Model 특화 Engn. S/W. 분할 + 번호 패턴 추출 |
|
||||
| **23** | `1171281203` | `table-2col` | `high` | `zone_adapt` | `auto_estimated` | `templates/blocks/tables/` | Application S/W 의 구분. **table family 중복 — variant 통합 검토** |
|
||||
| **24** | `1171281204` | `table-3col` | `high` | `zone_adapt` | `auto_estimated` | `templates/blocks/tables/` | Engn. S/W 구성과 특징. **table family 중복 — variant 통합 검토** |
|
||||
| **25** | `1171281205` | `left-categories-right-logos` | `medium` | `zone_extract` | `auto_estimated` | (좌우 분할 — 패턴 추출) | 상용 Engn. S/W. 좌우 분할 패턴 추출 |
|
||||
| **26** | `1171281206` | `cards-4-grid` | `medium` | `zone_extract` | `auto_estimated` | (4 카드 — 패턴 추출) | 상용 S/W 의존 4 대 문제. 4 카드 그리드 추출 |
|
||||
| **27** | `1171281208` | `central-split-synthesis` | `low` | `zone_extract` | `auto_estimated` | (중앙 합성 — 일부 추출) | 건설산업 고부가가치화. 중앙 + 양쪽 패턴 일부 추출 |
|
||||
| **28** | `1171281209` | `title-plus-3-emphasis` | `medium` | `zone_extract` | `auto_estimated` | (제목 + 3 강조 — 패턴 추출) | 현존 상용 S/W 의 현실. 제목 + 3 카드 패턴 추출 |
|
||||
| **29** | `1171281210` | `banner-top-2col-bottom` | `low` | `zone_extract` | `auto_estimated` | (banner + 2col 복합 — 패턴 추출) | Process/Product 혁신 (AS-IS/TO-BE). Zone 안에서 banner + 2col 패턴만 추출 |
|
||||
| **30** | `1171281211` | `table-3col` | `high` | `zone_adapt` | `auto_estimated` | `templates/blocks/tables/` | 산업별 현황. **table family 중복 — variant 통합 검토** |
|
||||
| **31** | `1171281212` | `table-3col` | `high` | `zone_adapt` | `auto_estimated` | `templates/blocks/tables/` | 산업별 특성과 발전방향. **table family 중복 — variant 통합 검토** |
|
||||
| **32** | `1171281213` | `central-5-goals` | `low` | `zone_extract` | `auto_estimated` | (5 목표 복합 — 일부 추출) | 정책 달성. 중앙 5 목표 패턴 일부 추출 |
|
||||
|
||||
---
|
||||
|
||||
## 자동 추정 룰
|
||||
|
||||
### layout 패턴 → Zone 적용 방식
|
||||
|
||||
| 패턴 / family | Zone 적용 방식 | Zone 적합성 | 근거 |
|
||||
|---|---|---|---|
|
||||
| `list-*` (단순 리스트) | `zone_direct` | `high` | 구조 단순 — Zone 안 그대로 |
|
||||
| `compare-rows` / `table-*col` / `paired-rows-*` (표 / 페어드) | `zone_adapt` | `high` | 표 구조 — Zone 사이즈 맞춰 재구성 |
|
||||
| `cycle-*` / `circular-*` / `radial-*` / `quadrilateral-*` (시각 다이어그램) | `zone_adapt` | `medium` | 시각 디자인 — Zone 사이즈 맞춰 SVG 재구성 |
|
||||
| `cards-3-*` / `persona-3col` / `3-column` / `cards-4-grid` / `quadrant-4` / `3-section-*` (3·4 단 카드 / 칼럼) | `zone_extract` | `medium` | 패턴 일부 추출 (카드 / 칼럼) |
|
||||
| `side-*` / `left-*-right-*` / `title-plus-*` (좌우 / 제목+카드) | `zone_extract` | `medium` | 부분 패턴 추출 |
|
||||
| `banner-top-*-bottom` / `bullet-cards-*-plus-center` / `policy-*-plus-list` (복합) | `zone_extract` | `low` | 복합 구조 — Zone 에서는 일부만 추출 |
|
||||
| `split-panel-*` / `central-*` (분할 / 중앙 합성) | `zone_extract` | `low` | 새 시각 패턴 — 일부 추출 |
|
||||
| `full-page-map-*` (지도 / 배너) | `reference_only` | `reference` | 콘텐츠 슬롯형 X — 디자인 참고만 |
|
||||
|
||||
---
|
||||
|
||||
## 분포 요약
|
||||
|
||||
### Zone 적용 방식
|
||||
|
||||
| 적용 방식 | 개수 | Frame |
|
||||
|---|---|---|
|
||||
| `zone_direct` | **2** | 03, 09 |
|
||||
| `zone_adapt` | **11** | 01, 04, 07, 10, 12, 17, 18, 23, 24, 30, 31 |
|
||||
| `zone_extract` | **18** | 02, 05, 08, 11, 13, 14, 15, 16, 19, 20, 21, 22, 25, 26, 27, 28, 29, 32 |
|
||||
| `reference_only` | **1** | 06 |
|
||||
| `reject` | **0** | — |
|
||||
|
||||
**합계 검증** : 2 + 11 + 18 + 1 + 0 = **32** ✓
|
||||
|
||||
### Zone 적합성
|
||||
|
||||
| 적합성 | 개수 | Frame |
|
||||
|---|---|---|
|
||||
| `high` | **9** | 03, 07, 09, 17, 18, 23, 24, 30, 31 |
|
||||
| `medium` | **15** | 01, 04, 05, 08, 10, 11, 12, 13, 14, 16, 19, 20, 25, 26, 28 |
|
||||
| `low` | **7** | 02, 15, 21, 22, 27, 29, 32 |
|
||||
| `reference` | **1** | 06 |
|
||||
|
||||
**합계 검증** : 9 + 15 + 7 + 1 = **32** ✓
|
||||
|
||||
### 검토 상태
|
||||
|
||||
| review_status | 개수 | Frame |
|
||||
|---|---|---|
|
||||
| `auto_estimated` | **30** | (04, 06 제외 모두) |
|
||||
| `needs_review` | **0** | (04, 06 사용자 확정 완료) |
|
||||
| `user_confirmed` | **2** | **04** (zone_extract 확정), **06** (reference_only 확정) |
|
||||
|
||||
---
|
||||
|
||||
## 핵심 메시지
|
||||
|
||||
> **모든 frame 은 최종적으로 Zone 에 맞게 들어간다.**
|
||||
> **원본이 full-slide 인지 아닌지는 참고 정보일 뿐, 최종 사용 단위는 Zone 이다.**
|
||||
>
|
||||
> - 사용자가 의미한 "needs_split" = 실제 의미는 `zone_extract` 또는 `zone_adapt`
|
||||
> - frame 은 디자인 레퍼런스 / 구조 패턴 / 슬롯 힌트
|
||||
> - Phase Z output 은 항상 Zone 에 맞는 재구성 결과
|
||||
|
||||
---
|
||||
|
||||
## 다음 단계
|
||||
|
||||
### ✅ 완료 (1 차)
|
||||
- 32 frame 자동 추정 표 작성
|
||||
- Frame 04, 06 사용자 확정 (`user_confirmed`)
|
||||
- 컬럼명 정정 : `legacy 대응 블록(참고, 미검증)` → `legacy 스타일 출처(참고)`
|
||||
|
||||
### ✅ 완료 (2 차, 2026-04-28) — Phase Z Frame Style Inventory 작성
|
||||
|
||||
산출물 : [`PHASE-Z-FRAME-STYLE-INVENTORY.md`](PHASE-Z-FRAME-STYLE-INVENTORY.md)
|
||||
|
||||
- Source Policy : 메인 (`figma_to_html_agent/blocks/` 32 frame) / 토큰 (`templates/styles/tokens/`) / legacy (`templates/blocks/structures/`)
|
||||
- Frame Inventory 32 행 (변환 14 + 미변환 18 보일러플레이트)
|
||||
- Token Inventory 18 행 (`covered` 7 / `gap_candidate` 5 / `hierarchy_mapping_only` 3 / `hold_recheck_after_conversion` 3)
|
||||
- Legacy Reference 6 행 (모두 `delete_after_extract` 후보)
|
||||
|
||||
> ⚠️ inventory = **추출 / 검증 단계**. 실제 token 파일 생성 / 변경 / catalog 설계 / templates/blocks 삭제 등 **실행은 별도 승인 단계**.
|
||||
|
||||
### 후속 작업
|
||||
- **3차** — Frame 14 anchor_sets 재라벨링 (02-2.2 매칭 실패 교정 — 매칭 성능 교정 작업, 별도 진행)
|
||||
- **추가** — table family (23/24/30/31) variant 통합 설계 (Phase Z catalog 작성 시)
|
||||
- **확정 처리** — 2 차 조사 결과 후 high confidence frame 부터 `user_confirmed` 일괄 처리
|
||||
|
||||
---
|
||||
|
||||
## 부록 — 제외 / 특수 항목
|
||||
|
||||
`1171281171` 은 `texts.md` 만 존재하고 `index.html` / `analysis.md` 가 없어 Phase Z frame / style inventory 메인 대상에서 제외한다. 정체는 미확인.
|
||||
@@ -0,0 +1,157 @@
|
||||
# C.E.L. Slide Pipeline — 개선 설계
|
||||
|
||||
## 목표
|
||||
|
||||
**MDX 콘텐츠를 넣으면, 중목차/소목차 기준으로 BEPs 디자인과 매칭하여 슬라이드를 자동 생성한다.**
|
||||
|
||||
현재는 블록마다 스타일이 제각각이고, 매칭/조립/크기 조정이 불완전하여 수작업 보정이 필요한 상태. 이를 토큰 기반 통일 + 블록 구조 부품화 + 파이프라인 2경로화로 개선한다.
|
||||
|
||||
---
|
||||
|
||||
## 개선 방향
|
||||
|
||||
| 현재 문제 | 개선 방향 |
|
||||
|-----------|-----------|
|
||||
| 블록마다 폰트/색/여백이 직접 박혀있어 통일 안 됨 | 토큰으로 공통 기준 확정, 블록은 변수 참조 |
|
||||
| slide-base에 구조+스타일이 섞여있음 | 프레임(HTML)과 스타일(CSS) 분리 |
|
||||
| 블록이 완성 HTML이라 재사용/조합이 어려움 | 블록 = 구조 부품, 스타일은 테마로 분리 |
|
||||
| 블록 매칭이 파일명/tag 중심 | 속성 테이블(catalog) 기반 매칭 |
|
||||
| 크기 조정이 1회 렌더로 끝남 | 입력→조정 반복 (빈 공간/overflow 자동 재분배) |
|
||||
| 검증이 overflow만 | 시각 품질까지 검증 (정렬, 위계, 가독성) |
|
||||
|
||||
---
|
||||
|
||||
## 핵심 축 3개
|
||||
|
||||
### 1. 표현 기준 통일
|
||||
|
||||
typography, spacing, colors, layout을 토큰으로 확정하여 모든 블록이 이 공통 기준을 따르게 한다.
|
||||
|
||||
### 2. 블록의 역할 재정의
|
||||
|
||||
블록 = 완성 HTML이 아니라 **구조 부품**. 구조 스타일(flex, grid)은 블록에, 직접값(font-size, color)은 토큰으로 분리한다.
|
||||
|
||||
### 3. 파이프라인 2경로화
|
||||
|
||||
- **direct-fit:** BEPs 매칭 → 블록 삽입 + 크기 조절
|
||||
- **recipe/composition:** 미매칭 → 꼭지 정리 → redesign + 반복 조정
|
||||
|
||||
---
|
||||
|
||||
## 실행 원칙
|
||||
|
||||
**direct-fit 기준:**
|
||||
direct-fit은 구조, 슬롯, 시각 위계가 기존 블록과 거의 1:1로 대응될 때만 허용한다.
|
||||
|
||||
**recipe-path 목적:**
|
||||
recipe/composition 경로는 미매칭 콘텐츠를 새로운 구조로 재정리하되, 기존 visual language를 최대한 상속한다.
|
||||
|
||||
**fit 루프 우선순위:**
|
||||
fit 조정은 줄바꿈 → 간격 조정 → preview 축약 → 폰트 1단계 축소 순으로 수행한다.
|
||||
|
||||
---
|
||||
|
||||
## 진행 계획
|
||||
|
||||
### 1단계: 토큰 기준 확정
|
||||
|
||||
**목표:** 모든 블록이 따라야 할 공통 기준표 확정
|
||||
|
||||
| 토큰 | 내용 |
|
||||
|------|------|
|
||||
| typography | 대목차/중목차/소목차/본문/캡션/footer 크기·굵기·줄간격 |
|
||||
| spacing | padding/gap/indent/margin |
|
||||
| colors 1층 | 공통 테마색 (title, body, border, card-bg, accent) |
|
||||
| colors 2층 | 블록 의미색 (compare-left/right, role-a/b, pill-left/right) |
|
||||
| layout | slide-base 위치값 (body-top, body-height, footer-bottom) |
|
||||
|
||||
### 2단계: slide-base CSS 분리
|
||||
|
||||
**목표:** slide-base를 프레임 전용으로 얇게
|
||||
|
||||
- HTML에서 `<style>` 추출 → 외부 CSS
|
||||
- 직접값 → 토큰 변수로 교체
|
||||
- slide-base = 대목차 + divider + body container + footer만 책임
|
||||
|
||||
### 3단계: 블록 작성 규칙 문서
|
||||
|
||||
**목표:** 블록화 시 참조할 기준 제공 (두 에이전트 간 계약서)
|
||||
|
||||
- 구조 스타일 OK (display:flex, grid, align-items, overflow)
|
||||
- 직접값 금지 (hardcoded font-size/color/padding)
|
||||
- 값은 토큰 참조 (`var(--font-body)`)
|
||||
- 클래스명 규칙 (`.zone-title`, `.bul` 등)
|
||||
- 슬롯 규칙 (zone_title, sub_title, body, bullets, image 등)
|
||||
|
||||
### 4단계: 폴더 구조 정리
|
||||
|
||||
**목표:** 프레임/구조/스타일/메타/자산 분리
|
||||
|
||||
```
|
||||
templates/
|
||||
├── base/ ← 프레임 (slide-base)
|
||||
├── blocks/
|
||||
│ ├── structures/ ← 구조 부품 (토큰 기반)
|
||||
│ ├── recipes/ ← 조합형 구조
|
||||
│ └── legacy/ ← 기존 블록 (점진 전환)
|
||||
├── styles/
|
||||
│ ├── tokens/ ← 공통 기준값
|
||||
│ ├── themes/ ← 톤 변형 (light/dark/formal)
|
||||
│ ├── base/ ← slide-base CSS
|
||||
│ └── blocks/ ← 블록별 CSS
|
||||
├── assets/ ← 이미지/SVG/텍스처
|
||||
└── catalog/ ← 속성 테이블 (blocks/recipes/rules)
|
||||
```
|
||||
|
||||
### 5단계: 기존 블록 점진 전환
|
||||
|
||||
**목표:** Figma 1:1 HTML → 토큰 기반 재사용 블록으로 전환
|
||||
|
||||
**5-1. 블록 분류 먼저**
|
||||
|
||||
| 분류 | 의미 | 전환 순서 |
|
||||
|------|------|-----------|
|
||||
| direct-fit candidate | 바로 사용 가능 | 먼저 (토큰 교체만) |
|
||||
| recipe component candidate | 조합용 부품 | 다음 |
|
||||
| rewrite required | 구조부터 다시 | 마지막 |
|
||||
|
||||
**5-2. 분류 결과에 따라 전환**
|
||||
- `figma_to_html_agent/blocks/` 완료물을
|
||||
- 토큰 기준에 맞춰 `templates/blocks/structures/`로
|
||||
- 한 번에 다 뜯지 않고 점진적으로
|
||||
|
||||
### 6단계: 파이프라인 연결
|
||||
|
||||
**목표:** TO-BE 프로세스 완성
|
||||
|
||||
- TF-IDF 기반 **direct-fit block 매칭**
|
||||
- 미매칭 시 **recipe/composition 경로 전환**
|
||||
- 토큰 기반 resize
|
||||
- 입력→조정 반복 (fit 루프 확장)
|
||||
- 시각 품질 검증 강화
|
||||
|
||||
**Source of Truth:**
|
||||
|
||||
| 기준 | 위치 |
|
||||
|------|------|
|
||||
| 구조 판정 | `normalized.sections` |
|
||||
| 블록 매칭 | `catalog/blocks.yaml` |
|
||||
| 스타일 기준 | `styles/tokens/` + `styles/themes/` |
|
||||
|
||||
---
|
||||
|
||||
## 역할 분리 (동시 진행)
|
||||
|
||||
```
|
||||
figma_to_html_agent (원재료) design_agent (조립 시스템)
|
||||
───────────────────── ──────────────────────
|
||||
"이 블록은 무엇인가" "이 블록을 언제 쓸까"
|
||||
구조 유형 발견 토큰 기준 확정
|
||||
슬롯 정의 catalog 필드 설계
|
||||
자산 메타 direct-fit vs recipe 규칙
|
||||
seam/crop/anchor theme/token 적용
|
||||
fit/validation
|
||||
|
||||
↓ 접점: 블록 작성 규칙 문서 (3단계) ↓
|
||||
블록화 시 이 규칙에 맞춰 전환
|
||||
```
|
||||
@@ -0,0 +1,285 @@
|
||||
# Phase Z — composition planning schema plan (Layer A + Layer B)
|
||||
|
||||
**Status** : v1 plan (2026-04-30 refactor — SPEC v1 의 Layer A + Layer B 구조 반영). schema 작업 *범위 / 순서 / 완료 기준* 정의. 구현 X.
|
||||
**Anchor** : [`PHASE-Z-PIPELINE-STATUS-BOARD.md`](PHASE-Z-PIPELINE-STATUS-BOARD.md) 의 핵심 missing — Step 3 / 4 / 11 + Step 10 (부분).
|
||||
|
||||
> **v0 → v1 변경 요약**
|
||||
> - SPEC v1 의 renumbered section 으로 cross-reference 일관 갱신 (§1 / §2 신규 / §3 / §4 / §5 …)
|
||||
> - Step 4 의 schema 가 SPEC v1 §2 (Internal Region — Layer A) 로 *이미 정의됨* → 본 plan 에서는 *신규 작성* 이 아니라 *완성도 점검* 으로 전환
|
||||
> - Step 11 의 schema 가 SPEC v1 §4 (2-stage placement: Stage A → Stage B) 로 재정의됨에 따라 validation 기준 보강
|
||||
> - mechanical rename : `sub_zone` → `Frame Slot` (PLAN 본문). YAML 필드명 `sub_zones` 는 코드 reality 로 유지
|
||||
> - 4 step 범위 (Step 3 / 4 / 10-partial / 11) 그대로. Step 10 의 density envelope 제외 그대로
|
||||
> - implementation / MDX 실행 검증 / AI 호출 / 파일명 변경 / code marker 이름 결정 — *모두 제외 그대로*
|
||||
|
||||
---
|
||||
|
||||
## 0. 문서 역할 분리
|
||||
|
||||
| 문서 | 역할 |
|
||||
|---|---|
|
||||
| [`PHASE-Z-CONTENT-OBJECT-SUBZONE-SPEC.md`](PHASE-Z-CONTENT-OBJECT-SUBZONE-SPEC.md) (v1) | schema / contract *명세 자체* (authoritative) |
|
||||
| **본 문서 (`PHASE-Z-CONTENT-OBJECT-SUBZONE-PLAN.md`)** (v1) | 그 schema 작업을 *어떤 범위 / 순서 / 산출물 / validation 기준* 으로 진행할지 *계획* |
|
||||
|
||||
본 plan 은 SPEC 을 *대체하지 않음*. SPEC 의 어느 section 이 어느 step 의 schema 인지 *cross-reference + 완성도 점검* 까지가 본 plan 의 영역.
|
||||
|
||||
---
|
||||
|
||||
## 1. 목적
|
||||
|
||||
일반 MDX 1 파일이 *render 전 단계* 에서 typed content_object → Internal Region → Frame Slot 으로 결정 가능한 상태에 도달하도록, 다음 4 step 의 schema 정의를 *완결* 시킨다.
|
||||
|
||||
```
|
||||
Step 3. Content Object 추출
|
||||
Step 4. Section Internal Composition Planning (Layer A)
|
||||
Step 10. Frame Contract 의 accepted_content_types + Frame Slot 선언 (부분, Layer B)
|
||||
Step 11. Content Unit / Child Group → Internal Region → Frame Slot Mapping (2-stage)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 범위
|
||||
|
||||
### 2.1 포함
|
||||
|
||||
| Step | schema 항목 | SPEC v1 의 위치 |
|
||||
|---|---|---|
|
||||
| Step 3 | content_object base schema + 6 type 별 schema (text_block / table / transform_table / image / diagram / details) | SPEC v1 §1.1, §1.2 |
|
||||
| Step 3 | role 의미 (summary / detail / decorative / reference) | SPEC v1 §1.3 |
|
||||
| Step 4 | Internal Region entity schema (region_id / role / content_type / ratio_estimate / content_unit_ids / frame_match_strategy) | SPEC v1 §2.1 |
|
||||
| Step 4 | Universal Region Model (every zone = 1+ regions) | SPEC v1 §2.2 |
|
||||
| Step 4 | 3-way decision tree (whole / group / split) | SPEC v1 §2.3 |
|
||||
| Step 4 | region 비율 산정 (content type 별 size proxy) | SPEC v1 §2.4 |
|
||||
| Step 4 | region → frame / display 매칭 interface (frame_match / display_only) | SPEC v1 §2.6 |
|
||||
| Step 10 | frame contract 의 `accepted_content_types` 필드 schema | SPEC v1 §3.1 |
|
||||
| Step 10 | Frame Slot 선언 schema (YAML 필드명 `sub_zones` — 의미 = Frame Slot) | SPEC v1 §3.1 |
|
||||
| Step 10 | F13 / F29 / F16 의 Frame Slots 선언 예시 | SPEC v1 §3.2 |
|
||||
| Step 11 | placement algorithm 2-stage I/O schema | SPEC v1 §4.1 |
|
||||
| Step 11 | Stage A — content → Internal Region (Layer A 매핑) | SPEC v1 §4.2 |
|
||||
| Step 11 | Stage B — Internal Region content → Frame Slot (Layer B 매핑) | SPEC v1 §4.3 |
|
||||
| Step 11 | tie-break (Frame Slot 단위) | SPEC v1 §4.4 |
|
||||
| Step 11 | display_only region 처리 (frame 우회) | SPEC v1 §4.5 |
|
||||
| Step 11 | display_strategy schema (inline_full / inline_preview_with_details / details_only / dropped) | SPEC v1 §5.1, §5.5 |
|
||||
| Step 11 | display_strategy 의 region-level + slot-level 양쪽 적용 | SPEC v1 §5.3 |
|
||||
|
||||
### 2.2 Step 10 의 *부분 범위* 명시 (불변)
|
||||
|
||||
> 본 plan 에서 Step 10 은 **두 항목** 까지만 다룸 :
|
||||
> - `accepted_content_types`
|
||||
> - Frame Slot 선언 (YAML 필드명 `sub_zones`)
|
||||
>
|
||||
> 본 plan 에서 *다루지 않는* Step 10 항목 :
|
||||
> - `density envelope`
|
||||
>
|
||||
> `density envelope` 은 `frame_internal_fit_candidate` (Step 17 / Step 19 Gap) 와 연결된 *frame internal fit policy* 영역. 본 plan 에 넣으면 *공통 padding 축소 antipattern* (`feedback_phase_z_spacing_direction`) 과 섞일 risk 있어 *별 plan* 으로 분리.
|
||||
>
|
||||
> 따라서 본 plan 이 완료되어도 Step 10 전체가 ✅ 완료되는 것은 아니고 — `accepted_content_types` + Frame Slot 부분만 ✅ / density envelope 부분은 ❌ 잔존.
|
||||
|
||||
### 2.3 명시적 제외
|
||||
|
||||
- 구현 (extractor / planner / parser 코드 작성) — 본 plan 은 schema 까지
|
||||
- frame partial template 변경 (Frame Slot / region container marker 추가)
|
||||
- mapper / classifier 의 region / Frame Slot-aware 진화
|
||||
- details / popup runtime
|
||||
- backward flow 자동화 (telemetry → composition 재호출)
|
||||
- MDX 01 / 02 / 03 / 04 적용 검증
|
||||
- AI 호출 (plan 단계 자체 + plan 산출 schema 자체 모두)
|
||||
- code / module / HTML marker / attribute *이름 결정* (SPEC v1 와 동일 — implementation step 에서 결정)
|
||||
- 파일명 변경 (PLAN / SPEC 둘 다 — 향후 별 결정)
|
||||
- 다음 단계 우선순위 / A/B/C 선택지
|
||||
|
||||
---
|
||||
|
||||
## 3. 산출물
|
||||
|
||||
### 3.1 schema-side 산출물
|
||||
|
||||
본 plan 의 schema 작업이 완료되면, SPEC v1 가 다음 상태에 도달 :
|
||||
|
||||
| schema | 현재 SPEC v1 상태 | plan 종료 후 목표 상태 |
|
||||
|---|---|---|
|
||||
| content_object base + 6 type 별 schema | draft (SPEC v1 §1.1, §1.2) | completeness reviewed + gaps listed |
|
||||
| role 의미 4 종 | draft (SPEC v1 §1.3) | completeness reviewed + gaps listed |
|
||||
| Internal Region entity + Universal Region Model | draft (SPEC v1 §2.1, §2.2) | completeness reviewed + gaps listed |
|
||||
| 3-way decision tree | draft (SPEC v1 §2.3) | completeness reviewed + gaps listed |
|
||||
| region 비율 산정 | draft (SPEC v1 §2.4) | completeness reviewed + gaps listed |
|
||||
| region → frame / display 매칭 interface | draft (SPEC v1 §2.6) | completeness reviewed + gaps listed |
|
||||
| accepted_content_types + Frame Slot 선언 | draft (SPEC v1 §3.1, 3 frame 예시 §3.2) | completeness reviewed + gaps listed |
|
||||
| 2-stage placement (Stage A + Stage B I/O) | draft (SPEC v1 §4.1, §4.2, §4.3) | completeness reviewed + gaps listed |
|
||||
| tie-break + display_only region 처리 | draft (SPEC v1 §4.4, §4.5) | completeness reviewed + gaps listed |
|
||||
| display_strategy 4 종 + region/slot 양쪽 적용 | draft (SPEC v1 §5.1, §5.3, §5.5) | completeness reviewed + gaps listed |
|
||||
|
||||
### 3.2 plan-side 산출물
|
||||
|
||||
- 본 PLAN 문서 (v1) — schema 작업 범위 정의
|
||||
- 작업 결과로 SPEC v1 의 *추가 / 수정 부분* (edge case 보강 등 — substantive 신규 schema 가 아닌 점검 결과)
|
||||
- pipeline data flow 위치 표 (§5 참조)
|
||||
- schema validation 기준 표 (§6 참조)
|
||||
|
||||
---
|
||||
|
||||
## 4. SPEC ↔ PLAN cross-reference
|
||||
|
||||
본 plan 의 4 step 각각이 SPEC v1 의 어느 section 으로 매핑되는지 :
|
||||
|
||||
| Step | SPEC v1 section | plan 에서의 작업 |
|
||||
|---|---|---|
|
||||
| Step 3 | §1 (content_object schema) | *완성도 점검*. nested_list / sub-decomposition edge case (SPEC v1 §9.2 미해결 부분 — text_block 의 nested 구조를 sub_text_block 으로 sub-decompose vs Frame Slot cardinality aggregate 해석) |
|
||||
| Step 4 | §2 (Internal Region schema, Layer A) | *완성도 점검*. 3-way decision 의 boundary 케이스 / region 비율의 fallback / frame_match_strategy 가 unknown / ambiguous 일 때 거동 |
|
||||
| Step 10 (partial) | §3.1 (schema) + §3.2 (3 frame 예시) | *완성도 점검*. `density envelope` 미포함 명시 |
|
||||
| Step 11 | §4 (placement algorithm 2-stage) + §5 (display strategy) | *완성도 점검*. Stage A → Stage B *interface* 정합 (Stage A 의 frame_match_strategy 가 Stage B 의 입력으로 자연 호환) / backward flow 자동화 X 등 v1 한계 재확인 |
|
||||
|
||||
---
|
||||
|
||||
## 5. Pipeline data flow 위치
|
||||
|
||||
본 4 step 의 schema 가 *어디서 생성 / 누가 소비* 하는지.
|
||||
|
||||
```
|
||||
Step 1 MDX 업로드
|
||||
↓
|
||||
Step 2 MDX 정규화 (section / heading / raw_content 분리)
|
||||
↓
|
||||
├─→ Step 3. Content Object 추출
|
||||
│ INPUT : section.raw_content (markdown 문자열)
|
||||
│ OUTPUT : section.content_objects = [ContentObject ...]
|
||||
│ SCHEMA : SPEC v1 §1
|
||||
│ 소비자 : Step 4, Step 11
|
||||
│
|
||||
└─→ Step 4. Section Internal Composition Planning (Layer A)
|
||||
INPUT : section.content_objects (from Step 3) + section metadata + V4 evidence
|
||||
OUTPUT : zone.internal_regions = [
|
||||
{region_id, role, content_type, ratio_estimate,
|
||||
content_unit_ids, frame_match_strategy}, ...
|
||||
]
|
||||
SCHEMA : SPEC v1 §2 (entity / Universal Region Model / 3-way decision / 비율 / interface)
|
||||
소비자 : Step 6 (composition planning) / Step 8 (region ratio) /
|
||||
Step 9 (region-level frame match) / Step 11 (Stage A 결과)
|
||||
|
||||
Step 5 Matching Evidence 생성 (V4 top-k)
|
||||
Step 6 Composition Planning (Step 4 region 분할 결과 입력)
|
||||
Step 7 Slide-Level Layout Planning
|
||||
Step 8 Zone + Internal Region Ratio Planning (region 비율 = Step 4 산출)
|
||||
Step 9 Region-Level Frame / Display Selection (region 별 frame_match_strategy = Step 4 산출)
|
||||
↓
|
||||
└─→ Step 10 (partial). Frame Contract 확인
|
||||
INPUT : selected frame_id (region 별)
|
||||
OUTPUT : frame_contract.accepted_content_types
|
||||
+ frame_contract.sub_zones (= Frame Slots, Layer B)
|
||||
SCHEMA : SPEC v1 §3
|
||||
소비자 : Step 11 (Stage B)
|
||||
|
||||
↓
|
||||
└─→ Step 11. Content Unit / Child Group → Internal Region → Frame Slot Mapping (2-stage)
|
||||
INPUT : section.content_objects (from Step 3)
|
||||
+ zone.internal_regions (from Step 4)
|
||||
+ frame_contract.sub_zones (= Frame Slots) (from Step 10, frame_match region 만)
|
||||
Stage A : content → Internal Region (Step 4 의 region 분할 결과 소비)
|
||||
Stage B : Internal Region content → Frame Slot (frame_match region 만 진입)
|
||||
display_only region → display strategy 처리 (Stage B 우회)
|
||||
OUTPUT : placement = {
|
||||
internal_regions: [
|
||||
{..., slot_assignments[], overflow_buffer[], rejection[]}, ...
|
||||
]
|
||||
}
|
||||
SCHEMA : SPEC v1 §4 + §5
|
||||
소비자 : Step 12 (slot payload 생성, region + Frame Slot 단위 grouping)
|
||||
|
||||
Step 12 Slot Payload 생성
|
||||
Step 13 Render
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. schema validation 기준
|
||||
|
||||
> validation = *schema 자체의 구조 검증* 까지만. *실제 MDX 적용 검증 X* (sample budget rule).
|
||||
|
||||
### 6.1 Step 3 — content_object schema
|
||||
|
||||
- [ ] base schema 의 모든 필수 필드 (id, type, role, size_estimate, raw_payload, type_specific) 정의됨
|
||||
- [ ] 6 type 모두 type_specific schema 있음 (text_block / table / transform_table / image / diagram / details)
|
||||
- [ ] role 4 종 (summary / detail / decorative / reference) 정의됨
|
||||
- [ ] role 별 fallback 거동 (decorative drop / detail → details escalate / summary·reference rejection) 명시됨
|
||||
- [ ] *원문 raw_payload 보존* 룰이 schema 에 포함됨 (자름 / 변형 X)
|
||||
|
||||
### 6.2 Step 4 — Internal Region schema (Layer A)
|
||||
|
||||
- [ ] Internal Region entity 의 모든 필수 필드 (region_id, role, content_type, ratio_estimate, content_unit_ids, frame_match_strategy) 정의됨
|
||||
- [ ] Universal Region Model 명시 (every zone has 1+ regions / single-region for text-only / multi-region for mixed-content)
|
||||
- [ ] 3-way decision tree 의 3 분기 (whole-section frame match / child-section grouping / content-type split) 가 *결정론적 함수* 로 표현됨 (AI 판단 X)
|
||||
- [ ] 각 분기의 판단 기준 (cardinality / accepted_content_types / heading depth + content 구조) 명시됨
|
||||
- [ ] region 비율 산정의 size proxy (text_block: line_count / table: rows × line_height / transform_table: pair_count × pair_height / image: aspect_ratio / details: summary line_count) 정의됨
|
||||
- [ ] zone 내 region ratio 합 = 1.0 normalize 룰 명시됨
|
||||
- [ ] frame_match_strategy 의 두 kind (frame_match / display_only) 정의됨
|
||||
- [ ] display_only path 가 frame contract 없이 동작 가능함이 명시됨
|
||||
- [ ] decision 이 unknown / ambiguous 일 때의 fallback 명시됨
|
||||
|
||||
### 6.3 Step 10 (partial) — frame contract schema (Layer B 선언)
|
||||
|
||||
- [ ] `accepted_content_types` 필드 schema 정의됨 (list of type 이름)
|
||||
- [ ] `not_accepted` 필드 schema 정의됨 (디버그용)
|
||||
- [ ] Frame Slot 선언 schema 정의됨 (id / role / accepts / cardinality / partial_target_path) — YAML 필드명 `sub_zones`, 의미 = Frame Slot
|
||||
- [ ] cardinality 표현 방식 (`strict` 또는 `min`/`max`) 정의됨
|
||||
- [ ] F13 / F29 / F16 3 frame 의 Frame Slot declaration 예시 *완비*
|
||||
- [ ] *density envelope 미포함* 이 schema 위에 명시 주석으로 박혀 있음
|
||||
|
||||
### 6.4 Step 11 — placement (2-stage) + display_strategy schema
|
||||
|
||||
- [ ] placement algorithm 2-stage I/O schema 정의됨 (input + Stage A 출력 + Stage B 출력)
|
||||
- [ ] Stage A schema 정의됨 (content_object → Internal Region 매핑 결과 + frame_match_strategy 결정)
|
||||
- [ ] Stage B schema 정의됨 (Internal Region content → Frame Slot 매핑 결과 — frame_match region 만)
|
||||
- [ ] sorting / type 매칭 / cardinality 적용 / role 우선순위 / tie-break 의 결정론적 룰 (Stage B) 명시됨
|
||||
- [ ] display_only region path 의 display_strategy 매핑 정의됨 (image area / table preview / details button / diagram inline)
|
||||
- [ ] 6 type × 3 escalation (inline / preview+details / popup-only) 매트릭스 정의됨
|
||||
- [ ] display_strategy 4 종 (inline_full / inline_preview_with_details / details_only / dropped) 정의됨
|
||||
- [ ] display_strategy 가 *region-level + slot-level 둘 다* 적용됨이 명시됨
|
||||
- [ ] *AI 호출 X* + *원문 손실 금지* 룰이 schema 위에 명시됨
|
||||
- [ ] backward flow 자동화 X (v1 한계) 가 schema 위에 명시됨
|
||||
|
||||
### 6.5 통합 validation
|
||||
|
||||
- [ ] Step 3 → Step 4 의 type 호환 — content_object.type 이 Internal Region.content_type 으로 *결정론적* 매핑
|
||||
- [ ] Step 3 → Step 11 (Stage B) 의 type 호환 — Step 3 의 모든 type 이 어딘가 frame.accepted_content_types 에 등장 가능 (또는 명시적 reject) 또는 display_only path 로 처리 가능
|
||||
- [ ] Step 4 → Step 8 의 ratio 호환 — Internal Region.ratio_estimate 가 Step 8 의 region-level ratio 입력으로 호환
|
||||
- [ ] Step 4 → Step 9 의 frame_match_strategy 호환 — Step 9 의 region-level frame 매칭 입력으로 호환
|
||||
- [ ] Step 4 → Step 11 (Stage A) 의 region 분할 호환 — Stage A 가 Step 4 의 internal_regions 를 *재계산하지 않고 그대로 소비*
|
||||
- [ ] Step 10 의 cardinality 가 Step 11 Stage B 의 placement algorithm 이 소비 가능한 형태
|
||||
|
||||
---
|
||||
|
||||
## 7. AI 원칙
|
||||
|
||||
- 본 plan 작성 / schema 정의 단계 — **AI 호출 없음**
|
||||
- runtime AI = Step 12 의 *light_edit / restructure 의 content_object → Internal Region / Frame Slot proposal* 1 곳만
|
||||
- Step 0 (Figma → HTML 변환 등 사전 준비) 의 AI 사용은 *precondition phase* 로, runtime AI 가 아님
|
||||
|
||||
---
|
||||
|
||||
## 8. 금지
|
||||
|
||||
- 구현 금지 (extractor / planner / parser 코드 작성)
|
||||
- render 변경 금지
|
||||
- frame partial 변경 금지 (Frame Slot / region container marker 추가 미포함)
|
||||
- mapper / classifier 의 region / Frame Slot-aware 진화 금지
|
||||
- details / popup runtime 작성 금지
|
||||
- MDX 01 / 02 / 03 / 04 실행 금지
|
||||
- AI 호출 금지
|
||||
- code / module / HTML marker / attribute *이름 결정* 금지 (SPEC v1 와 동일 — implementation step 에서 결정)
|
||||
- 파일명 변경 금지
|
||||
- next step 추천 금지
|
||||
- 우선순위 결정 금지
|
||||
- A / B / C 선택지 제시 금지
|
||||
|
||||
---
|
||||
|
||||
## 9. 본 plan 의 보존 / 변경 정책
|
||||
|
||||
- 본 plan 은 *schema 작업 범위 + 완료 기준* 의 기준점. schema 작업 진행 중 새로운 edge case 발견 시 본 plan 의 6 절 (validation 기준) 에 *항목 추가* 형태로 갱신
|
||||
- *범위 확장* (예: Step 7 추가, density envelope 포함, code marker 이름 결정 포함) 은 사용자 명시 잠금 후에만
|
||||
- 본 plan 이 완료되면 STATUS-BOARD 의 :
|
||||
- Step 3 → ⚠ partial (schema 정의 완료, 구현 미완)
|
||||
- Step 4 → ⚠ partial (Layer A schema 정의 완료, 구현 미완)
|
||||
- Step 11 → ⚠ partial (2-stage schema 정의 완료, 구현 미완)
|
||||
- Step 10 → ⚠ partial (accepted_content_types + Frame Slot 부분 완료, density envelope 미포함 잔존)
|
||||
- SPEC 의 *추가 갱신* (edge case 보강 등) 은 본 plan 의 §3.1 목표 상태 (completeness reviewed + gaps listed) 의 자연 산출물
|
||||
@@ -0,0 +1,849 @@
|
||||
# Phase Z-2 — content composition planning spec (Layer A: Internal Region + Layer B: Frame Slot)
|
||||
|
||||
**Status** : v1 spec (2026-04-30 refactor — Layer A / Internal Region 추가 + Layer B / Frame Slot 명확화). 정의만. 구현은 별도 step (사용자 승인 후).
|
||||
|
||||
> **v0 → v1 변경 요약**
|
||||
> - Zone Internal Region (Layer A) 를 first-class entity 로 추가 (§2 신규)
|
||||
> - 기존 §2 ~ §8 → §3 ~ §9 로 renumber (v0 의 ## 9 다음 step → v1 의 ## 10)
|
||||
> - 기존 `sub_zone` 단어 = *Frame Slot (Layer B)* 의미로 일관 정리. *YAML 필드명 `sub_zones` 는 코드 reality 로 유지 — 의미만 명시*
|
||||
> - placement algorithm (§4) 을 *2-stage* (Stage A: content → Internal Region / Stage B: Internal Region content → Frame Slot) 로 재작성
|
||||
> - content_object schema (§1) / display strategy 어휘 (§5) / telemetry 구조 (§6) 는 *layer-agnostic 공유 개념* 으로 보호 — substantive 미변경
|
||||
> - code / module / HTML marker 이름은 *implementation step* 으로 defer
|
||||
|
||||
---
|
||||
|
||||
## §0. 목적 / 위치
|
||||
|
||||
본 spec 은 **render *전* composition planning layer** 의 정의. fit_classifier / overflow_router / zone_ratio_retry 같은 *post-render telemetry* 가 아니라, *애초에 content 를 어디에 어떻게 배치할지* 결정하는 *진짜 fit policy 의 중심*.
|
||||
|
||||
```
|
||||
1. PLANNING (composition) ← 본 spec 의 영역
|
||||
- section raw_content → content_object 정규화
|
||||
- Zone Internal Region (Layer A) 분할 — text/table/image/details 에 따라
|
||||
- frame contract → accepted_content_types + Frame Slot (Layer B) 선언
|
||||
- content_object → Internal Region → Frame Slot 배치 (compatibility 기반)
|
||||
- inline preview vs details/popup 표시 전략
|
||||
↓
|
||||
2. RENDER (Jinja2 + frame partial — Frame Slot aware)
|
||||
↓
|
||||
3. POST-RENDER TELEMETRY (A1~A4) ← 별 spec, 이미 구축됨
|
||||
- Selenium → fit_classifier → router → retry → failure_classifier → next_action
|
||||
- 1 단계 (본 spec 영역) 가 정밀하면 거의 trigger 안 됨
|
||||
- exception 케이스의 *진단 + 다음 capability 안내*
|
||||
```
|
||||
|
||||
### Layer 구분
|
||||
|
||||
```
|
||||
Slide → Zone → Internal Region → Frame → Frame Slot → Content
|
||||
──────────── ──────────
|
||||
Layer A Layer B
|
||||
```
|
||||
|
||||
- **Layer A — Zone Internal Region** : Zone *내부* 영역, frame *밖*. content type 기반 분할 (text region / table region / image region / details region). region 별 frame 또는 display strategy 선택.
|
||||
- **Layer B — Frame Slot** : frame *내부* 자리 (= F13 의 pillar_1, F29 의 process_column 등). frame 안에서 content unit 이 들어갈 곳.
|
||||
|
||||
Layer A 와 Layer B 는 *별개 entity* 가 아니라 *한 composition pipeline 의 두 sub-phase*. content_object schema / display strategy 어휘 / telemetry interface 는 *공유*.
|
||||
|
||||
### 본 spec 의 핵심 원칙
|
||||
|
||||
- *render 전* 결정. *render 후 retry* 가 아님
|
||||
- content_object 의 *type* 이 핵심 — text / table / image / diagram / details
|
||||
- Layer A 가 *region 분할* 결정 / Layer B 가 *region 안 Frame Slot 매핑* 결정
|
||||
- 매칭 안 되는 content 는 *details/popup 으로 escalate*. *원문 삭제 / 압축 X*
|
||||
- frame 의 *Frame Slot* 이 어떤 type 을 받을 수 있는지 *명시적으로 선언*
|
||||
|
||||
본 spec 은 *정의만*. 구현 우선순위는 별도 step (사용자 승인 후).
|
||||
|
||||
---
|
||||
|
||||
## §1. content_object 정규화 schema
|
||||
|
||||
> **layer-agnostic** — Layer A / Layer B 둘 다 사용. v0 → v1 refactor 시 *substantive 미변경*.
|
||||
|
||||
MDX section 의 `raw_content` (markdown 문자열) 를 *typed content_object list* 로 정규화.
|
||||
|
||||
### 1.1 base schema
|
||||
|
||||
```yaml
|
||||
section:
|
||||
section_id: str
|
||||
title: str
|
||||
content_objects:
|
||||
- id: str # section 내 unique
|
||||
type: str # text_block / table / image / diagram / details / transform_table
|
||||
role: str # summary / detail / decorative / reference
|
||||
size_estimate:
|
||||
line_count: int # text/details 의 경우
|
||||
rows: int # table 의 경우
|
||||
aspect_ratio: float # image/diagram 의 경우
|
||||
bytes: int # raw payload 크기 (heuristic용)
|
||||
raw_payload: str # 원본 (자름 / 변형 X)
|
||||
type_specific: {...} # 아래 type 별 schema
|
||||
```
|
||||
|
||||
### 1.2 type 별 schema
|
||||
|
||||
#### `text_block` — 자유 텍스트 / 불릿
|
||||
```yaml
|
||||
type: text_block
|
||||
type_specific:
|
||||
format: paragraph | bullet_list | nested_list
|
||||
bullet_count: int # 불릿이면
|
||||
max_indent_level: int
|
||||
has_emphasis: bool # **bold** 등 inline emphasis
|
||||
```
|
||||
|
||||
#### `table` — markdown 표
|
||||
```yaml
|
||||
type: table
|
||||
type_specific:
|
||||
rows: int # header 제외 데이터 row
|
||||
cols: int
|
||||
header_present: bool
|
||||
is_transform: bool # AS-IS / arrow / TO-BE 구조면 true → transform_table 으로 분류
|
||||
raw_md: str # 원본 markdown
|
||||
```
|
||||
|
||||
#### `transform_table` — AS-IS / TO-BE pair (`table` 의 specialization)
|
||||
```yaml
|
||||
type: transform_table
|
||||
type_specific:
|
||||
pair_count: int # 행 수 (각 행 = 1 transform pair)
|
||||
arrow_glyph: str # ➠ 등
|
||||
rows: [{from: str, arrow: str, to: str}]
|
||||
```
|
||||
|
||||
#### `image` — markdown / HTML 이미지
|
||||
```yaml
|
||||
type: image
|
||||
type_specific:
|
||||
src: str
|
||||
alt: str
|
||||
aspect_ratio: float | null # 알면 (asset metadata 에서)
|
||||
intrinsic_width_px: int | null
|
||||
intrinsic_height_px: int | null
|
||||
```
|
||||
|
||||
#### `diagram` — SVG / 도식
|
||||
```yaml
|
||||
type: diagram
|
||||
type_specific:
|
||||
source_type: svg_inline | svg_file | mermaid | other
|
||||
src: str | null
|
||||
```
|
||||
|
||||
#### `details` — `<details>/<summary>` 또는 ":::note[...]" 같은 명시 marker
|
||||
```yaml
|
||||
type: details
|
||||
type_specific:
|
||||
summary: str # 펼치기 전 보일 헤더
|
||||
body_raw: str # 펼친 후 content (자름 X)
|
||||
display_hint: button | inline_collapse | popup # MDX 가 hint 줄 수 있음
|
||||
```
|
||||
|
||||
### 1.3 role 의미
|
||||
|
||||
- `summary` — section 의 *핵심 메시지*. inline 으로 반드시 표시
|
||||
- `detail` — 보조 / 부연. 공간 부족 시 details 로 escalate 가능
|
||||
- `decorative` — 시각 보조 (배경 이미지 등). 공간 부족 시 *생략 가능*
|
||||
- `reference` — 출처 / footnote / 보충 자료
|
||||
|
||||
### 1.4 정규화 parser 위치
|
||||
|
||||
신규 module (이름 *implementation step 에서 결정* — defer) :
|
||||
- `extract_content_objects(section: MdxSection) -> list[ContentObject]`
|
||||
- markdown AST parser (예: mistune) 활용 또는 regex 기반 v0
|
||||
- 현재 `align_sections_to_v4_granularity` 다음 단계에 삽입
|
||||
|
||||
---
|
||||
|
||||
## §2. Zone Internal Region schema (Layer A — 신규)
|
||||
|
||||
본 section 은 **v1 신규**. Zone *내부* 영역 (frame *밖*) 의 entity 정의 + 3-way decision tree + region 비율 + region → frame/display interface.
|
||||
|
||||
### 2.1 Internal Region entity schema
|
||||
|
||||
```yaml
|
||||
zone:
|
||||
zone_id: str
|
||||
layout_position: str # top / bottom / left / right / ...
|
||||
internal_regions:
|
||||
- region_id: str # zone 내 unique
|
||||
role: str # primary / secondary / supporting / reference
|
||||
content_type: str # text / table / image / diagram / details / mixed
|
||||
ratio_estimate: float # 0.0 ~ 1.0 (zone 내 비율, 합 = 1.0)
|
||||
content_unit_ids: [str] # 이 region 에 배치된 content_object id 들 (Layer A → B 의 입력)
|
||||
frame_match_strategy: # region → frame/display 매칭 결과
|
||||
kind: str # frame_match | display_only
|
||||
frame_id: str | null # frame_match 이면 실제 frame
|
||||
display_strategy: str # inline_full | inline_preview_with_details | details_only | dropped
|
||||
```
|
||||
|
||||
### 2.2 Universal Region Model
|
||||
|
||||
```
|
||||
모든 Zone 은 1 개 이상의 Internal Region 을 가짐.
|
||||
|
||||
text-only zone = single-region zone (현 거동의 자연 표현)
|
||||
mixed-content zone = multi-region zone
|
||||
|
||||
각 Internal Region 은 *자기만의* frame match + display strategy 를 가질 수 있음.
|
||||
```
|
||||
|
||||
text-only section 도 *single-region zone* 으로 표현 (= 현 거동 보존). mixed-content (text + table / text + image / 등) 은 *multi-region zone* 으로 확장. region 이 *first-class entity* — special case 가 아님.
|
||||
|
||||
### 2.3 3-way decision tree
|
||||
|
||||
각 section 에 대해 *Internal Region 분할* 여부 결정 :
|
||||
|
||||
```
|
||||
section 전체 → 1 frame 매칭 가능?
|
||||
├ YES → whole-section frame match
|
||||
│ → single-region zone (region 1개, content_type=primary)
|
||||
│
|
||||
└ NO → child-section grouping 가능?
|
||||
├ YES → group merge → 1 frame 매칭
|
||||
│ → single-region zone (region 1개, content_type=primary)
|
||||
│
|
||||
└ NO → content-type split
|
||||
→ text region / table region / image region / details region
|
||||
→ region 비율 산정 (예: text 80% / table 20%)
|
||||
→ multi-region zone (region 2~N개)
|
||||
```
|
||||
|
||||
**판단 기준** :
|
||||
- *whole-section frame match* — section 전체와 frame contract 의 accepted_content_types 가 호환 + cardinality 가 맞는 경우
|
||||
- *child-section grouping* — sibling section 들이 같은 frame contract (예: F16 의 4-quadrant) 와 묶이는 경우. heading depth + content 구조 + frame cardinality 로 판단
|
||||
- *content-type split* — section 안에 *호환 안 되는 content type 조합* 이 있을 때 (text + table 처럼)
|
||||
|
||||
### 2.4 region 비율 산정
|
||||
|
||||
content type 별 *expected size* 기반 :
|
||||
|
||||
| content type | size proxy |
|
||||
|---|---|
|
||||
| `text_block` | line_count (text_block.size_estimate.line_count) |
|
||||
| `table` | rows × line_height_factor (rows × 1.2 ~ 1.5) |
|
||||
| `transform_table` | pair_count × pair_height |
|
||||
| `image` | aspect_ratio 기반 height (width 고정 시) |
|
||||
| `diagram` | aspect_ratio 기반 height |
|
||||
| `details` | summary line_count (펼치기 전) |
|
||||
|
||||
*zone 내 합 = 1.0* 으로 normalize. role 가중치 (primary > supporting) 는 v1 에서 균등 — *별 step refinement*.
|
||||
|
||||
### 2.5 Internal Region Layout / Topology Vocabulary
|
||||
|
||||
region 들의 *공간 배치 패턴* 어휘. multi-region zone 의 *방향 / 배치* 결정. ratio 와 content_type 만으로는 *어떻게 배치되는지* 가 결정 안 되므로 *vocabulary 단계* 가 명시적으로 필요.
|
||||
|
||||
#### 명명 style — slide-level vs region-level 의 *의도된 비대칭*
|
||||
|
||||
- **Slide-level 8 vocabulary** (Step 7) = *count-based* 명명 (`horizontal-2`, `top-1-bottom-2`). zone 의 *layout-driven* 성격 반영
|
||||
- **Region-level vocabulary** (본 §) = *descriptor-based* 명명 (`vertical-stack`, `main-support`). region 의 *content-type / role-driven* 성격 반영
|
||||
- 이 비대칭은 *의도된 것*. region count 가 작고 (1~4) content type / role 이 핵심 결정 기준이라 descriptor 가 더 의미 전달
|
||||
|
||||
#### v1 vocabulary (6 entry)
|
||||
|
||||
| region_layout_type | 의미 | 사용 조건 |
|
||||
|---|---|---|
|
||||
| `region-single` | 1 region 만 (zone 전체 = region 1개) | region count = 1 (single-region zone) |
|
||||
| `region-vertical-stack` | 위·아래 수직 stack | region count ≥ 2, content type 이 *순차적 흐름* (예: 본문 + supporting). default fallback |
|
||||
| `region-horizontal-split` | 좌·우 수평 분할 | region count = 2, content type 이 *대등 비교* 또는 *side-by-side 시각* (text + image, text + diagram 등) |
|
||||
| `region-main-support` | main region + supporting region (asymmetric ratio) | region count = 2, role = [primary, supporting], ratio asymmetric (예: 0.7 / 0.3) |
|
||||
| `region-preview-details` | inline preview region + details/popup region | details_presence = true, 또는 큰 content (table N ≥ 5, long text 등) |
|
||||
| `region-grid-2x2` | 2×2 grid (4 region) | region count = 4, content type 이 *대등 4 항목* |
|
||||
|
||||
#### deterministic decision rule
|
||||
|
||||
`region_layout_type` 은 AI 호출 X. 다음 *결정론적 함수* 로 도출 :
|
||||
|
||||
```
|
||||
입력 :
|
||||
- region_count : int
|
||||
- content_type_mix : list[str]
|
||||
- ratio_estimate : list[float]
|
||||
- role 분포 : list[str] (primary / supporting / ...)
|
||||
- details_presence : bool
|
||||
|
||||
결정 분기 (순차 적용, 첫 매칭 채택) :
|
||||
1. region_count == 1
|
||||
→ region-single
|
||||
|
||||
2. details_presence == true 또는 큰 content (table N ≥ 5 / long text 등)
|
||||
→ region-preview-details
|
||||
|
||||
3. region_count == 4 AND content_type_mix 가 *4 종 대등*
|
||||
→ region-grid-2x2
|
||||
|
||||
4. region_count == 2 AND role == [primary, supporting] AND ratio asymmetric (max / min ≥ 2)
|
||||
→ region-main-support
|
||||
|
||||
5. region_count == 2 AND content_type_mix 내 visual element (image / diagram) 포함
|
||||
→ region-horizontal-split
|
||||
|
||||
6. fallback (위 모든 분기 미매칭)
|
||||
→ region-vertical-stack
|
||||
```
|
||||
|
||||
#### 출력 schema
|
||||
|
||||
각 zone 의 `internal_regions` 컨테이너에 region_layout 필드 추가 :
|
||||
|
||||
```yaml
|
||||
zone:
|
||||
zone_id: str
|
||||
internal_regions: [...]
|
||||
region_layout:
|
||||
region_layout_type: str # 위 6 entry 중 하나
|
||||
region_order: [str] # region_id 의 배치 순서 (위→아래 / 좌→우 등)
|
||||
region_placement: str # vertical | horizontal | grid | main-side | stack
|
||||
```
|
||||
|
||||
#### 구현 위치 (예정)
|
||||
|
||||
신규 module (이름 *implementation step 에서 결정* — defer) :
|
||||
- input : `zone.internal_regions` (from §2.1, ratio + content_type 산정 후)
|
||||
- output : `zone.region_layout` (region_layout_type + order + placement)
|
||||
- 위치 : §2.4 region 비율 산정 *직후*, §2.6 region → frame / display interface *직전*
|
||||
- 결정 함수 deterministic — AI 호출 X
|
||||
|
||||
#### v1 vocabulary 의 한계 / 향후 확장 (참고)
|
||||
|
||||
- 현재 6 entry = v1 starting set. 추후 sample / frame DB 확장 시 vocabulary 추가 가능 (예: `region-vertical-3` / `region-horizontal-3` / `region-main-side-bottom` 등)
|
||||
- v1 fallback (`region-vertical-stack`) 이 매칭 안 되는 패턴 발견 시 별 step 으로 entry 추가
|
||||
|
||||
### 2.6 region → frame / display 매칭 interface
|
||||
|
||||
각 region 은 다음 중 하나 :
|
||||
- **frame_match** — region 의 content_type 에 호환되는 frame 선택. 매칭된 frame 의 contract 가 §3 의 입력. Stage B 진입
|
||||
- **display_only** — frame 없이 display strategy 로 처리 (image area 직접 / table preview / details button). frame contract 미사용. Stage B 우회
|
||||
|
||||
현재 *runtime contract-registered / verified* frame set (F13 / F29 / F16) 은 모두 text region 만 수용. image / table / details region 은 현재 *display_only* path.
|
||||
|
||||
### 2.7 구현 위치 (예정)
|
||||
|
||||
신규 module (이름 *implementation step 에서 결정* — defer) :
|
||||
- input : `section.content_objects` (from §1)
|
||||
- output : `zone.internal_regions` (with ratio + frame_match_strategy) + `zone.region_layout` (from §2.5)
|
||||
- 현재 composition planner 의 frame 매칭 *직전* 에 삽입
|
||||
- region 분할 / 비율 산정 / topology vocabulary 선택 / 매칭 기준 — 모두 deterministic rule 기반 (AI 호출 X)
|
||||
|
||||
---
|
||||
|
||||
## §3. frame contract 확장 — accepted_content_types + Frame Slot (Layer B)
|
||||
|
||||
> **v0 의 §2 → v1 의 §3 (renumber)**. *Layer B / Frame Slot* spec.
|
||||
|
||||
`templates/phase_z2/catalog/frame_contracts.yaml` 에 *2 개 신규 필드* 추가.
|
||||
|
||||
### 3.1 schema
|
||||
|
||||
```yaml
|
||||
<template_id>:
|
||||
... (기존 필드 그대로 — source_shape / cardinality / payload / visual_hints / ...)
|
||||
|
||||
# NEW : 이 frame 이 받을 수 있는 content type 들
|
||||
accepted_content_types:
|
||||
- text_block
|
||||
- transform_table
|
||||
- ...
|
||||
not_accepted: # 명시적 비호환 (디버그용)
|
||||
- image
|
||||
- diagram
|
||||
|
||||
# NEW : frame 내부 Frame Slot 선언
|
||||
# YAML field name = 'sub_zones' — 코드 / catalog reality 로 유지. 의미 = Frame Slot (Layer B).
|
||||
sub_zones:
|
||||
- id: str # Frame Slot 식별자
|
||||
role: main_text | supporting_visual | label | details_button | ...
|
||||
accepts: # 이 Frame Slot 이 받는 content_object type
|
||||
- text_block
|
||||
- transform_table
|
||||
cardinality: # Frame Slot 내 capacity
|
||||
strict: int # 정확히 N개
|
||||
# or
|
||||
min: int
|
||||
max: int
|
||||
partial_target_path: # frame partial template 에서 이 Frame Slot 의 위치
|
||||
# 예 : "f29b__cell--left.row-1" — partial 안 marker (attribute name *implementation step 에서 결정*)
|
||||
```
|
||||
|
||||
> **YAML field 이름** : 코드 / catalog reality 로 `sub_zones` 유지. 의미는 *Frame Slot* (= Layer B).
|
||||
|
||||
### 3.2 구체 예시 (현재 3 frame)
|
||||
|
||||
#### F13 — three_parallel_requirements
|
||||
```yaml
|
||||
three_parallel_requirements:
|
||||
...
|
||||
accepted_content_types: [text_block]
|
||||
sub_zones: # = Frame Slots (Layer B)
|
||||
- id: pillar_1
|
||||
role: main_text
|
||||
accepts: [text_block]
|
||||
cardinality: { strict: 1 }
|
||||
- id: pillar_2
|
||||
role: main_text
|
||||
accepts: [text_block]
|
||||
cardinality: { strict: 1 }
|
||||
- id: pillar_3
|
||||
role: main_text
|
||||
accepts: [text_block]
|
||||
cardinality: { strict: 1 }
|
||||
```
|
||||
|
||||
#### F29 — process_product_two_way
|
||||
```yaml
|
||||
process_product_two_way:
|
||||
...
|
||||
accepted_content_types: [text_block, transform_table]
|
||||
sub_zones: # = Frame Slots (Layer B)
|
||||
- id: process_column
|
||||
role: main_text
|
||||
accepts: [text_block, transform_table]
|
||||
cardinality: { strict: 3 } # 3 sections per column
|
||||
- id: product_column
|
||||
role: main_text
|
||||
accepts: [text_block] # product 쪽은 transform 안 받음 (현재 frame 의 시각적 구분)
|
||||
cardinality: { strict: 3 }
|
||||
```
|
||||
|
||||
#### F16 — bim_issues_quadrant_four
|
||||
```yaml
|
||||
bim_issues_quadrant_four:
|
||||
...
|
||||
accepted_content_types: [text_block]
|
||||
sub_zones: # = Frame Slots (Layer B)
|
||||
- id: quadrant_1
|
||||
role: main_text
|
||||
accepts: [text_block]
|
||||
cardinality: { strict: 1 }
|
||||
- id: quadrant_2
|
||||
role: main_text
|
||||
accepts: [text_block]
|
||||
cardinality: { strict: 1 }
|
||||
- id: quadrant_3
|
||||
role: main_text
|
||||
accepts: [text_block]
|
||||
cardinality: { strict: 1 }
|
||||
- id: quadrant_4
|
||||
role: main_text
|
||||
accepts: [text_block]
|
||||
cardinality: { strict: 1 }
|
||||
```
|
||||
|
||||
### 3.3 partial template 의 Frame Slot 마커
|
||||
|
||||
frame partial 의 HTML 에 Frame Slot 식별 marker 추가 — render 후 Selenium 이 Frame Slot 단위 측정 가능, A1~A4 의 정밀도 향상. *marker attribute name (예: `data-subzone` / `data-frame-slot` / 기타) 은 implementation step 에서 결정 — defer*.
|
||||
|
||||
---
|
||||
|
||||
## §4. placement algorithm — 2-stage (Layer A → Layer B)
|
||||
|
||||
> **v0 의 §3 → v1 의 §4 (renumber + 2-stage 재작성)**. *layer 순차 dependency*.
|
||||
|
||||
### 4.1 input / output
|
||||
|
||||
```
|
||||
input :
|
||||
section: { section_id, content_objects: [...] }
|
||||
zone: { zone_id, layout_position }
|
||||
available_frames: [...] # V4 top-k from Step 5
|
||||
|
||||
output :
|
||||
internal_regions: [
|
||||
{
|
||||
region_id, role, content_type, ratio_estimate,
|
||||
content_unit_ids: [...],
|
||||
frame_match_strategy: { kind, frame_id, display_strategy },
|
||||
# Stage B 결과 (frame_match region 만)
|
||||
slot_assignments: [
|
||||
{ content_object_id, frame_slot_id, display_strategy }
|
||||
],
|
||||
overflow_buffer: [...],
|
||||
rejection: [...]
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
### 4.2 Stage A — content → Internal Region (Layer A)
|
||||
|
||||
> region 분할 결정 + content_object → region 배치.
|
||||
|
||||
```
|
||||
1. 3-way decision (§2.3) 적용
|
||||
- whole-section frame fit 가능 → single-region (Stage B 로 1 region)
|
||||
- child-grouping 가능 → group merge → single-region
|
||||
- content-type split 필요 → multi-region
|
||||
|
||||
2. multi-region 인 경우 :
|
||||
- content_object.type 별로 region 분류 (text region / table region / image region / ...)
|
||||
- 같은 region 안의 content_object 끼리 묶음
|
||||
- region 별 ratio 산정 (§2.4)
|
||||
|
||||
3. region 별 frame_match_strategy 결정 :
|
||||
- region.content_type 이 frame.accepted_content_types 에 매칭 가능 → frame_match
|
||||
- 매칭 frame 없음 → display_only (image / table / details path)
|
||||
|
||||
4. 결과 : zone.internal_regions = [region_1, region_2, ...]
|
||||
각 region 은 content_unit_ids + frame_match_strategy 를 가짐
|
||||
```
|
||||
|
||||
### 4.3 Stage B — Internal Region content → Frame Slot (Layer B)
|
||||
|
||||
> region 의 content 를 frame 의 Frame Slot 에 배치. *frame_match_strategy.kind == "frame_match"* 인 region 에만 적용.
|
||||
|
||||
각 frame_match region 에 대해 :
|
||||
|
||||
```
|
||||
1. content_object 정렬
|
||||
- role 기준 우선순위 : summary > reference > detail > decorative
|
||||
- 같은 role 내 raw_payload 등장 순서
|
||||
|
||||
2. content_object.type 이 frame.accepted_content_types 에 없는 것
|
||||
→ rejection 으로 분리. 본 frame 부적합 신호 (Stage A 의 frame_match_strategy 재검토 신호)
|
||||
|
||||
3. 남은 content_object 를 Frame Slot 들에 배치
|
||||
- 각 Frame Slot 을 순회 (frame contract 의 declaration 순서)
|
||||
- Frame Slot.accepts 에 매칭되는 content_object 들에서
|
||||
cardinality.strict 또는 max 수만큼 할당
|
||||
- role 우선순위 높은 것부터
|
||||
|
||||
4. 배치 안 된 content_object
|
||||
- role = decorative → 무조건 drop (생략)
|
||||
- role = detail → overflow_buffer 로 (details/popup 후보)
|
||||
- role = summary / reference → rejection (frame 부적합)
|
||||
|
||||
5. 결과 :
|
||||
- slot_assignments : 정확히 무엇이 어디로
|
||||
- overflow_buffer : details/popup 으로 escalate 할 candidate
|
||||
- rejection : 본 frame 으로는 표현 불가 — frame_reselect 신호
|
||||
```
|
||||
|
||||
### 4.4 매칭 충돌 / tie-break
|
||||
|
||||
Frame Slot 단위로 :
|
||||
|
||||
- 동일 Frame Slot 에 다수 content_object 후보 시 :
|
||||
- role 우선순위 (summary > reference > detail)
|
||||
- 동률 시 size_estimate 작은 것 우선 (fit 가능성 ↑)
|
||||
- 동일 content_object 가 여러 Frame Slot 에 매칭 가능 시 :
|
||||
- role 매칭 우선 (Frame Slot.role == content_object.role)
|
||||
- 그래도 동률이면 contract declaration 순서 (앞쪽 Frame Slot 우선)
|
||||
|
||||
### 4.5 display_only region 의 처리
|
||||
|
||||
`frame_match_strategy.kind == "display_only"` 인 region 은 Stage B 우회. 대신 :
|
||||
- image region → image area 직접 배치 (frame 없이, region 안에 직접 inline)
|
||||
- table region → table preview (rows ≤ N inline) + 자세히보기 (rows > N popup)
|
||||
- details region → details button + popup
|
||||
- diagram region → diagram inline
|
||||
|
||||
display strategy 어휘는 §5 와 동일 — region-level 적용.
|
||||
|
||||
### 4.6 구현 위치 (예정)
|
||||
|
||||
신규 module (이름 *implementation step 에서 결정* — defer) :
|
||||
- `plan_placement(section, zone, available_frames) -> Placement`
|
||||
- composition planner 의 frame 매칭 *직후*, slot_payload 생성 *직전*
|
||||
- Stage A → Stage B 순차 실행
|
||||
- 결과를 slot_payload 생성 단계에 전달
|
||||
|
||||
---
|
||||
|
||||
## §5. 표시 전략 — inline preview vs details / popup escalation
|
||||
|
||||
> **v0 의 §4 → v1 의 §5 (renumber)**. **layer-agnostic** — region-level (Stage A) + slot-level (Stage B) 둘 다 적용. *어휘 미변경*.
|
||||
|
||||
### 5.1 결정 기준 (per content_object type)
|
||||
|
||||
| type | inline 가능 조건 | preview + details 전환 | popup-only 전환 |
|
||||
|---|---|---|---|
|
||||
| `text_block` | line_count ≤ Frame Slot capacity | line_count > capacity AND role=detail | role=detail AND line_count >> capacity (예: 20+) |
|
||||
| `table` (rows N) | N ≤ 4 | 5 ≤ N ≤ 7 (preview 첫 N rows + details) | N ≥ 8 (popup-only) |
|
||||
| `transform_table` | rows ≤ frame 의 transform Frame Slot capacity (보통 3) | rows > capacity, 일부 inline | rows >> capacity |
|
||||
| `image` | aspect_ratio fit 가능 | 일부 frame 에서 inline + details 의 thumbnail | 거의 없음 (image 는 보통 inline 또는 drop) |
|
||||
| `diagram` | Frame Slot 호환 | preview thumbnail + popup full | popup-only |
|
||||
| `details` (already-marked) | inline 만 안 함 (정의상) | summary inline + body popup | summary + body popup |
|
||||
|
||||
### 5.2 *원문 손실 금지* 룰
|
||||
|
||||
- 표시 전략 결정은 *어디 보여줄지*. *content 자르지 / 압축 / 요약 X*
|
||||
- inline preview 도 *raw_payload 의 일부* 만 빌려옴. 나머지는 details 로
|
||||
- AI 호출 X — 모든 결정은 deterministic rule 기반
|
||||
|
||||
### 5.3 적용 layer
|
||||
|
||||
display strategy 어휘 (`inline_full` / `inline_preview_with_details` / `details_only` / `dropped`) 는 *동일* :
|
||||
- **region-level** (Stage A 의 display_only region) — image area / table preview / details button 등
|
||||
- **slot-level** (Stage B 의 frame_match region 안 Frame Slot 별 content) — Frame Slot 안 content 가 fit 안 되면 escalate
|
||||
|
||||
### 5.4 details / popup runtime
|
||||
|
||||
- frame partial 또는 region container 에 `<details>/<summary>` 또는 별 button + popup layer
|
||||
- 단순 v0 : `<details>` 내장 — 클릭으로 펼침
|
||||
- 향후 v1 : 별도 popup overlay (CLAUDE.md 의 자세히보기 원칙)
|
||||
|
||||
### 5.5 구현 위치 (예정)
|
||||
|
||||
placement planner (§4.6) 의 후속 단계 — 각 assignment / region 에 `display_strategy` 부착 :
|
||||
- `inline_full` — content 전체 inline
|
||||
- `inline_preview_with_details` — 일부 inline, 나머지 details
|
||||
- `details_only` — summary 만 inline, content 는 popup
|
||||
- `dropped` — decorative 가 공간 부족으로 생략
|
||||
|
||||
---
|
||||
|
||||
## §6. A1~A4 telemetry 와의 interface
|
||||
|
||||
> **v0 의 §5 → v1 의 §6 (renumber)**. *layer-agnostic*. *구조 미변경* — `sub_zone` 단어 mechanical rename + region-level metadata 추가.
|
||||
|
||||
본 composition layer 와 기존 telemetry layer (A1~A4) 가 *어떻게 흐르는지*.
|
||||
|
||||
### 6.1 forward flow (composition → render → telemetry)
|
||||
|
||||
```
|
||||
section
|
||||
↓ extract_content_objects
|
||||
content_objects
|
||||
↓ placement_planner (Stage A → Stage B)
|
||||
placement {
|
||||
internal_regions: [
|
||||
{
|
||||
region_id, content_type, ratio_estimate,
|
||||
slot_assignments: [{content_object_id, frame_slot_id, display_strategy}],
|
||||
overflow_buffer: [...],
|
||||
rejection: [...],
|
||||
}
|
||||
]
|
||||
}
|
||||
↓ slot_payload 생성 (region + Frame Slot 단위로 grouping)
|
||||
slot_payload (with region + Frame Slot metadata)
|
||||
↓ render (frame partial — Frame Slot aware + region container aware)
|
||||
HTML
|
||||
↓ Selenium check
|
||||
overflow signals
|
||||
↓ A1 fit_classifier
|
||||
categories
|
||||
↓ A2 router
|
||||
proposed_actions
|
||||
↓ A3 retry / A4 failure_classifier / next_action
|
||||
final_status
|
||||
```
|
||||
|
||||
### 6.2 telemetry 에 전달되는 새 metadata
|
||||
|
||||
각 zone 의 debug entry 에 추가 :
|
||||
```yaml
|
||||
zone:
|
||||
... (기존)
|
||||
internal_regions: # Layer A
|
||||
- region_id
|
||||
content_type
|
||||
ratio_estimate
|
||||
frame_match_strategy
|
||||
placement:
|
||||
slot_assignments: [...] # 이 zone 에 어떤 content_object 가 어디 Frame Slot 으로
|
||||
overflow_buffer: [...] # details 로 간 것
|
||||
rejection: [...] # frame 부적합 후보
|
||||
region_metrics: # Selenium 이 region 별로 측정 (Layer A)
|
||||
- region_id
|
||||
ch / sh / excess_y # region 단위 overflow
|
||||
frame_slot_metrics: # Selenium 이 Frame Slot 별로 측정 (Layer B, frame_match region 만)
|
||||
- frame_slot_id
|
||||
content_object_id
|
||||
ch / sh / excess_y # Frame Slot 단위 overflow
|
||||
```
|
||||
|
||||
### 6.3 backward flow (telemetry → composition)
|
||||
|
||||
A4 의 `next_proposed_action` 이 `frame_internal_fit_candidate` 또는 `frame_reselect` 일 때 :
|
||||
- composition layer 가 *재호출* 됨 (단, retry budget 별도)
|
||||
- 다른 frame 또는 다른 placement 시도
|
||||
|
||||
본 v1 에서는 *backward flow 자동화 X* (구현 단계). placement 가 정확히 되어 있으면 telemetry 거의 trigger X.
|
||||
|
||||
### 6.4 fit_classifier 의 *content_type aware* 진화
|
||||
|
||||
현재 fit_classifier 는 *className → semantic_content_type* 매핑. 본 spec 적용 후 :
|
||||
- Selenium 이 region marker / Frame Slot marker / content_object_id marker 를 읽음 (marker attribute name *implementation step 에서 결정* — defer)
|
||||
- classifier 는 *content_object 의 type* 을 직접 알 수 있음
|
||||
- 분류 정밀도 향상 (예: F29 의 frame_match region 안 Frame Slot 의 transform-block 이 transform_table content_object 임을 *직접* 알 수 있음 — 현재는 inner_content_signals 로 추론)
|
||||
|
||||
---
|
||||
|
||||
## §7. current code gap — 재사용 / 신규 분리
|
||||
|
||||
> **v0 의 §6 → v1 의 §7 (renumber)**. 신규 module 이름 *defer*.
|
||||
|
||||
### 7.1 이미 있는 것 (재사용)
|
||||
|
||||
- MDX parser : section 단위 (## / ### drilling)
|
||||
- align_sections_to_v4_granularity
|
||||
- composition planner (parent_merged_inferred 포함)
|
||||
- frame_contracts.yaml + builder/parser registry
|
||||
- mapper (catalog-driven slot_payload 생성)
|
||||
- Jinja2 render
|
||||
- 8-preset layout vocabulary
|
||||
- A1~A4 telemetry chain
|
||||
|
||||
### 7.2 신규 필요
|
||||
|
||||
| 항목 | 위치 | 비고 |
|
||||
|---|---|---|
|
||||
| **content_object 정규화** | 신규 module (이름 *defer*) | markdown AST 또는 regex 기반 v0 |
|
||||
| **Internal Region planner (Layer A)** | 신규 module (이름 *defer*) | 3-way decision + region 비율 + frame_match_strategy 결정 |
|
||||
| **frame_contracts.yaml** 의 `accepted_content_types` + `sub_zones` 필드 (= Frame Slot 선언) | catalog (기존 yaml 확장) | 3 frame (F13/F29/F16) 우선 |
|
||||
| **placement_planner (Layer A → Layer B)** | 신규 module (이름 *defer*) | Stage A: content → Internal Region / Stage B: region content → Frame Slot |
|
||||
| **display_strategy** 결정기 | placement_planner 내부 | inline_full / inline_preview_with_details / details_only / dropped |
|
||||
| **frame partial 에 Frame Slot 마커** | `templates/phase_z2/families/*.html` | marker attribute name *defer* |
|
||||
| **region container 마커** | `templates/phase_z2/slide_base.html` 또는 partial | region 단위 측정 marker, name *defer* |
|
||||
| **details/popup runtime** | partial template 또는 base slide | `<details>` 우선, 추후 popup overlay |
|
||||
| **fit_classifier 의 region / Frame Slot 인식** | `src/phase_z2_classifier.py` 확장 | inner_content_signals → region / Frame Slot 직접 read |
|
||||
| **mapper 의 region / Frame Slot-aware slot_payload** | `src/phase_z2_mapper.py` 확장 | builder 들이 region + Frame Slot 그룹핑 인식 |
|
||||
|
||||
### 7.3 정의 vs 구현 분리
|
||||
|
||||
본 spec 은 *정의만*. 구현 axis 는 별도 step :
|
||||
- B1. content_extractor (MDX → content_object 정규화)
|
||||
- B2. internal_region_planner (Layer A — 3-way decision + 비율 + frame_match_strategy)
|
||||
- B3. frame_contracts 의 accepted_content_types + sub_zones (= Frame Slot) 선언 (3 frame)
|
||||
- B4. placement_planner (Layer A → Layer B 통합)
|
||||
- B5. partial / region container marker 추가 + telemetry 연동 (이름 결정 포함)
|
||||
- B6. details/popup runtime
|
||||
|
||||
각 axis 는 *별도 step*. 한 axis 씩 사용자 승인 후 진행.
|
||||
|
||||
> **module / marker / attribute 이름** : 본 spec 에서 *defer*. implementation step 에서 결정.
|
||||
|
||||
---
|
||||
|
||||
## §8. 본 spec 의 활용
|
||||
|
||||
> **v0 의 §7 → v1 의 §8 (renumber)**.
|
||||
|
||||
### 8.1 composition layer 의 룰북
|
||||
|
||||
향후 frame 추가 / content_object 변경 / Layer A 재분할 / Frame Slot 매핑 변경 시 본 spec 의 schema 를 따름. *임의 매핑 / hack 차단*.
|
||||
|
||||
### 8.2 telemetry 와의 cross-check
|
||||
|
||||
A1~A4 의 분류 결과 (`structural_minor_overflow` 등) 가 본 spec 의 placement 결과와 *일치하는가* 확인 가능. 불일치 = composition planning 의 *예상치 못한 케이스* — 진단 자료.
|
||||
|
||||
### 8.3 미사용 sample (MDX 01 / 02) 진단
|
||||
|
||||
본 spec 적용 후 MDX 01/02 를 돌리면 :
|
||||
- 각 section 의 content_object 정규화 결과 visible
|
||||
- 각 zone 의 Internal Region 분할 결과 visible (single vs multi)
|
||||
- 어떤 content type 이 frame contract 에 없는지 (frame 추가 필요 신호)
|
||||
- placement 의 rejection 비율 (frame coverage gap)
|
||||
- overflow_buffer 의 details 후보 (popup runtime 필요 신호)
|
||||
- display_only region 비율 (현재 frame DB 의 Layer A 미커버 영역)
|
||||
|
||||
이 정보가 *generalization validation* 의 진짜 신호.
|
||||
|
||||
---
|
||||
|
||||
## §9. MDX 03 의 case 를 본 spec 으로 검증 (illustrative)
|
||||
|
||||
> **v0 의 §8 → v1 의 §9 (renumber)**. mechanical rename + 2-stage 표현.
|
||||
|
||||
> MDX 03 = sample. *fix 대상 X*. 본 spec 룰의 *예시 적용*.
|
||||
|
||||
### 9.1 03-1 의 content_object 정규화 (예상)
|
||||
|
||||
```yaml
|
||||
section_id: "03-1"
|
||||
title: "1. DX 시행을 위한 필수 요건"
|
||||
content_objects:
|
||||
- id: "03-1.text-1"
|
||||
type: text_block
|
||||
role: summary
|
||||
type_specific: { format: nested_list, bullet_count: 3 (top), nested_count: 7 }
|
||||
size_estimate: { line_count: ~12 }
|
||||
```
|
||||
|
||||
→ 1 content_object (text_block, role=summary).
|
||||
|
||||
### 9.2 03-1 의 Stage A → Stage B (F13 contract 적용)
|
||||
|
||||
**Stage A** :
|
||||
- 3-way decision : section 전체가 F13 (3 pillars) 의 child grouping 으로 매칭 → *whole-section frame match*
|
||||
- single-region zone, content_type=text, ratio=1.0
|
||||
- frame_match_strategy = { kind: "frame_match", frame_id: "F13" }
|
||||
|
||||
**Stage B** :
|
||||
- F13 sub_zones (= Frame Slots) : [pillar_1, pillar_2, pillar_3] (각 cardinality strict 1, accepts text_block)
|
||||
- text_block 1 개 → 3 Frame Slot 에 어떻게 배치?
|
||||
- 현재 mapper (`pillar_item` parser) 가 *implicit* 으로 top_bullet 3 개를 3 pillar 에 분배
|
||||
- 본 spec 적용 시 : text_block 의 nested 구조를 *3 sub_text_block* 으로 sub-decompose 하거나, Frame Slot cardinality 를 *aggregate (3)* 으로 해석할지 결정 필요
|
||||
- v1 단순화 : text_block 의 top-bullet 단위가 *implicit 한 sub-content_object* — 향후 explicit 화
|
||||
|
||||
### 9.3 03-2 의 case (transform_table 포함)
|
||||
|
||||
```yaml
|
||||
section_id: "03-2"
|
||||
content_objects:
|
||||
- id: "03-2.transform-1"
|
||||
type: transform_table
|
||||
role: summary
|
||||
type_specific: { pair_count: 3 }
|
||||
- id: "03-2.text-1"
|
||||
type: text_block
|
||||
role: detail
|
||||
type_specific: { bullet_count: 1 }
|
||||
- id: "03-2.text-2"
|
||||
type: text_block
|
||||
role: detail
|
||||
type_specific: { bullet_count: 1 }
|
||||
- id: "03-2.text-3"
|
||||
type: text_block
|
||||
role: detail
|
||||
type_specific: { bullet_count: 3 (large) }
|
||||
- ... (product 쪽도 4 개)
|
||||
```
|
||||
|
||||
**Stage A** :
|
||||
- 3-way decision : section 전체가 F29 (process/product 2-column structure) 와 매칭 → *whole-section frame match*
|
||||
- single-region zone, content_type=text+transform_table, ratio=1.0
|
||||
- frame_match_strategy = { kind: "frame_match", frame_id: "F29" }
|
||||
|
||||
**Stage B** :
|
||||
- F29 sub_zones (= Frame Slots) : [process_column (accepts: text_block + transform_table, cardinality 3), product_column (accepts: text_block, cardinality 3)]
|
||||
- process_column → transform_table + 2 text_block (3 개)
|
||||
- product_column → 3 text_block
|
||||
- 모두 inline_full 로 표시
|
||||
|
||||
이건 *현재 mapper (column_with_transform / column_plain) 가 implicit 으로 하는 것* — 본 spec 이 *explicit 하게 표현*.
|
||||
|
||||
### 9.4 03-2 의 cell row 1 (transform_table) 의 10 px overflow 재해석
|
||||
|
||||
placement 가 explicit 하게 되어도 transform_table 이 row 1 cell 에 *콘텐츠 height 131 vs 가용 121* 인 건 변하지 않음.
|
||||
|
||||
**그러나** :
|
||||
- placement 가 *transform_table 의 size_estimate* 를 미리 알면
|
||||
- frame contract 의 Frame Slot 이 *expected_height* 를 declare 하면
|
||||
- planning 단계에서 *"transform_table 이 row 1 Frame Slot 의 expected_height 초과한다"* 를 사전 감지 가능
|
||||
- 그 시점에서 display_strategy = `inline_preview_with_details` 로 자동 전환 (3 transforms 중 2 inline + "1 더 보기")
|
||||
- 또는 placement 가 *frame 부적합* 으로 판정 → frame_reselect 신호
|
||||
|
||||
즉 *본 spec 의 §4 placement algorithm 에 size_estimate 기반 fit pre-check* 가 들어가면 — A1~A4 telemetry 가 *trigger 안 되는 정상 path* 가 됨.
|
||||
|
||||
이게 본 spec 이 가리키는 *진짜 fit policy 의 자리*.
|
||||
|
||||
---
|
||||
|
||||
## 10. 다음 step (사용자 결정)
|
||||
|
||||
본 spec v1 정의 후 구현 axis 후보 :
|
||||
- B1. content_extractor (MDX → content_object 정규화)
|
||||
- B2. internal_region_planner (Layer A — 3-way decision + 비율 + frame_match_strategy)
|
||||
- B3. frame_contracts 에 accepted_content_types + sub_zones (= Frame Slot) 선언 (3 frame)
|
||||
- B4. placement_planner (Layer A → Layer B 통합)
|
||||
- B5. partial / region container marker 추가 + telemetry 연동 (이름 결정 포함)
|
||||
- B6. details/popup runtime
|
||||
|
||||
각 axis 는 *별도 step*. 사용자가 우선순위 결정.
|
||||
|
||||
본 spec 자체는 *implementation 0 단계의 정의*. 다음 step 은 사용자가 잠근 후 진행.
|
||||
@@ -0,0 +1,220 @@
|
||||
# Phase Z-2 — fit_classifier / overflow_router spec
|
||||
|
||||
**Status** : v0 spec (2026-04-29). 정의만. 구현은 별도 step (사용자 승인 후).
|
||||
|
||||
---
|
||||
|
||||
## 0. 목적 / 위치
|
||||
|
||||
자동 파이프라인이 Selenium 으로 *detect 한* overflow / clipping 을 *어떤 pipeline action 으로 routing 할지* 결정하는 layer 의 spec.
|
||||
|
||||
현재 파이프라인은 detection 까지 정상 작동 (Selenium + debug.json 으로 신호 캡처). 그러나 detection 결과를 받은 직후 `sys.exit(1)` 으로 abort — **detection 과 action 사이의 decision layer 가 비어 있음**.
|
||||
|
||||
```
|
||||
parse_mdx → align → composition (v0.2: capacity_fit) → render (Jinja2)
|
||||
↓
|
||||
Selenium visual_runtime_check ← 기존 (detection)
|
||||
↓
|
||||
🆕 fit_classifier ← 신규 (사실 분류)
|
||||
↓
|
||||
🆕 overflow_router ← 신규 (정책 결정)
|
||||
↓
|
||||
action :
|
||||
- zone_ratio_retry ← 신규 미구현
|
||||
- layout_adjust ← 신규 미구현
|
||||
- details_popup_escalation ← 신규 미구현 (CLAUDE.md 의 <details> 원칙 활성)
|
||||
- frame_reselect ← 신규 미구현 (V4 top-k 활용)
|
||||
- adapter_needed ← composition v0.1.1 partial
|
||||
- abort ← 기존 (현재 default)
|
||||
```
|
||||
|
||||
**핵심 원칙** : classifier = *사실 분류* (이 overflow 가 어떤 종류인가), router = *정책 결정* (그 종류면 무엇을 할 것인가). 두 layer 분리 — 같은 분류가 context (retry 횟수 등) 에 따라 다른 action 을 요구할 수 있음.
|
||||
|
||||
---
|
||||
|
||||
## 1. fit_classifier 입력 schema
|
||||
|
||||
### 1.1 detection-side (기존 — 이미 캡처됨)
|
||||
|
||||
| 입력 | 출처 | 비고 |
|
||||
|---|---|---|
|
||||
| `clipped_inner: [{class_name, excess_x, excess_y, scrollWidth/Height, clientWidth/Height}]` | `run_overflow_check` Selenium JS | ✅ |
|
||||
| zone 별 `overflowed`, `excess_y/x` | 같음 | ✅ |
|
||||
| slide / slide_body level overflow | 같음 | ✅ |
|
||||
|
||||
### 1.2 composition-side (기존 — composition v0.2)
|
||||
|
||||
| 입력 | 출처 | 비고 |
|
||||
|---|---|---|
|
||||
| `unit.frame_template_id` / `contract_id` | composition + pipeline | ✅ |
|
||||
| `capacity_fit` (item count, fit_status) | `mapper.compute_capacity_fit` | ✅ (v0.2) |
|
||||
| `content_truncated_count` (zone 별) | pipeline 의 mapper 호출 후 | ✅ (v0.1.1) |
|
||||
| zone size (`height_px`, `min_height_px`, `content_weight`) | `compute_zone_layout` | ✅ |
|
||||
|
||||
### 1.3 신규 입력 (이번 spec 에서 정의)
|
||||
|
||||
| 입력 | 비고 |
|
||||
|---|---|
|
||||
| `semantic_content_type` | className → 의미적 분류. §2 registry 참조 |
|
||||
| `line_equivalent` | excess_y / 해당 element 의 line-height (1 줄 단위로 환산) |
|
||||
| `structural_unit_drop_count` | structural_unit 중 *완전히 또는 부분적으로 잘린* 개수 |
|
||||
| `retry_budget_used` (router 의 상태) | 같은 slide 에 대해 router 가 이미 시도한 retry 횟수 |
|
||||
|
||||
---
|
||||
|
||||
## 2. className → semantic content_type registry
|
||||
|
||||
| className 패턴 | semantic type | 설명 |
|
||||
|---|---|---|
|
||||
| `transform-block`, `transform-block__*`, `transform-row*` | `structural_unit` | paired comparison (AS-IS/TO-BE 한 쌍이 의미 단위). 행 단위 자르면 의미 깨짐 |
|
||||
| `text-line`, `text-line--bullet`, `text-line--indent-*` | `text_flow` | 자유 wrap, 줄 단위 자르기 가능 |
|
||||
| `*table*`, native `<table>` | `tabular` | 행/열 단위 의미 — 행 잘리면 의미 손실 |
|
||||
| `f29b`, `f13b`, `f16b` (frame-family root) | `frame_internal` | frame 자체가 zone 안에 못 들어감 (zone level 문제) |
|
||||
| `*__cell`, `*__pillar`, `*__quadrant` 등 frame 내부 cell | `frame_internal_cell` | frame 내부 cell 단위 (cell 내부 content 가 cell 경계 초과) |
|
||||
| `*__title`, `*__section-title`, `*__banner`, `*__label` 등 | `frame_label` | 제목/라벨 단위 (text 와 비슷하지만 wrap 제약 있음) |
|
||||
| `<img>`, `<svg>`, `*-bg` 등 | `visual_asset` | 시각 자산 (cropping 가능) |
|
||||
| 매칭 안 됨 | `unknown` | classifier 가 보수적으로 처리 (가장 안전한 action 선택) |
|
||||
|
||||
본 registry 는 신규 module (예: `src/phase_z2_classifier.py`) 의 상수 또는 catalog yaml entry 로 구현.
|
||||
|
||||
---
|
||||
|
||||
## 3. fit_classifier 출력 taxonomy
|
||||
|
||||
### 3.1 카테고리 정의 (계산 가능한 룰)
|
||||
|
||||
| 카테고리 | 판정 룰 |
|
||||
|---|---|
|
||||
| `frame_capacity_mismatch` | composition 단계의 `capacity_fit.fit_status` ∈ {`strict_mismatch`, `exceeds_max`, `below_min`, `exceeds_truncate`}. → 이미 v0.2 가 잡고 있는 영역. 본 카테고리는 *post-render 검증 / 누락된 케이스 캐치* 용 |
|
||||
| `structural_major_overflow` | content_type = `structural_unit` 또는 `tabular` AND `structural_unit_drop_count` ≥ 1 (1 개 이상 *완전 단위* 잘림) |
|
||||
| `structural_minor_overflow` | content_type = `structural_unit` 또는 `tabular` AND `structural_unit_drop_count` < 1 (마지막 1 단위가 *부분만* 잘림, 즉 boundary spill) |
|
||||
| `tabular_overflow` | content_type = `tabular` (위와 별도 — 표는 행 1개라도 잘리면 popup) |
|
||||
| `layout_zone_mismatch` | content_type = `frame_internal` (frame root 자체 overflow) — zone 이 frame 을 못 담음 |
|
||||
| `moderate_overflow` | content_type ∈ {`text_flow`, `frame_label`} AND `line_equivalent` ∈ (1.5, 4] |
|
||||
| `minor_overflow` | content_type ∈ {`text_flow`, `frame_label`} AND `line_equivalent` ≤ 1.5 |
|
||||
| `hard_visual_fail` | 위 어디에도 매핑 안 됨 OR retry budget 소진 |
|
||||
|
||||
### 3.2 분류 우선순위 (위에서 아래로)
|
||||
|
||||
1. `frame_capacity_mismatch` (composition 결과 우선)
|
||||
2. `tabular_overflow` (표는 즉시 popup 영역)
|
||||
3. `structural_major_overflow` (1+ 완전 단위 잘림)
|
||||
4. `layout_zone_mismatch` (frame root level)
|
||||
5. `structural_minor_overflow` (boundary spill — 양 작음)
|
||||
6. `moderate_overflow`
|
||||
7. `minor_overflow`
|
||||
8. `hard_visual_fail` (fallback)
|
||||
|
||||
### 3.3 핵심 구분
|
||||
|
||||
- **structural_minor vs structural_major** : 부분만 잘렸나 (`< 1` unit) vs 완전 단위가 잘렸나 (`≥ 1` unit). 부분 잘림은 zone 을 조금 더 주면 fit 가능. 완전 단위 잘림은 의미 손실 — popup escalation.
|
||||
- **structural vs moderate vs minor** : content type 이 *구조적 의미 단위* 인지 여부. 같은 px 양이라도 text_flow 는 minor, structural_unit 은 structural_minor 이상.
|
||||
|
||||
---
|
||||
|
||||
## 4. overflow_router action mapping
|
||||
|
||||
| 카테고리 | action | retry budget | fallback (안 풀리면) |
|
||||
|---|---|---|---|
|
||||
| `minor_overflow` | `zone_ratio_retry` (양보 가능 zone 식별 → compute_zone_layout 재실행) | 1 | escalate → `moderate_overflow` 처리 |
|
||||
| `moderate_overflow` | `layout_adjust` (8-preset 중 다른 preset 검토 + zone ratio 재분배) | 1 | escalate → `structural_major_overflow` 처리 |
|
||||
| `structural_minor_overflow` | `zone_ratio_retry` (구조 자르지 않도록 zone 키움) | 1 | escalate → `structural_major_overflow` 처리 |
|
||||
| `structural_major_overflow` | `details_popup_escalation` (`<details>/<summary>` path) | N/A | popup 미구현 시 → `frame_reselect` → `adapter_needed` |
|
||||
| `tabular_overflow` | `details_popup_escalation` 또는 `frame_reselect` (table-friendly frame 후보) | N/A | 없으면 `adapter_needed` |
|
||||
| `frame_capacity_mismatch` | `frame_reselect` (V4 top-k rank 2+ 평가) | 1 | 없으면 `adapter_needed` |
|
||||
| `layout_zone_mismatch` | `layout_adjust` 또는 `zone_ratio_retry` | 1 | escalate → `frame_reselect` |
|
||||
| `hard_visual_fail` | `abort` (현재 sys.exit(1) 그대로) | — | — |
|
||||
|
||||
---
|
||||
|
||||
## 5. current code gap
|
||||
|
||||
### 5.1 이미 있어서 *재사용* 할 것
|
||||
|
||||
- detection (Selenium `run_overflow_check`) — clipped_inner / excess_y / className 모두 캡처
|
||||
- composition v0.2 의 `compute_capacity_fit` (item count level)
|
||||
- composition v0.1.1 의 `adapter_needed` catch (mapper FitError)
|
||||
- debug.json 의 `slide_status` / `zones` / `candidates_summary` (입력 자료원)
|
||||
- 8-preset layout vocabulary + `compute_zone_layout`
|
||||
|
||||
### 5.2 새로 만들어야 할 것
|
||||
|
||||
| 신규 항목 | 비고 |
|
||||
|---|---|
|
||||
| **content_type registry** (§2) | className → semantic type. classifier 의 핵심 입력 |
|
||||
| **`fit_classifier` 모듈** | §1 입력 → §3 카테고리 |
|
||||
| **`overflow_router` 모듈** | §4 카테고리 → action |
|
||||
| `zone_ratio_retry` action 구현 | compute_zone_layout 의 retry path |
|
||||
| `layout_adjust` action 구현 | preset 동적 변경 |
|
||||
| `details_popup_escalation` 구현 | `<details>/<summary>` runtime + slot_payload 분리 룰 (큰 작업) |
|
||||
| `frame_reselect` 구현 | V4 top-k 사용 (rank-2+ 평가) |
|
||||
|
||||
### 5.3 정의 vs 구현 분리
|
||||
|
||||
본 spec 은 **정의만**. 위 신규 항목 중 어느 axis 부터 구현할지는 *별도 step* 에서 사용자 승인 후. 한꺼번에 다 만들지 X.
|
||||
|
||||
---
|
||||
|
||||
## 6. 본 spec 의 활용 — *visual fix 결정의 검증 자료*
|
||||
|
||||
본 spec 은 *룰북*. 향후 overflow 발생 시 :
|
||||
|
||||
1. classifier 가 *어떤 카테고리* 인지 결정
|
||||
2. router 가 *어떤 action* 인지 결정
|
||||
3. action 이 *현재 구현되어 있나* 확인
|
||||
4. 미구현이면 → "본 spec 의 이 path 미구현이라 처리 불가" 로 명확히 보고
|
||||
|
||||
**중요 활용** : 누군가 (Claude 든 사람이든) "padding 줄여서 끼우자" 같은 fix axis 를 제시하면 — 이 fix 가 본 spec 의 어느 action 에도 매핑되지 않음 → **자동 반려**. 본 spec 을 검증 자료로 가지면 *symptom-silencing fix* 가 들어올 자리가 없어짐.
|
||||
|
||||
---
|
||||
|
||||
## 7. MDX 03 의 10px clipping 을 본 spec 으로 분류 (검증용 sample, fix 대상 X)
|
||||
|
||||
> MDX 03 = sample instance. spec 룰의 *작동 검증* 용 — fix 대상 아님.
|
||||
|
||||
### 측정값
|
||||
- excess_y = 10px (~0.6 줄, transform-row line-height 15.95px 기준)
|
||||
- clipped element className = `transform-block` 의 마지막 row (cell 내부)
|
||||
- semantic content_type = `structural_unit` (transform-row pair)
|
||||
- structural_unit_drop_count = 0.6 (1 개의 마지막 row 가 부분만 잘림 — 완전 단위 1 개가 아님)
|
||||
- composition `capacity_fit.fit_status` = `ok`
|
||||
|
||||
### 분류 적용 (§3.2 우선순위)
|
||||
|
||||
1. `frame_capacity_mismatch`? — capacity_fit ok → ✗
|
||||
2. `tabular_overflow`? — content_type 이 tabular 아님 → ✗
|
||||
3. `structural_major_overflow`? — drop_count `< 1` → ✗
|
||||
4. `layout_zone_mismatch`? — frame_internal 아님 → ✗
|
||||
5. `structural_minor_overflow`? — content_type = structural_unit AND drop_count `< 1` → **✓**
|
||||
|
||||
**카테고리 = `structural_minor_overflow`**
|
||||
|
||||
### Action mapping 적용 (§4)
|
||||
|
||||
→ `zone_ratio_retry` (구조 자르지 않도록 F29 zone 을 더 키움)
|
||||
|
||||
### 현재 구현 상태
|
||||
|
||||
→ `zone_ratio_retry` 는 **MISSING**. 따라서 본 spec 기준으로 MDX 03 은 *classifier 가 정상 작동하면 structural_minor_overflow 로 분류되고 zone_ratio_retry 로 routing 되어야 하는 case* 인데, *그 path 가 현재 미구현*. 따라서 정직한 상태는 `RENDERED_WITH_VISUAL_REGRESSION` 그대로 유지.
|
||||
|
||||
### 반례 검증 (이전 잘못된 fix)
|
||||
|
||||
이전에 시도했던 "transform-block padding 6→4 + transform-row padding 3→2" :
|
||||
- 본 spec 의 어떤 action 에도 매핑되지 X (`density_reduce` 같은 action 자체가 없음)
|
||||
- 따라서 본 spec 기준으로 **자동 반려되는 fix axis** — 들어올 자리 없음
|
||||
|
||||
이게 본 spec 이 *visual fix 결정의 검증 자료* 로 작동한다는 증거.
|
||||
|
||||
---
|
||||
|
||||
## 8. 다음 step (구현 우선순위 — 사용자 결정 영역)
|
||||
|
||||
본 spec 정의 후 구현 axis 후보 (사용자가 우선순위 결정):
|
||||
|
||||
- A. content_type registry + fit_classifier (분류 layer)
|
||||
- B. overflow_router (정책 layer)
|
||||
- C. `zone_ratio_retry` action (가장 자주 트리거될 action)
|
||||
- D. `details_popup_escalation` (큰 작업 — 새 path)
|
||||
- E. `frame_reselect` (V4 top-k 사용 layer)
|
||||
|
||||
각 axis 는 *별도 step* 으로 한 단위씩. 한꺼번에 묶지 X.
|
||||
@@ -0,0 +1,229 @@
|
||||
# Phase Z Frame Style Inventory
|
||||
|
||||
> Phase Z 가 계승할 **색감, 여백, 폰트 위계, 표 / 카드 / 다이어그램 스타일, pill / badge, SVG / CSS 구현 힌트** 를 추출하는 인벤토리.
|
||||
>
|
||||
> ⚠️ 이 문서는 **블록 매핑이 아니다.** Figma frame 은 디자인 레퍼런스 / 구조 패턴 / 슬롯 힌트로 본다 ([`FRAME-INTEGRATION-MAP.md`](FRAME-INTEGRATION-MAP.md) 참조).
|
||||
>
|
||||
> ⚠️ `Phase Z Target` 컬럼은 **결정이 아니라 후보**. 사용자 승인 후 프로모션 게이트에서 확정.
|
||||
|
||||
---
|
||||
|
||||
## 1. Source Policy
|
||||
|
||||
| 구분 | 위치 | 역할 |
|
||||
|---|---|---|
|
||||
| **메인 소스** | `figma_to_html_agent/blocks/{figma_id}/` | 32 frame (1171281171 제외) — Phase Z 스타일 추출의 1 차 출처 |
|
||||
| **토큰 소스** | `templates/styles/tokens/` | `colors.css` / `spacing.css` / `typography.css` — Phase Z 에서 계승 / 조정 |
|
||||
| **legacy 참고** | `templates/blocks/structures/` 등 | Phase Z 의 실제 조립 재료 X. 폐기 / 아카이브 방향. 스타일 / 시각 언어 참고만 |
|
||||
|
||||
추출 우선 순위 :
|
||||
|
||||
1. **변환 완료 frame** (`index.html` + `flat.md` 보유) — 실제 CSS 관찰 기반 스타일 추출
|
||||
2. **미변환 frame** — `Style Elements` / `Extracted Style Hints` / `Phase Z Target` 보류, 변환 완료 후 갱신
|
||||
3. **MCP / Figma 직접 조회 사용 X** — `figma_to_html_agent` 의 본업이므로 inventory 작성 단계에서 침범하지 않음
|
||||
|
||||
---
|
||||
|
||||
## 2. Frame Inventory — 컬럼 정의
|
||||
|
||||
| 컬럼 | 의미 |
|
||||
|---|---|
|
||||
| **Frame** | `FRAME-INTEGRATION-MAP.md` 의 row 번호 (01~32) |
|
||||
| **Figma ID** | `figma_to_html_agent/blocks/` 디렉토리 ID |
|
||||
| **Layout** | `layouts.yaml` controlled vocabulary |
|
||||
| **Style Elements** | frame 안에서 *관찰되는* 스타일 요소 (gradient bar, pill, table header, radial node, soft shadow 등) |
|
||||
| **Extracted Style Hints** | Phase Z 에 *계승할* 구체 힌트 (예 : "table header dark fill + white text", "card gap 12~16px") |
|
||||
| **Phase Z Target** | **후보** 위치 (예 : `tokens/colors.css`, `styles/frame-patterns/table.css`, `svg-helpers/`). 결정 X |
|
||||
| **Asset Notes** | 자산 의존도 / Phase Z 재사용 가능성 (예 : "타이틀 아이콘 PNG 1 개, conclusion box 는 CSS 변환 가능") |
|
||||
| **Notes** | cardinality / optional slot / 변형 축 / 기타 |
|
||||
|
||||
### 작성 룰
|
||||
|
||||
1. **관찰 가능한 값만 작성** — 변환 frame 의 셀은 `flat.md` + `index.html` 에 *실제 있는* 관찰값만 채운다. flat.md 깊이는 frame 마다 다를 수 있고, 빠진 항목 (예 : 변형 축 명시 없음) 은 채우지 않고 비운다. **다른 frame 깊이에 맞추기 위한 추론 / 추측 채움은 하지 않는다.**
|
||||
2. **Phase Z Target 후보는 가능한 한 family 단위로 수렴** — 같은 layout family (표 / 카드 / 다이어그램 / 리스트 / banner) 의 frame 들은 동일한 target 파일 후보를 가리키게 작성. variant 차이는 별도 target 으로 쪼개기보다 `Notes` 에 메타로 남긴다. 표기는 항상 `(후보)` 접미사 — "만들 파일" 이 아니라 "수렴 위치 후보" 로 읽히게.
|
||||
3. **scale / zoom 은 Notes 에 메타** — Figma 원본 폭이 1280 이 아닐 경우 `flat.md` 의 scale / zoom 값을 그대로 두 (raw px 는 원본 기준). Phase Z slide-body (≈1200×590) 에 적용 시 재계산이 필요하다는 사실을 `Notes` 에 한 줄 기록.
|
||||
4. **redescription 금지** — `analysis.md` 의 cardinality / slot / anchor / layout 설명, `FRAME-INTEGRATION-MAP.md` 의 비고 / 검토 상태를 Inventory 에 다시 베끼지 않는다. 같은 정보가 두 문서에 들어가면 drift 위험.
|
||||
|
||||
### 미변환 frame 의 셀 표기
|
||||
|
||||
- `Style Elements` : *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.*
|
||||
- `Extracted Style Hints` / `Asset Notes` / `Notes` : `—`
|
||||
- `Phase Z Target` : `TBD after conversion` (단, `reference_only` frame 은 `N/A — reference_only`)
|
||||
|
||||
---
|
||||
|
||||
## 3. Frame Inventory (32 행)
|
||||
|
||||
> 14 변환 완료 frame 은 `flat.md` + `index.html` 관찰 기반 스타일 추출.
|
||||
> 18 미변환 frame 은 보일러플레이트 일괄.
|
||||
> 작성 룰 #1~4 (위 섹션 2) 준수.
|
||||
|
||||
| Frame | Figma ID | Layout | Style Elements | Extracted Style Hints | Phase Z Target | Asset Notes | Notes |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **01** | `1171281172` | `circular-nodes-6` | • 6 원형 노드 absolute 배치 (각 노드 = 배경 원 + 내부 아이콘 + 라벨)<br>• 모든 노드 / 연결선 / 중앙 / 배경 = 이미지 자산 (9 개) | • **2D 다이어그램 패턴** — 노드 좌표 절대 배치<br>• 자산 의존도 매우 큼 — 본 frame 의 시각 구성은 거의 이미지 | svg helpers (helper area 후보, 자산 의존 제한적) | 9 자산 (배경, 중앙, 노드 ×6, 연결선) — 모두 이미지 유지. Phase Z 재현 시 자산 풀 / placeholder 필요 | 원본 1579×981, scale 0.81064. 변형 축 명시 없음 (flat.md sparse) |
|
||||
| **02** | `1171281173` | `bullet-cards-4-plus-center` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
|
||||
| **03** | `1171281174` | `list-numbered-4` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
|
||||
| **04** | `1171281175` | `quadrilateral-relations` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
|
||||
| **05** | `1171281176` | `side-card-with-list` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
|
||||
| **06** | `1171281177` | `full-page-map-banner` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `N/A — reference_only` | — | — |
|
||||
| **07** | `1171281178` | `2col-paired-list` | • 좌 H/W 7 항목 + 우 S/W 6 항목 + 중앙 시스템 원 + 하단 ground 이미지<br>• 16 자산 (배경 / 패널 / 중앙 원 / 장식 아이콘 / 헤더 바 SVG) | • **2D 복합 시스템 구성도 패턴**<br>• 자산 의존도 매우 큼 — Phase Z 재구성 곤란 | `styles/frame-patterns/system-diagram.css` (후보, 자산 의존 제한적) | 16 자산 모두 이미지 유지. Phase Z 재현 시 자산 풀 필수 | 원본 2446×1943, scale 0.52331. 변형 축 명시 없음 (flat.md sparse) |
|
||||
| **08** | `1171281179` | `3-section-framework` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
|
||||
| **09** | `1171281180` | `list-stacked-vertical` | • 5 pill 행 : `bg: rgba(255,255,255,0.5)`, `border-bottom: 3px solid {color}`, `radius: 30px`, `box-shadow: 2px 4px 5px rgba(0,0,0,0.5)`, `padding: 10px 20px`<br>• pill 색상 5 개 : `#fb5915` / `#e79000` / `#e9a804` / `#919f00` / `#0d6361`<br>• 다이아몬드 stacking : 넓→좁→좁→넓→넓 (좌측 indent 변화)<br>• 타이틀 바 : `#fbd5b9`, `radius: 5px`, shadow<br>• 좌측 아크 장식 SVG (이미지) + 화살표 SVG (`rotate(-90deg)`) | • **pill row + colored bottom border** : 핵심 패턴, 색만 갈아끼우면 N=3~7 동작<br>• **다이아몬드 stacking 패턴** : indent 차이로 시각 리듬<br>• translucent bg + colored border = 부드러운 카테고리 분리 | `styles/frame-patterns/pill-list.css` (후보) + `tokens/colors.css` 5 pill color palette (후보) | 좌측 아크 SVG / 화살표 SVG 2 개 — 이미지 유지. pill 본체는 CSS 변환 완료 | 변형 축 : `items[N=3~7]`, `stacking_pattern` (required), `arc_decoration` / `vertical_label` (optional). 원본 1153×592 (scale 1.11015 — 원본이 1280 보다 작음, zoom up 처리) |
|
||||
| **10** | `1171281181` | `radial-diagram-5` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
|
||||
| **11** | `1171281182` | `cards-3-category` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
|
||||
| **12** | `1171281189` | `cycle-3way-intersection` | • 메인 3 원 (350×350) : outer + inner SVG (Ellipse585~592) + 중앙 라벨 (50px Bold white, `text-shadow: #cc5200`)<br>• 액센트 6 원 (130.9 px) : 한자 라벨 (45px Bold white, 같은 text-shadow)<br>• 사이드 라벨 6 그룹 : 40px Bold + 30px Medium desc<br>• 영역별 heading color : 상단 `#cc5200` / 좌측 `#604f32` / 우측 `#124133`, desc 공통 `#525151`<br>• 장식 RECT : gradient 회전 + `mix-blend-mode: multiply`<br>• 타이틀 : 70px Bold gradient `#000→#883700` | • **3 원 교차 다이어그램** : main 3 + accent 6 으로 영역 표현<br>• **white text + colored text-shadow** : 깊이 부여 효과<br>• **영역별 hue 분리** (`#cc5200` / `#604f32` / `#124133`) — 시각 zone 구분<br>• bg_texture multiply blending — 부드러운 배경 강조<br>• 사이드 라벨 위계 : Bold heading + Medium desc | svg helpers (helper area 후보, 원 / 교차 영역) + `styles/frame-patterns/cycle.css` (후보) + `tokens/colors.css` 영역별 hue palette (후보) | 19 SVG (Ellipse585~603 outer/inner pairs) — 좌표 기반 SVG 재구성 가능. bg_texture PNG 1 개는 이미지 유지 | 변형 축 명시 없음 (메인 3 원 고정 가능성). 원본 2195×1195, scale 0.58312. 수학 : main 350px = 15.94% width / accent 130.9px = 5.96% width |
|
||||
| **13** | `1171281190` | `3-column` | • 3 컬럼 (각 690 원본) : 좌 BAR (152.5 px) gradient + 우 본문<br>• BAR gradient 3 가지 : 기술 `#0D78D0→#023056` / 사람 `#FF9A23→#CC5200` / 자연 `#39BE49→#23742C`<br>• 한자 (技術 / 人材 / 天地) : 50px Bold white on bar<br>• 헤딩 : 45px Bold gradient (top / bottom 별도 gradient 2 종)<br>• 본문 : 35px Medium `#3E3523`<br>• 세로 라벨 (rotate 90°) — 옵셔널 메타<br>• 테두리 : 실선 + 점선 SVG | • **3-pillar 카드 패턴** : 동등 카테고리 3 개 (예 : 기술/사람/자연)<br>• **gradient bar + 한자 + heading + body** 조합<br>• **컬럼별 hue rotation** (blue / orange / green) — 의미 차별화<br>• heading 도 gradient (단색 X) — 일관된 시각 언어 | `styles/frame-patterns/three-pillar.css` (후보) + `tokens/colors.css` 3 column gradient palette (후보) | 아이콘 PNG 1 개 + 테두리 SVG 4 개 (CSS border 변환 가능). 자산 의존 적음 | 변형 축 명시 없음 (3 pillar 고정 가능성 큼). 원본 2123×724, scale 0.60290. 수학 : 열 너비 416px / 바 92px after scale |
|
||||
| **14** | `1171281191` | `persona-3col` | • 3 컬럼 동일 사이즈 (833×1845 원본) + 각 컬럼 텍스처 BG 이미지<br>• 컬러 오버레이 (opacity 0.80) — 컬럼별 다른 색감 hue<br>• 하단 사진 3 개 : `border-radius: 49~50px`, opacity 0.70<br>• 상단 원형 뱃지 (3 개) : outer + inner 이미지 + 한글 라벨<br>• 라벨 색 hue rotation : 발주자 `#285B4A` / 시공자 `#445A2F` / 설계자 `#743002`<br>• 체크박스 불릿 아이콘 (이미지, 32×32)<br>• 불릿 텍스트 : 40px Medium `#000` | • **3 컬럼 persona / actor 카드 패턴** : 역할별 한 컬럼<br>• **컬럼별 hue rotation** : 같은 톤 안에서 색상만 다르게 (역할 구분)<br>• **타이틀을 원형 뱃지로 표현** — 컬럼 상단 절반 걸침 (overhang)<br>• **사진을 borderless 가 아니라 둥근 corner + opacity 처리** (텍스트 가독성 확보)<br>• **체크박스 불릿** — 토큰화 가능 (Phase Z list-marker 후보) | `styles/frame-patterns/persona-cards.css` (후보) + `tokens/colors.css` 컬럼별 actor hue palette (후보) + svg helpers 원형 뱃지 (helper area 후보) | 다수 이미지 자산 : BG texture (×3), overlay (×3), photo (×3), badge outer/inner (×6), 체크박스 (×20 동일). 사진은 컨텍스트 의존 → Phase Z 에서 placeholder / 사용자 제공 자산 필요. 체크박스는 SVG 단순 대체 가능 | 원본 2601×1927 (대형 frame), scale 0.49213 |
|
||||
| **15** | `1171281192` | `policy-4card-plus-list` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
|
||||
| **16** | `1171281193` | `quadrant-4` | • 2×2 사분면 (각 1080×270 헤더/푸터바 + 본문)<br>• 헤더/푸터 bar (4 개) : brown gradient (좌측, `270deg, rgba(165,161,150,0.5)→#39321E`) / green gradient (우측, `270deg, rgba(41,107,85,0.5)→#032118`)<br>• Bar 라벨 : 60px Black white + `text-shadow: 0 0 4px #322c1e`<br>• 사분면 헤드라인 : red `#ff0000` 55px Bold (강조)<br>• 본문 : black 42px Regular, bullet list (`<ul><li>`, 텍스트 마커)<br>• 중앙 원 + 영문 인용 (white 55px Bold)<br>• 배경 텍스처 PNG (`border-radius: 50px`, ×4 동일) | • **2×2 quadrant 패턴** : 4 사분면에 헤드라인 + body 쌍 + 헤더/푸터 bar<br>• **bar 색 양극** (brown / green) — 좌·우 의미 구분<br>• **bar text 강한 시각 강조** : 60px Black + text-shadow<br>• **사분면 헤드라인 red** — 문제 / 이슈 강조 패턴<br>• 중앙 원 + 인용 — 결론 표현 (옵셔널)<br>• bullet — `<ul><li>` 텍스트 마커 (이미지 마커 X) | `styles/frame-patterns/quadrant.css` (후보) + `tokens/colors.css` bar gradient + 강조 red (후보) | 배경 텍스처 PNG (×4 동일) + 중앙 원 PNG + bar SVG (×4, CSS gradient 변환 가능). 배경 / 원만 이미지 유지 | 변형 축 : `quadrants[4]` (required, 고정), `bar_labels[4]` (required), `center_quote` / `center_image` / `bg_texture` (optional). 원본 2226×1766, scale 0.57503 |
|
||||
| **17** | `1171281194` | `paired-rows-2x2` | • 4 행 (각 좌 pill + 본문 + 분할선 + 우 pill + 본문)<br>• 행 배경 : `border: 3px #60A451`, `radius: 30px`, `bg: rgba(250,237,203,0.15)`<br>• 분할선 : `dashed 2px #60A451` (CSS)<br>• pill 이미지 (R16: 두루마리 곡선) — 좌측 `left:-45.3% width:145.3%` / 우측 `left:0 width:151.25%`<br>• pill 라벨 : 40px Bold white<br>• 본문 : 36px Medium `#0c271e`<br>• 행 교대 pill 회전 : 상행 정상 / 하행 `rotate(180deg)`<br>• 타이틀 : 70px Bold gradient `#CC5200→#883700` | • **paired-rows 패턴** : 좌 / 우 라벨 + body 페어, 분할선 중앙<br>• **두루마리 pill (R16)** : 이미지 기반 곡선 형상 — CSS 재구성 곤란<br>• **상/하 pill 회전 교대** = 시각 리듬<br>• translucent bg + colored border = visual containment<br>• dashed 분할선 = soft separation | `styles/frame-patterns/compare-paired.css` (후보, Frame 18 과 같은 family) + `tokens/colors.css` border / bg color (후보) | 타이틀 아이콘 PNG + 두루마리 pill PNG (R16 frame 배치, CSS 재구성 곤란) + 분할선 SVG (CSS 변환 가능) | 변형 축 : `rows[N=3~6]`, `pill_alternation` 상/하 교대 (required), `pill_image` (required), `bg_border_color: #60A451` (required). 원본 1857×1326, scale 0.68927. **compare-paired family — variant : `paired-rows` (pill alternation)** |
|
||||
| **18** | `1171281195` | `compare-rows` | • 타이틀 70px Bold gradient text (`#CC5200 → #883700`) + 아이콘<br>• 서브헤더 pill bar : `linear-gradient(270deg, #285B4A → #4A4026)`, `border-radius: 50px`<br>• 중앙 카테고리 뱃지 12 개 : 같은 gradient (alpha 0.64~0.8), `border-radius: 10px`<br>• 좌·우 텍스트 색 양극 : `#5C3714` (BIM 측, 갈색계) ↔ `#285B4A` (DX 측, 청록계), 40px Bold<br>• 결론 박스 : `#FAEDCB` + `mix-blend-mode: multiply`<br>• 결론 강조 텍스트 : `#AE3607` 55px Bold | • **다행 비교표 패턴** : 좌 (BIM/AS-IS) ↔ 중앙 카테고리 라벨 ↔ 우 (DX/TO-BE) 의 3 컬럼 페어드<br>• **양극 색 표현** : 좌·우를 명도·색상이 다른 두 hue 로 분리 (대비 의도)<br>• **gradient 재사용** : title gradient + bar/badge gradient 가 동일 팔레트 (저채도 그린 + 다크 brown) 변주<br>• **결론 처리** : multiply blending + accent color 로 강조 | `styles/frame-patterns/compare-paired.css` (후보, Frame 17 과 같은 family) + `tokens/colors.css` 의 `--g-title` / `--c-as-is` / `--c-to-be` (후보) + `styles/blocks/conclusion.css` multiply blend (후보) | 타이틀 아이콘 PNG 1 개 (이미지 유지) + 화살표 SVG 1 개 (`rotate(180deg)`, 이미지 유지) + 결론 박스 SVG → CSS 변환 완료 (자산 불필요) | 변형 축 : `rows[N=8~15]` (required), `title` (required), `conclusion` / `arrow_decoration` (optional). 원본 1868×1908, scale 0.68524. **compare-paired family — variant : `vs-center-badge` (좌·우 텍스트 + 중앙 카테고리 라벨 컬럼)** |
|
||||
| **19** | `1171281197` | `cards-3-compare` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
|
||||
| **20** | `1171281198` | `cards-3-header` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
|
||||
| **21** | `1171281201` | `split-panel-diagram` | • 좌 다이어그램 이미지 + 우 4 번호 항목 + 하단 결론 바<br>• 14 자산 (다이어그램 요소 / 번호 뱃지 / 행 바 / 화살표 / 결론 바) — 모두 이미지 유지 | • **split-panel 패턴** (이미지-기반 다이어그램 + 번호 리스트)<br>• 자산 의존 큼 — Phase Z 재구성 시 다이어그램 이미지 제공 필요 | `styles/frame-patterns/split-panel.css` (후보, Frame 22 와 같은 family) | 14 자산 모두 이미지 유지. 다이어그램 이미지 = 핵심 자산 | 변형 축 : `right_items[N=3~6]`, `conclusion_text` (optional). 원본 1889×824, scale 0.67761. flat.md sparse |
|
||||
| **22** | `1171281202` | `split-panel-numbered` | • 좌 패널 : 배경 IMG + 카테고리 텍스트 (40px Bold white, text-shadow) + detail (35px Medium black)<br>• 우 패널 : 5 행 (번호 뱃지 IMG + 행 바 IMG + 텍스트 45px Medium `#11231d` + 화살표 IMG `rotate(180deg)`)<br>• 중앙 연결 : 세로 괄호 IMG + 커넥터 IMG<br>• 행 바 (×5 동일 이미지)<br>• 번호 뱃지 (5 개 별개 이미지)<br>• 타이틀 : 70px/50px gradient | • **split-panel + numbered list 패턴** : 좌 카테고리 패널 + 우 번호 항목 페어<br>• 카테고리 텍스트 = text-shadow + white (배경 위 가독성)<br>• 번호 뱃지 + 행 바 + 화살표 = 단위 리스트 행 컴포넌트<br>• 중앙 괄호 / 커넥터 = 좌 ↔ 우 연결 시각화 | `styles/frame-patterns/split-panel.css` (후보, Frame 21 과 같은 family) | 15 자산 (좌측 BG, 타이틀 장식, 구분선 ×3, 세로 괄호, 커넥터, 행 바 ×5 동일, 뱃지 ×5 별개, 화살표 ×5 동일, 타이틀 아이콘) — 모두 이미지 유지 | 변형 축 : `left_categories[N=2~5]`, `right_items[N=3~8]` (required), `bg_image` (required), `bracket_image` (optional). 원본 1863×834, scale 0.68707 |
|
||||
| **23** | `1171281203` | `table-2col` | • 열 헤더 bar (3 개) : `#589e8d` (구분/좌) / `#ef7a26` (우), 40px Bold white<br>• 행 배경 교대 : white / `rgba(253,198,158,0.16)`<br>• 강조 키워드 : `#a14101` Bold inline<br>• 본문 : black 40px Medium<br>• 그리드 라인 : 모두 CSS border<br>• 배경 텍스처 PNG (상단 / 하단 분할) | • **compare-table 패턴** (구분 컬럼 + N 열 비교)<br>• **헤더 bar 색상 양극** (`#589e8d` 청록 / `#ef7a26` 오렌지) — 의미 구분<br>• **행 alternating bg** = readability<br>• **강조 키워드 inline color** (`#a14101`) — 표 셀 안 강조 | `styles/frame-patterns/compare-table.css` (후보, Frame 24 와 같은 family) + `tokens/colors.css` 헤더 bar palette (후보) | 배경 PNG 2 개 + 아이콘 PNG + line SVG ×5 (모두 CSS border 변환). 배경 PNG 만 이미지 유지 | 변형 축 : `columns[2]`, `rows[N=3~7]` (required), `header_colors[2]` (required), `bg_images[2]` (optional). 원본 1924×2014, scale 0.66527. **table family — Frame 24 / 30 / 31 과 column 수 / 행 수만 다름, variant 통합 후보** |
|
||||
| **24** | `1171281204` | `table-3col` | • 열 헤더 bar (4 개) : `#589e8d` (구분/상용) / `rgba(62,53,35,0.9)` (3rd) / `#ef7a26` (전용), 40px Bold white<br>• 행 배경 교대 : white / `rgba(253,198,158,0.16)`<br>• 강조 키워드 : `#a14101` Bold inline<br>• 본문 : black 35px Medium<br>• 그리드 라인 : 모두 CSS border<br>• 행 라벨 (좌측 열) : 35px Bold | • **compare-table 패턴** (Frame 23 의 3-column variant — 같은 family)<br>• **헤더 색상 3-way** (`#589e8d` / 다크 brown / `#ef7a26`) — Frame 23 의 2-way 확장<br>• **행 라벨 좌측 열** = 행 그룹 식별자 (예 : 개념 / 개발주체 / 성과품 / 사용) | `styles/frame-patterns/compare-table.css` (후보, Frame 23 과 같은 family — variant: `columns[N=2~4]`) + `tokens/colors.css` 헤더 bar palette 확장 (후보) | 아이콘 PNG + line SVG ×8 (전부 CSS border 변환). 자산 의존 거의 없음 | 변형 축 : `columns[N=2~4]`, `rows[N=3~6]` (required), `header_colors[N]` (required), `highlight_color` (optional, default `#a14101`). 원본 1869×1926, scale 0.68511. **table family — Frame 23 / 30 / 31 과 variant 통합 후보** |
|
||||
| **25** | `1171281205` | `left-categories-right-logos` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
|
||||
| **26** | `1171281206` | `cards-4-grid` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
|
||||
| **27** | `1171281208` | `central-split-synthesis` | • 좌 (생산성 향상) + 우 (디지털 전환) + 중앙 원 (건설산업의 고부가가치화)<br>• 상단 헤더 bar / 하단 결론 bar (SVG `rotate(180deg)`)<br>• 2D 배치 (중앙 원 좌 / 우 영역 겹침) → absolute + zoom | • **split-center 패턴** : 좌 / 우 / 중앙 3-area 합성<br>• 중앙 원 = 좌·우 영역에 걸침 (overhang) — 결론 표현 방식 | `styles/frame-patterns/split-center.css` (후보) | 변환 완료 (preview.png 존재). 자산 상세 미기록 (flat.md sparse) | 원본 1697×914, scale 0.75427. flat.md sparse — 추가 관찰 필요 |
|
||||
| **28** | `1171281209` | `title-plus-3-emphasis` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
|
||||
| **29** | `1171281210` | `banner-top-2col-bottom` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
|
||||
| **30** | `1171281211` | `table-3col` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
|
||||
| **31** | `1171281212` | `table-3col` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
|
||||
| **32** | `1171281213` | `central-5-goals` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
|
||||
|
||||
---
|
||||
|
||||
## 4. Token Inventory
|
||||
|
||||
> 14 변환 frame 에서 관찰된 값과 기존 `templates/styles/tokens/` 의 매칭 검증.
|
||||
>
|
||||
> ⚠️ **본 inventory 는 신규 발굴표가 아니라 *기존 token 검증 + gap 발견 + 보류* 정리표**.
|
||||
> ⚠️ 추출 / 검증만. 실제 token 파일 생성 / 변경 / 폐기는 별도 승인 단계.
|
||||
|
||||
### 작성 룰 (최종 6 개)
|
||||
|
||||
1. **2+ frame 에서 반복 관찰된 값만 행 승격**.
|
||||
2. **1 frame 에서만 관찰된 값은 Token Inventory 에 올리지 않음** — 해당 Frame Inventory 의 `Notes` 에 "관찰 보류 (single-frame)" 로만 남김.
|
||||
3. **패턴 / 기법은 token 이 아니라 family CSS / variant 영역** — `mix-blend-mode`, R16 pill 곡선, hue rotation, badge overhang, pill alternation 등.
|
||||
4. **타이포 / 스페이싱은 값 직접 매칭이 아니라 위계 매핑** — figma raw px (frame 1280 폭 기준) ↔ slide-body token (slide-body 스케일) 은 스케일이 다르므로 위계만.
|
||||
5. **gradient 는 from / to pair 단위 한 행**.
|
||||
6. **`covered` 는 hex 정확 일치일 때만** — 부분 일치 / 변형은 `gap_candidate` 또는 Notes 에 별도 표기.
|
||||
|
||||
### 명명 컨벤션 (가벼운 가이드)
|
||||
|
||||
신규 token 후보에만 적용. 기존 token (`--color-*`, `--font-*`, `--space-*`, `--card-*`) 은 그대로 사용.
|
||||
|
||||
- `--c-*` color
|
||||
- `--g-*` gradient
|
||||
- `--fs-*` font-size
|
||||
- `--sp-*` spacing
|
||||
- `--r-*` radius
|
||||
- `--sh-*` shadow
|
||||
|
||||
### 컬럼 정의
|
||||
|
||||
| 컬럼 | 의미 |
|
||||
|---|---|
|
||||
| **Token Category** | color / gradient / typography / spacing / radius / shadow |
|
||||
| **Existing Token** | `templates/styles/tokens/{file}` 의 token 명. 없으면 `—` |
|
||||
| **Covered Frame Observations** | frame 번호 + 짧은 컨텍스트 라벨만 (값 X). status 에 따라 의미 다름 (아래 참조) |
|
||||
| **Gap / Candidate** | 신규 token 후보 (이름 + 값 + `(후보)`). 기존 token 검증인 행은 `—` |
|
||||
| **Status** | `covered` / `gap_candidate` / `hierarchy_mapping_only` / `hold_recheck_after_conversion` |
|
||||
| **Notes** | family CSS cross-reference + 변형 메타 + scale 경계 등 |
|
||||
|
||||
### `Covered Frame Observations` 셀의 Status 별 의미
|
||||
|
||||
| Status | `Covered Frame Observations` 의 의미 |
|
||||
|---|---|
|
||||
| `covered` | 이 token 이 *cover 한* frame 들 (hex 정확 일치) |
|
||||
| `gap_candidate` | 이 후보가 *target 으로 하는* frame 들 (2+ frame 에서 동일 값 관찰) |
|
||||
| `hierarchy_mapping_only` | 이 위계 매핑이 *적용 가능한* frame 들 (값 직접 매칭 X) |
|
||||
| `hold_recheck_after_conversion` | 현재 cover 한 frame 없음 (`—`). 14 converted 기준 미관찰 — 전체 변환 후 재검증 |
|
||||
|
||||
### Token Inventory — 18 행
|
||||
|
||||
| Token Category | Existing Token | Covered Frame Observations | Gap / Candidate | Status | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| gradient | `--color-block-title-from` / `--color-block-title-to` (`#CC5200` / `#883700`) | F17, F18 (frame inner title) | — | `covered` | compare-paired family 의 title gradient slot. F12, F13 의 title 은 `from` 이 `#000` 으로 변형 — 별도 행 (`gap_candidate`) 처리 |
|
||||
| gradient | `--color-col-1-from` / `--color-col-1-to` (`#0D78D0` / `#023056`) | F13 (기술 bar — blue tone) | — | `covered` | three-pillar.css 의 column 1. 1 frame 매칭이지만 token 자체가 frame 값을 정확 흡수 |
|
||||
| gradient | `--color-col-2-from` / `--color-col-2-to` (`#FF9A23` / `#CC5200`) | F13 (사람 bar — orange tone) | — | `covered` | three-pillar.css 의 column 2. `#CC5200` 는 title gradient `from` 과 같은 hex 이지만 의미 다름 (column-2 끝값) |
|
||||
| gradient | `--color-col-3-from` / `--color-col-3-to` (`#39BE49` / `#23742C`) | F13 (자연 bar — green tone) | — | `covered` | three-pillar.css 의 column 3 |
|
||||
| color | `--color-compare-left` (`#5c3714`) | F18 (BIM / AS-IS 측 텍스트) | — | `covered` | compare-paired.css 의 좌측 / AS-IS 색 |
|
||||
| color | `--color-compare-right` (`#285b4a`) | F14 (발주자 라벨), F18 (DX / TO-BE 측 텍스트) | — | `covered` | compare-paired.css 의 우측 / TO-BE + persona-cards.css 의 actor 색. F14, F18 모두 hex 정확 일치 (대소문자 제외) — 의미 다르지만 token 재사용 가능 |
|
||||
| color | `--color-compare-badge` (`#ae3607`) | F18 (결론 강조 / VS 뱃지) | — | `covered` | compare-paired.css 의 결론 / VS 뱃지 강조색 |
|
||||
| gradient | — | F12, F13 (title — `#000` → `#883700`) | `--g-title-dark: linear-gradient(#000, #883700)` (후보) | `gap_candidate` | title gradient 변종 (`from` 이 `#000`). `--color-block-title-to` 와 끝값 공유 — 신규 token 으로 묶을지 / variant 처리할지 검토. 영향 family : 미정 (cycle.css / three-pillar.css) |
|
||||
| color | — | F23, F24 (table 헤더 좌 / 구분 — 청록 톤) | `--c-table-header-cyan: #589e8d` (후보) | `gap_candidate` | compare-table.css 의 좌측 / 구분 헤더. 기존 `--color-table-header-bg: #64748b` (회색) 과 다른 톤 — 회색 헤더 token 은 `hold` 행 참조 |
|
||||
| color | — | F23, F24 (table 헤더 우 / 전용 — 오렌지 톤) | `--c-table-header-orange: #ef7a26` (후보) | `gap_candidate` | compare-table.css 의 우측 헤더 |
|
||||
| color | — | F23, F24 (table 행 강조 키워드) | `--c-table-highlight: #a14101` (후보) | `gap_candidate` | compare-table.css 의 inline 강조색 |
|
||||
| color | — | F23, F24 (table 행 교대 배경) | `--c-table-row-alt: rgba(253,198,158,0.16)` (후보) | `gap_candidate` | compare-table.css 의 alternating row bg. white / `--c-table-row-alt` 교대 |
|
||||
| typography | `--font-slide-title` (22px) / `--font-zone-title` (13px) / `--font-sub-title` (12px) | F12, F13, F17, F18 (frame inner title — raw 70px Bold) | — | `hierarchy_mapping_only` | frame raw 70px 는 figma 1280 폭 기준 — slide-body 스케일과 다름. **위계 매핑만**. frame inner title 이 slide-body 안에서 어느 위계 (`--font-zone-title` / `--font-sub-title`) 로 매핑될지는 catalog 설계 단계 결정 |
|
||||
| typography | `--font-body` (11px) | F12, F13, F16, F17, F18, F22, F23, F24 (본문 — raw 35~42px Medium) | — | `hierarchy_mapping_only` | frame raw 35~42px 는 figma 1280 폭 기준. slide-body 안에서는 `--font-body` 위계 적용 |
|
||||
| spacing / radius | `--space-md` / `--space-lg` / `--card-radius` 등 | F09 (pill `radius: 30px`), F16 (배경 `radius: 50px`), F17 (행 `radius: 30px`), F18 (badge `radius: 10px`, pill `radius: 50px`), F23/F24 (셀 padding) | — | `hierarchy_mapping_only` | frame raw radius / gap / padding 은 figma 폭 기준. slide-body 안에서는 위계 매핑 + 재산정 필요 |
|
||||
| color | `--color-dark-card-1` (`#1a365d`) / `-2` (`#1e3a2f`) / `-3` (`#3b1f2b`) / `-title` (`#fbbf24`) / `-body` (`#e2e8f0`) | — | — | `hold_recheck_after_conversion` | 다크 카드 시각 시스템 5 token. 14 converted 기준 미관찰. 전체 32 frame 변환 후 재검증 (특히 미변환 zone_extract 18 개) |
|
||||
| color | `--color-pill-bg` (`#1e293b`) / `--color-pill-text` (`#ffffff`) | — | — | `hold_recheck_after_conversion` | 다크 pill 스타일. F09 pill (translucent + colored border) / F18 badge (gradient) 와 다른 톤. 14 converted 기준 미관찰 — 전체 변환 후 재검증 |
|
||||
| color | `--color-table-header-bg` (`#64748b`) / `--color-table-header-text` (`#ffffff`) | — | — | `hold_recheck_after_conversion` | 회색계 표 헤더. F23 / F24 의 colored 헤더 (`#589e8d` / `#ef7a26`) 와 다른 톤. 14 converted 기준 미관찰. 향후 회색 헤더 frame 등장 시 재검증 |
|
||||
|
||||
---
|
||||
|
||||
## 5. Legacy Reference
|
||||
|
||||
- legacy structures 6 개는 runtime 재사용 후보 X
|
||||
- frame 변환본 (`figma_to_html_agent/blocks/`) 이 우선 source
|
||||
- disposition 분류 목적은 archive / delete 판단
|
||||
- Phase Z catalog / runtime 설계 근거로 **직접 사용 X**
|
||||
|
||||
### 발견 — 6 파일 모두 frame 변환본의 *slide-body 스케일 재구현*
|
||||
|
||||
각 legacy file 의 헤더 주석에 `Source: figma_to_html_agent/blocks/{figma_id}` 명시 (확인). figma 변환 (raw 1280 폭, 40~70px 폰트) → slide-body 스케일 (`var(--font-sub-title)` 12px, `var(--space-sm)` 8px 등 token 적용) 재구현 시도. 즉 *figma 변환과 별개의 legacy* 가 아니라, *figma 변환에서 파생된 slide-body 스케일 시도*.
|
||||
|
||||
→ Phase Z runtime 은 frame catalog + family CSS 로 rebuild 예정. legacy structures 는 *변환 검증 증거 / 토큰 매핑 참고* 외 직접 사용 X.
|
||||
|
||||
### `Phase Z Disposition` 값
|
||||
|
||||
- `archive` — 보존 (스타일 / 토큰 매핑 증거 가치)
|
||||
- `delete_after_extract` — Style Note 추출 후 삭제
|
||||
- `hold_until_catalog_ready` — Phase Z catalog 안정화 전까지 유지
|
||||
|
||||
### Legacy Reference — 6 행
|
||||
|
||||
| Legacy File | Current Role | Phase Z Disposition | Style Note | Notes |
|
||||
|---|---|---|---|---|
|
||||
| `compare-table-2col.html` | F23 (`1171281203`) 의 slide-body 스케일 재구현. 표 헤더 colored / 행 교대 bg / 강조 inline color | `delete_after_extract` (후보) | inline hex (`#589e8d` / `#ef7a26` / `#a14101` / `rgba(253,198,158,0.16)`) 가 Token Inventory 의 `gap_candidate` 4 행과 정확 일치 | `compare-table.css` family 의 first reference 가치 |
|
||||
| `compare-table-3col.html` | F24 (`1171281204`) 의 3-column variant slide-body 재구현 | `delete_after_extract` (후보) | F23 과 같은 hex + 추가 `rgba(62,53,35,0.9)` (column 2 어두운 brown 헤더). 단일 frame 관찰값 — Token Inventory 비승격 (룰 #2) | `compare-table.css` family variant — F23 과 통합 후보 |
|
||||
| `compare-vs-rows.html` | F18 (`1171281195`) 의 slide-body 재구현 | `delete_after_extract` (후보) | `var(--color-compare-left)` / `var(--color-compare-right)` 기존 token 활용 — covered token 검증 증거 | `compare-paired.css` family |
|
||||
| `issues-paired-rows.html` | F17 (`1171281194`) 의 slide-body 재구현 | `delete_after_extract` (후보) | `--color-row-border: #60A451` inline 정의 — F17 단일 frame 관찰값, Token Inventory 비승격 (룰 #2). family CSS variant 처리 검토 | `compare-paired.css` family |
|
||||
| `prerequisites-3col.html` | F13 (`1171281190`) 의 slide-body 재구현 | `delete_after_extract` (후보) | `--color-col-N-{from,to}` 기존 token 활용 가능 — covered token 검증 증거 | `three-pillar.css` family |
|
||||
| `stacked-arrow-list.html` | F09 (`1171281180`) 의 slide-body 재구현 | `delete_after_extract` (후보) | 타이틀 바 `#fbd5b9` / 텍스트 `#144838` inline — F09 단일 frame 관찰값, Token Inventory 비승격 (룰 #2) | `pill-list.css` family |
|
||||
|
||||
---
|
||||
|
||||
## 6. 진행 단계
|
||||
|
||||
| 단계 | 상태 |
|
||||
|---|---|
|
||||
| 33 frame ↔ Integration Map 32 행 대조 | ✅ 완료 (`FRAME-INTEGRATION-MAP.md` row 21~28 ID 정정 반영) |
|
||||
| Inventory 골격 + 샘플 5 행 (이 문서) | ✅ 본 단계 |
|
||||
| 사용자 검토 | ⬜ |
|
||||
| Frame Inventory 27 행 일괄 확장 (32 frame 완성) | ⬜ |
|
||||
| Token Inventory 본격 작성 | ⬜ |
|
||||
| Legacy Reference 본격 작성 | ⬜ |
|
||||
| Phase Z catalog / runtime template 설계 | ⬜ (별도 단계) |
|
||||
| 사용자 승인 → `templates/blocks/` 신규 구조 교체 (프로모션 게이트) | ⬜ (별도 단계) |
|
||||
|
||||
### 샘플 5 검증 포인트
|
||||
|
||||
| 검증 항목 | 결과 |
|
||||
|---|---|
|
||||
| 변환 / 미변환 frame 이 같은 양식에 들어가는가 | ✅ — 미변환은 `Style Elements` 셀 1 개에 통일 보일러플레이트, 나머지 4 셀은 `—` (`Phase Z Target` 만 `TBD after conversion` 또는 `N/A — reference_only`) |
|
||||
| `Style Elements` 와 `Extracted Style Hints` 의 구분이 명확한가 | ✅ (Frame 18, 14) — 관찰 사실 (CSS 값 / 픽셀) vs 계승 의도 (패턴 / 의미) |
|
||||
| `Phase Z Target` 후보 표현이 통일되는가 | ✅ — 변환 frame 은 "(후보)" 명시, 미변환은 `TBD after conversion`, reference_only 는 `N/A — reference_only` |
|
||||
| 미변환 frame 이 `analysis.md` 내용을 redescribe 하지 않는가 | ✅ — 보일러플레이트 1 행 외 어떤 redescription 도 없음 |
|
||||
| 변환 frame 의 `Notes` 가 스타일 계승에 의미 있는 메타만 담는가 | ⚠️ 샘플은 적정. 32 행 확장 시 단순 출처 (Scale 값, "대표 frame" 등) 는 추가로 정리 필요 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 부록 — 제외 / 특수 항목
|
||||
|
||||
`figma_to_html_agent/blocks/` 의 33 개 디렉토리 중 `1171281171` 은 본 인벤토리 메인 32 frame 에서 제외한다 (`texts.md` 만 존재, `index.html` / `analysis.md` 없음, 정체 미확인). 상세는 [`FRAME-INTEGRATION-MAP.md` 부록](FRAME-INTEGRATION-MAP.md#부록--제외--특수-항목) 참조.
|
||||
@@ -0,0 +1,206 @@
|
||||
# Phase Z 매칭 아키텍처 — 원칙 anchor
|
||||
|
||||
> 22-step pipeline 의 *매칭 layer* (Step 5/6/7 + B-axis Step 9/10/11) 를 다루는 *원칙 anchor* doc.
|
||||
>
|
||||
> 본 문서는 *새 디자인 생성* 이 아니라 *기존 코드 / 과거 docs / 관찰 사례 / session 인사이트* 를 근거로 한 *forward improvement consolidation*.
|
||||
>
|
||||
> 관련 anchors :
|
||||
> - `docs/architecture/PHASE-Z-PIPELINE-OVERVIEW.md` (D1, 22-step pipeline)
|
||||
> - `docs/architecture/PHASE-Z-CONTENT-OBJECT-SUBZONE-SPEC.md` (D2, Layer A/B SPEC)
|
||||
> - `docs/architecture/PHASE-Z-CONTENT-OBJECT-SUBZONE-PLAN.md` (D3, schema completion plan)
|
||||
> - `docs/architecture/PHASE-Z-PIPELINE-STATUS-BOARD.md` (D4, 현재 status)
|
||||
|
||||
|
||||
## §0. 본 문서의 작성 방법론 (P0)
|
||||
|
||||
**evidence-based forward improvement** — 매번 *4 source* 를 거쳐 도출 :
|
||||
|
||||
1. 과거 docs (D1 / D2 / D3 / D4) — *원래 의도*
|
||||
2. 현재 코드 (`src/phase_z2_*.py` / `templates/phase_z2/catalog/frame_contracts.yaml`) — *실제 동작*
|
||||
3. 관찰된 혼선 / 실패 사례 — *경험적 evidence*
|
||||
4. session 인사이트 — *최근 정리된 mental model*
|
||||
|
||||
이걸 *관찰 → 혼선 → 도출 원칙 → 개선 방향* form 으로 표면화. *과거 회귀* 가 아니라 *과거를 근거로 한 개선*. *감으로 결정* / *가장 쉬운 선택* 금지.
|
||||
|
||||
|
||||
## §1. 현재 코드 상태 (관찰)
|
||||
|
||||
### 1.1 매칭 단위 — `CompositionUnit`
|
||||
|
||||
매칭의 *atomic unit* 은 section 이 아니라 `CompositionUnit`. `src/phase_z2_composition.py` 의 `collect_candidates` 가 *3 종 candidate type* 을 동시 생성 :
|
||||
|
||||
- `single` — `src/phase_z2_composition.py:230-243` (1 section 단독)
|
||||
- `parent_merged` — `src/phase_z2_composition.py:260-273` (parent 자체가 V4 매칭, children 묶음)
|
||||
- `parent_merged_inferred` — `src/phase_z2_composition.py:352-368` (children rep_match 기반 inferred merge)
|
||||
|
||||
각 candidate 는 `source_section_ids[]` + `merge_type` (line 232 / 262 / 354) 로 *어떤 section 들을 어떻게 묶었는지* 표현.
|
||||
|
||||
### 1.2 frame 선택 — render path authority = V4 rank-1
|
||||
|
||||
현재 render path 의 `frame_template_id` 는 composition planner 의 *V4 rank-1* 기반 :
|
||||
- `lookup_v4_match()` in `src/phase_z2_pipeline.py` (D1:212 명시) — rank-1 만 반환
|
||||
- composition planner 가 V4 결과로 candidate 생성
|
||||
|
||||
B4 (`src/phase_z2_placement_planner.py:90-107` 의 `_select_frame`) 도 frame selection 을 *수행* 하지만 *trace-only* — render path authority 는 V4 rank-1.
|
||||
|
||||
V4 = `top-k` 선언만 됐고 *rank-1 사용* 만 활성. D4 의 ⚠ partial.
|
||||
|
||||
### 1.3 binding — `phase_z2_mapper.map_with_contract` (Layer 1)
|
||||
|
||||
`src/phase_z2_mapper.py:584-609` 의 `map_with_contract(section, contract)` 는 contract 에서 *오직* 두 field 만 read :
|
||||
- `contract["source_shape"]` (split 규칙)
|
||||
- `contract["payload"]["builder"]` (named PAYLOAD_BUILDERS dispatch)
|
||||
|
||||
`accepted_content_types` / `sub_zones` 등 Layer 2 field 는 *접근 X*. 즉 mapper 는 *Layer 1 binder* 로 격리.
|
||||
|
||||
### 1.4 placement — `phase_z2_placement_planner` (Layer 2 reader, trace-only)
|
||||
|
||||
`src/phase_z2_placement_planner.py` :
|
||||
- `_select_frame` (90-107) — `accepted_content_types ⊇ content_type_set` cover + declaration order first
|
||||
- `_assign_region_to_sub_zone` (113-151) — `sub_zones` 의 narrowest-accepts first
|
||||
- `plan_placement` (157+) — Stage A (B2 internal regions) + Stage B (region ↔ sub_zone) 통합
|
||||
|
||||
B4 는 *Layer 2 reader* 이지만 `src/phase_z2_pipeline.py:1060-1145` 에서 *trace-only* 로만 호출. render path (slot_payload 생성) 미연결. D4:78-83 의 *render path placement_trace 미사용* 과 일치.
|
||||
|
||||
### 1.5 capacity / fit — classifier (post-render telemetry)
|
||||
|
||||
`src/phase_z2_classifier.py` 의 `classify_visual_runtime_check` — *render 후* visual measurement 기반 fit 판정. *Architectural reframe* (D2:16-33 의 PLANNING / RENDER / POST-RENDER 3-layer) : A1~A4 = *post-render telemetry layer*. *덜 중요* 가 아니라 *위치가 다름*. 매칭 결정 자체에는 직접 입력 X (현재).
|
||||
|
||||
|
||||
## §2. 확인된 혼선 (resolution)
|
||||
|
||||
### 2.1 Sub-zone 용어 4-방향 충돌 → 4-tier 분리 (P5'')
|
||||
|
||||
session 안 누적 혼선 :
|
||||
- D1 의 "Zone (top / bottom_l / bottom_r 등)"
|
||||
- composition.py 의 child unit 분리 (single / parent_merged 등)
|
||||
- yaml 의 `sub_zones` field
|
||||
- 사용자 mental model 의 "### split"
|
||||
|
||||
→ *4 tier 별 명칭 lock* :
|
||||
|
||||
| Tier | 명칭 | 위치 | 정체 |
|
||||
|---|---|---|---|
|
||||
| 1 | **Layout** | slide-level | slide-body 안 zone topology (8-preset vocabulary) |
|
||||
| 2 | **Zone** | layout 안 | 콘텐츠 구역 (top / bottom_l / bottom_r 등). 1 frame 매칭 단위 |
|
||||
| 3 | **Split Zone (Child Unit)** | zone 안 | composition planner 가 만든 child unit (parent_merged 의 분리 단위) |
|
||||
| 4 | **Frame Slot** | frame 안 | yaml 의 `sub_zones` = Layer B placement target. region 매칭 대상 |
|
||||
|
||||
이후 본 문서 / 후속 doc 은 *위 명칭 어휘* 만 사용. yaml field `sub_zones` 는 *기존 이름 유지하되 의미 = Frame Slot* 으로 lock (mechanical rename 은 D3 plan).
|
||||
|
||||
### 2.2 composition tie-break ≠ Frame Slot tie-break
|
||||
|
||||
scope-lock 단계에서 *D2:524-533 의 tie-break* 을 *composition tie-break evidence* 로 잘못 분류. 실제로는 :
|
||||
- D2:524-533 = *Frame Slot 단위* tie-break (Stage B 안, frame 이미 선택된 후)
|
||||
- composition tie-break = `composition.py:433-441` (sort + greedy + coverage)
|
||||
|
||||
두 layer 가 다름. P7 evidence 는 *composition layer* 만, *Frame Slot layer* 는 별개 location.
|
||||
|
||||
### 2.3 "Layer 2 미사용" framing 오류
|
||||
|
||||
scope-lock 초안의 *"mapper 가 Layer 2 를 consume 안 함"* framing 은 암묵적으로 *mapper 가 Layer 2 reader 가 돼야 한다* 의 fix direction 함의. 실제 architectural intent :
|
||||
- mapper = Layer 1 binder (의도된 격리)
|
||||
- B4 = Layer 2 reader (의도된 분리)
|
||||
|
||||
*문제* 는 *Layer 2 unconsumed* 가 아니라 *B4 의 Layer 2 read 가 trace-only 라 render path 까지 안 닿음*. *bridge* 가 답이지 *mapper 확장* 이 아님.
|
||||
|
||||
### 2.4 Step 4/5/9 분리 미완
|
||||
|
||||
현재 구현은 D1 이 의도한 *Step 4 → Step 5 → Step 9* 분리가 완성되지 않음 :
|
||||
- Step 4 (composition planning input — section_layout_signature / content_object 구조) = *partial/dormant* (D4:73 의 ⚠ partial transition)
|
||||
- Step 5 evidence 와 Step 9 final selection = *conflate* (D1:255)
|
||||
|
||||
D1:185 가 명시적으로 *Step 4 가 frame matching 보다 먼저 와야 함* 을 지적.
|
||||
|
||||
|
||||
## §3. 도출된 원칙
|
||||
|
||||
### P1 : 매칭 단위 = `CompositionUnit` (section 아님)
|
||||
|
||||
- *근거* : `composition.py:230-243` (single) / `:260-273` (parent_merged) / `:352-368` (parent_merged_inferred). 각 line `:232` / `:262` / `:354` 가 `merge_type` signal
|
||||
- *함의* : *MDX section* 을 1:1 zone 매칭 단위로 가정하는 코드 / doc 는 모두 *추상화 누락*. *CompositionUnit* 으로 통일.
|
||||
|
||||
### P3 : frame 선택 / content binding *layer 분리*
|
||||
|
||||
- *근거* : 현재 코드의 *de facto* 분리 (mapper §1.3 + placement_planner §1.4)
|
||||
- *원칙* : frame *선택* (어떤 frame 을 쓸지) 과 content *binding* (선택된 frame 에 콘텐츠를 어떻게 채울지) 는 *별개 axis*. 함께 결정하면 search space 가 곱셈으로 폭발 + 실패 격리 불가.
|
||||
- *함의* : 어떤 axis 가 frame 을 *결정* 하는지 + 어떤 axis 가 *binding* 하는지 *명시적 분리* — 한 module 이 둘 다 하면 회귀 위험.
|
||||
|
||||
### P5'' : 4-tier terminology lock
|
||||
|
||||
§2.1 표 그대로. *후속 doc / commit message / 코드 docstring* 모두 4-tier 어휘만 사용.
|
||||
|
||||
### P7 : candidate-based composition (sequential A/B 아님)
|
||||
|
||||
- *근거* :
|
||||
- 원리 : `docs/architecture/PHASE-Z-PIPELINE-OVERVIEW.md:216-218` ("child 따로 / sibling 묶기 / parent 단위" + scoring inputs)
|
||||
- 구현 : `composition.py:230-243` / `:260-273` / `:352-368` (3 candidate type 동시 생성)
|
||||
- 선택 : `composition.py:433-441` (`(score desc, source_section_ids count desc)` + greedy covered skip)
|
||||
- *원칙* : composition 은 "## → fail → ### split → re-merge" sequential 이 아니라 *3 candidate type 을 동시 생성 → score + coverage tie-break* 의 *parallel evaluation*.
|
||||
- D2:524-533 은 *Frame Slot tie-break (Stage B)* 로 본 P7 evidence 와 *별도 layer*.
|
||||
|
||||
### P8 : staged migration — frame 선택 authority + binding 진화 분리
|
||||
|
||||
#### P8-a (near-term)
|
||||
|
||||
- *목표* : frame selection authority 정리 (V4 rank-1 단독 → B4 informant 또는 B4-mediated 의 *어떤 형태*)
|
||||
- *유지* : mapper binding channel (PAYLOAD_BUILDERS) — 현재 working channel 안전
|
||||
- *open* : B4 결과를 render path frame selection 에 *어떻게 반영* 할지의 bridge 형태 → §5 의 Q-V4B4 / Q-LB 에서 별도 scope-lock
|
||||
|
||||
#### P8-b (longer-term, *지금 결정 X*)
|
||||
|
||||
- region / Frame Slot-aware slot_payload 진화
|
||||
- PLACEMENT_PAYLOAD_BUILDERS 등 별도 namespace 가능성
|
||||
- *DC2 Open question (Q-LB) 해결 후* 결정. 본 문서 시점에서는 *deferred*.
|
||||
|
||||
|
||||
## §4. 코드 변경 결정 지점
|
||||
|
||||
### DC1 : Step 4/5/9 분리 미완
|
||||
|
||||
- *현 상태* : D1 의도한 Step 4 → Step 5 → Step 9 분리가 완성되지 않음. Step 4 (composition planning input — section_layout_signature / content_object 구조) 는 *partial/dormant* (D4:73 의 ⚠ partial), Step 5 evidence 와 Step 9 final selection 은 *conflate* (D1:255).
|
||||
- *결정 지점* :
|
||||
- Step 4 가 frame matching *전* 에 composition candidate 평가 input 으로 흘러야 (D1:185 명시적 지적)
|
||||
- Step 5 evidence layer 와 Step 9 final selection layer 의 *명시적 분리* 필요
|
||||
- *영향 module* : `src/phase_z2_pipeline.py` orchestrator + `src/phase_z2_composition.py` (input 확장)
|
||||
|
||||
### DC2-a : `phase_z2_mapper` = Layer 1 working channel
|
||||
|
||||
- *현 상태* : `src/phase_z2_mapper.py:584-609` 가 `source_shape` + `payload.builder` 만 read. *현재 render path 의 유일 working channel*.
|
||||
- *결정 지점* : *Layer 1 격리 유지*. mapper 에 Layer 2 read 를 추가하는 방향은 *반대* — 실제 의도는 별 axis (DC2-b + Open question Q-LB).
|
||||
|
||||
### DC2-b : `phase_z2_placement_planner` = Layer 2 reader, render path 미연결
|
||||
|
||||
- *현 상태* : `src/phase_z2_placement_planner.py:90-107` (accepted_content_types) + `:113-151` (sub_zones) — Layer 2 read 활성. 그러나 `src/phase_z2_pipeline.py:1060-1145` 에서 *trace-only*. final render 미연결.
|
||||
- *결정 지점* : Layer 2 read 결과를 render path 에 *어떻게 합류시킬지* 의 bridge 결정 — §5 의 Q-LB 에서 별도 scope-lock.
|
||||
|
||||
### DC3 : Internal Region runtime — trace-only partial
|
||||
|
||||
- *현 상태* : SPEC v1 (D2) 가 Internal Region (Layer A) / Frame Slot (Layer B) 의 layered placement 정의. Layer A runtime 은 *trace-only partial* — `src/phase_z2_placement_planner.py:157+` 의 `plan_placement` 가 Stage A 호출 (`:192-197` 의 `plan_internal_regions` call) 수행하지만, 결과가 render path 미합류. *부재* 가 아니라 *render path authority 미연결*. D4:73 의 ⚠ partial 분류와 일치.
|
||||
- *결정 지점* : Layer A planning telemetry 활성 단계 → render path 합류 단계의 *separate axis*. P8-b 와 연관 (slot_payload 진화 이전 단계).
|
||||
|
||||
|
||||
## §5. open scope-lock questions
|
||||
|
||||
본 axis 범위 *밖* 의 deferred 결정. 후속 axis 에서 각각 별도로 lock.
|
||||
|
||||
### Q-FW : 4 filter score weight / threshold
|
||||
- F1 frame internal fit / F2 child frame divergence / F3 content type-structure match / F4 content loss risk 의 *score weight* 와 *auto vs review threshold* 결정 필요.
|
||||
|
||||
### Q-CO : candidate priority tie-break
|
||||
- composition.py:433 의 `(score, source_section_ids count)` 외에 *추가 axis* (cardinality_fit / hierarchy_coherence / density) 도입 시점.
|
||||
|
||||
### Q-RT : review / adapter_needed threshold
|
||||
- W1/W2/W3 신호 + auto_selectable=False candidate 의 review surface 정책.
|
||||
|
||||
### Q-V4B4 : V4 → B4 frame selection authority transition timing
|
||||
- P8-a 의 *언제* — V4 단독 / B4 informant / B4-mediated 의 단계.
|
||||
|
||||
### Q-LB : Layer 1 ↔ Layer 2 bridge architecture
|
||||
- DC2-b 의 *어떻게* — mapper direct consume / B4-mediated integration / 별도 translator. Q-V4B4 와 연관되나 *별개 layer* (frame 선택 timing vs Layer 2 → render path bridge 구조).
|
||||
|
||||
### Q-CE : catalog extension surface
|
||||
- frame_contracts.yaml 에 신규 frame 추가 시 *어떤 layer 부터 채워야 self-consistent* 인지의 declarative form.
|
||||
|
||||
### Q-DT : decision trace 표준 form
|
||||
- composition / placement / fit_classification 의 trace 통합 schema. 현재 각각 다른 dict 구조.
|
||||
@@ -0,0 +1,472 @@
|
||||
# Phase Z — master pipeline overview
|
||||
|
||||
**Status** : 마스터 reference (2026-04-30 잠금). 본 문서 = *워크플로우 전체 도면*. 향후 모든 작업은 *이 22-step 도면의 어느 위치에 속하는지* 먼저 self-locate 해야 함.
|
||||
|
||||
**용도** :
|
||||
- 새 작업 시작 시 — "지금 하는 게 22-step 중 어느 step 인가" 식별
|
||||
- 새 spec / memory rule 추가 시 — "어느 step 의 어느 의사결정에 적용되는가" 매핑
|
||||
- 새 sample / 새 frame 추가 시 — "어디서 막힐 가능성이 높은가" 사전 예측
|
||||
|
||||
**본 문서가 *하지 않는* 것** :
|
||||
- 새 구현 제안 X
|
||||
- next step 추천 X
|
||||
- 우선순위 결정 X
|
||||
- A/B/C 선택지 X
|
||||
- specific MDX sample 분석 X
|
||||
|
||||
본 문서는 *기준점*. 의사결정은 별도 step 에서.
|
||||
|
||||
---
|
||||
|
||||
## 3-block 구조
|
||||
|
||||
전체 22 step 은 다음 3 block 으로 grouping :
|
||||
|
||||
| Block | Step 범위 | 역할 |
|
||||
|---|---|---|
|
||||
| **A. PRE-RENDER PLANNING** | 0 — 12 | render *전* 모든 결정 — *slide-level zone 분배* + *zone-internal region 분배* + frame / slot 매핑. *진짜 fit policy 의 중심* |
|
||||
| **B. RENDER** | 13 | Jinja2 + frame partial → final.html |
|
||||
| **C. POST-RENDER TELEMETRY / EXCEPTION HANDLING** | 14 — 22 | render 결과 검증 + 분류 + routing + status. *exception 처리 layer* |
|
||||
|
||||
**중요** : 진짜 fit policy 의 자리는 A block (composition planning). C block (telemetry) 은 *exception 처리 + 진단 안내* layer. 둘 다 필요하지만 *위치가 다름*.
|
||||
|
||||
---
|
||||
|
||||
## 위계 + 용어 (entity hierarchy)
|
||||
|
||||
본 파이프라인은 다음 entity 위계 위에서 동작 :
|
||||
|
||||
> **Lock phrase (canonical)** : `Slide → Zone → Internal Region → Frame → Frame Slot → Content`
|
||||
|
||||
```
|
||||
Slide
|
||||
└─ Zone (slide-level layout 이 만든 큰 영역)
|
||||
└─ Internal Region (zone *내부* 영역, frame *밖*)
|
||||
└─ Frame (Figma design 단위)
|
||||
└─ Frame Slot (frame *내부* 자리)
|
||||
└─ Content unit (text / table / image / details / ...)
|
||||
```
|
||||
|
||||
### Universal Region Model
|
||||
|
||||
> **Lock phrase (canonical)** :
|
||||
> `Every Zone has 1+ Internal Regions.`
|
||||
> `text-only zone = single-region.`
|
||||
> `mixed-content zone = multi-region.`
|
||||
|
||||
```
|
||||
모든 Zone 은 1 개 이상의 Internal Region 을 가짐.
|
||||
|
||||
text-only zone = single-region zone (현 거동의 자연 표현)
|
||||
mixed-content zone = multi-region zone
|
||||
|
||||
각 Internal Region 은 *자기만의* :
|
||||
- frame match
|
||||
- display strategy (inline / preview+details / popup-only / dropped)
|
||||
을 가질 수 있음.
|
||||
```
|
||||
|
||||
text-only section 도 *single-region zone* 으로 표현 (= 현 거동 보존). mixed-content (text + table / text + image / 등) 은 *multi-region zone* 으로 확장. region 이 *first-class entity* — special case 가 아님.
|
||||
|
||||
### 용어 표
|
||||
|
||||
| 용어 | 의미 | 위치 |
|
||||
|---|---|---|
|
||||
| **Slide** | 1280×720 한 장 | 최상위 |
|
||||
| **Zone** | slide-level layout 이 만든 큰 영역 (top / bottom / left / right 등) | Slide 안 |
|
||||
| **Internal Region** | Zone *내부* 영역, frame *밖*. content type 기반 분할 (text region / table region / image region / details region) | Zone 안 |
|
||||
| **Frame** | Figma design 단위 (= F13 / F29 / F16 등) | Internal Region 안 |
|
||||
| **Frame Slot** | frame *내부* 자리 (= pillar_1 / quadrant_1 / process_column 등) | Frame 안 |
|
||||
| **Content unit** | MDX section 안의 typed 콘텐츠 조각 (text_block / table / image / details / ...) | Frame Slot 에 배치 |
|
||||
|
||||
> **주의** : `PHASE-Z-CONTENT-OBJECT-SUBZONE-SPEC.md` 의 `sub_zones` (YAML 필드명) 은 본 표의 *Frame Slot (Layer B)* 의미로 정의되어 있음. 본 표의 *Internal Region (Layer A)* 는 SPEC v1 §2 에 정의됨.
|
||||
|
||||
---
|
||||
|
||||
## Operating Principles / Hard Locks
|
||||
|
||||
본 섹션 = *anchor / index*. 각 원칙의 상세 정의 / 적용 룰 / 예외 처리는 *referenced source* 에 있음. drift 방지를 위해 OVERVIEW 는 *짧은 anchor* 로만 둠.
|
||||
|
||||
### 1. MDX mapping convention
|
||||
|
||||
| MDX | 슬라이드 |
|
||||
|---|---|
|
||||
| `# 대목차 제목` | `slide-title` |
|
||||
| `# 대목차 결론` / note | `slide-footer` |
|
||||
| `##` / `###` 본문 | `slide-body` 안 (layout + zone + region + frame + slot) |
|
||||
| `<details>` | 별도 details layer |
|
||||
|
||||
> 참조 : `CLAUDE.md` 의 *MDX → 슬라이드 매핑* 표
|
||||
|
||||
### 2. 자유 디자인 금지
|
||||
|
||||
Figma frame DB / catalog / frame contract 기반으로만 디자인 결정. *임의 HTML / CSS 디자인 생성 X*. AI 가 frame 자체 / layout 자체 / 새 디자인 패턴을 *생성하지 않음*.
|
||||
|
||||
> 참조 : `CLAUDE.md` 디자인 원칙 + `feedback_no_hardcoding` + `feedback_blocks_must_be_css`
|
||||
|
||||
### 3. 원문 무손실
|
||||
|
||||
MDX 원문 *삭제 / 요약 / 압축 금지*. AI 호출이 normal path 에서 콘텐츠를 *재작성하지 않음*. 원문은 본문 preview 또는 details/popup 어딘가에 *반드시* 보존.
|
||||
|
||||
> 참조 : `feedback_ai_isolation_contract` + `PHASE-Z-CONTENT-OBJECT-SUBZONE-SPEC.md` §5.2
|
||||
|
||||
### 4. 그릇 변경 원칙 (positive form)
|
||||
|
||||
콘텐츠가 안 맞을 때 *콘텐츠를 줄이지 않음*. 대신 *그릇* (layout / zone / internal region / frame / display strategy) 을 변경하여 수용. 공통 CSS / padding / tolerance 임의 축소는 *그릇 변경* 이 아님 → 금지.
|
||||
|
||||
> 참조 : `feedback_phase_z_spacing_direction`
|
||||
|
||||
### 5. preview / details 원칙
|
||||
|
||||
inline preview = 원문의 *일부* 만 빌려 보여주는 것. 원문은 details / popup 에 *반드시* 보존. preview 자체가 원문을 대체하지 않음.
|
||||
|
||||
> 참조 : `PHASE-Z-CONTENT-OBJECT-SUBZONE-SPEC.md` §5.1 / §5.5
|
||||
|
||||
---
|
||||
|
||||
## Heritage / Current State (참고)
|
||||
|
||||
본 섹션 = 시간 따라 변할 수 있는 *history / state* 기록. *원칙* 아님. frame DB 확장 / vocabulary 진화 시 mechanical 갱신.
|
||||
|
||||
### 1. Type A / B / B' / B'' → 8-layout vocabulary 진화
|
||||
|
||||
기존 *Type A / B / B' / B''* 의 4 preset 은 사라진 게 아니라 **8-layout vocabulary** (single / horizontal-2 / vertical-2 / top-1-bottom-2 / top-2-bottom-1 / left-1-right-2 / left-2-right-1 / grid-2x2) 로 *일반화* 된 *전신*. Step 7 의 8 vocabulary 는 이 진화의 결과.
|
||||
|
||||
### 2. 현재 runtime-verified frame set 은 text-frame 중심
|
||||
|
||||
현재 *runtime contract-registered / verified* frame set = `F13` (three_parallel_requirements) / `F29` (process_product_two_way) / `F16` (bim_issues_quadrant_four) — *모두 text 전용 성격*. `figma_to_html_agent/blocks` 의 전체 frame inventory 가 image / table / mixed frame 을 얼마나 포함하는지는 *전수 audit 전까지 미확정*. 따라서 현재 runtime 기준 :
|
||||
|
||||
- *text region* → frame 매칭 (현 거동)
|
||||
- *image region* / *table region* / *details region* → 현재 contract-registered frame set 안에서는 frame 매칭 근거가 부족하므로 *display strategy* 로 처리 (image area 직접 배치 / table preview / details button 등)
|
||||
|
||||
**Step 9 의 region 단위 매칭은 현재 *runtime-verified frame set 기준으로 text region 만 frame 매칭 가능*** 함을 인지. 전체 frame inventory audit 또는 contract 등록 상태가 바뀌면 본 항목은 갱신 대상.
|
||||
|
||||
---
|
||||
|
||||
## 22-step 상세
|
||||
|
||||
각 step 의 형식 :
|
||||
> **Step N. 이름** — purpose (1-2 줄)
|
||||
> **Status** : ✅ implemented / ⚠ partial / ❌ missing
|
||||
> **Code 위치** : (해당 시)
|
||||
> **Gap** : (해당 시)
|
||||
|
||||
### Block A — PRE-RENDER PLANNING
|
||||
|
||||
#### Step 0. 사전 준비
|
||||
파이프라인 가동 전 준비되어 있어야 하는 정적 자료들. catalog / contract / matching data / template / asset.
|
||||
|
||||
- **포함** : Figma/BEP frame → HTML 변환물 / frame catalog / frame contract / V4 matching data + ontology / slide-base template / render assets
|
||||
- **frame contract 필수 필드** : frame_id, template_id, accepted_content_types, slots, sub_zones, capacity, visual_hints, asset paths
|
||||
- **Status** : ⚠ partial
|
||||
- **Code 위치** : `templates/phase_z2/catalog/frame_contracts.yaml` / `tests/matching/v4_full32_result.yaml` / `templates/phase_z2/slide_base.html` / `templates/phase_z2/families/*.html` / `figma_to_html_agent/blocks/`
|
||||
- **Gap** : `accepted_content_types` 와 `sub_zones` 필드 contract 에 미선언. *visual_hints* 는 일부만 (min_height_px). *density envelope* 미선언.
|
||||
|
||||
#### Step 1. MDX 업로드
|
||||
사용자 MDX 파일 입력. 목표 : *MDX 1 → 자동 슬라이드 1 장*.
|
||||
|
||||
- **Status** : ✅ implemented
|
||||
- **Code 위치** : `src/phase_z2_pipeline.py` 의 CLI entry (`run_phase_z2_mvp1(mdx_path, run_id)`)
|
||||
|
||||
#### Step 2. MDX 정규화
|
||||
업로드된 MDX 를 파이프라인 표준 구조로 변환. frontmatter 분리 / slide title / heading tree / section id / 대중소 목차 관계 / note·footer·details 분리 / **raw content 보존**.
|
||||
|
||||
- **Status** : ⚠ partial
|
||||
- **Code 위치** : `parse_mdx()` (frontmatter, ## sections, footer 추출) + `align_sections_to_v4_granularity()` (### drilling)
|
||||
- **Gap** : heading tree 자체는 미생성 (현재 flat list). note / details 분리 미완. 대중소 목차 관계도 implicit. 정규화 결과가 *단순 문자열 + section_id* 수준 — heading tree 가 있는 *정규화 MDX 모델* 이 아직 아님.
|
||||
|
||||
#### Step 3. Content Object 추출
|
||||
각 section 의 raw content 를 type 별 객체로 분해. text_block / bullet_list / numbered_list / table / image / diagram / jsx_block / note / details / long_original. *MDX 원문 보존, AI 요약 X*.
|
||||
|
||||
- **Status** : ❌ missing
|
||||
- **Cross-reference** : `docs/architecture/PHASE-Z-CONTENT-OBJECT-SUBZONE-SPEC.md` §1 (content_object 정규화 schema)
|
||||
|
||||
#### Step 4. Section Internal Composition Planning
|
||||
각 section 을 어떻게 다룰지 결정 — *whole-section 단일 frame 매칭* / *child-section grouping* / *content-type split* 의 3-way decision. split 인 경우 *Internal Region* 들로 분해 + region 비율 산정. **이 단계가 frame matching 보다 *먼저* 와야 함**.
|
||||
|
||||
- **3-way decision** :
|
||||
```
|
||||
section 전체 → 1 frame 매칭 가능?
|
||||
├ YES → whole-section frame match (single-region zone)
|
||||
└ NO → child-section grouping 가능?
|
||||
├ YES → group merge → 1 frame 매칭 (single-region zone)
|
||||
└ NO → content-type split
|
||||
→ text region / table region / image region / details region
|
||||
→ region 비율 산정 (예: text 80% / table 20%)
|
||||
→ multi-region zone
|
||||
```
|
||||
- **출력** :
|
||||
- `section_layout_signature` = text_only / text_plus_table / text_plus_image / table_heavy / image_with_caption / mixed_visual_text / details_heavy
|
||||
- `composition_decision` = whole / group / split
|
||||
- `internal_regions` (split 인 경우) = [{region_id, role, content_type, ratio_estimate}, ...]
|
||||
- **Status** : ❌ missing
|
||||
- **Cross-reference** : `docs/architecture/PHASE-Z-CONTENT-OBJECT-SUBZONE-SPEC.md` §2 (Internal Region schema, Layer A — entity / Universal Region Model / 3-way decision tree / 비율 산정 / topology vocabulary / region → frame·display interface). §1 의 content_object size_estimate / role 도 입력 자료.
|
||||
|
||||
#### Step 5. Matching Evidence 생성
|
||||
정규화된 section + layout need 기반으로 V4 매칭 evidence 수집. *최종 선택이 아니라 후보 evidence*.
|
||||
|
||||
- **대상** : 소목차 section / 중목차 parent / 필요 시 sibling group 후보
|
||||
- **V4 출력** : top-k frame candidates (frame_id, template_id, confidence, label, axes score)
|
||||
- **Label** : use_as_is / light_edit / restructure / reject
|
||||
- **Status** : ⚠ partial
|
||||
- **Code 위치** : `lookup_v4_match()` in `phase_z2_pipeline.py`
|
||||
- **Gap** : 현재 *rank-1 만* 반환. top-k 사용 안 됨. sibling group 후보도 없음.
|
||||
|
||||
#### Step 6. Composition Planning
|
||||
어떤 MDX 덩어리를 하나의 *slide-level zone unit* 으로 볼지 결정. child 따로 / sibling 묶기 / parent 단위.
|
||||
|
||||
- **판단 기준** : heading 관계 / content_object 구조 / section_layout_signature / V4 top-k evidence / frame compatibility / capacity fit / content density
|
||||
- **Status** : ⚠ partial
|
||||
- **Code 위치** : `src/phase_z2_composition.py` (`plan_composition`, `parent_merged_inferred`, `capacity_fit` integration)
|
||||
- **Gap** : section_layout_signature / content_object 구조 input 부재 (step 3, 4 가 없어서). frame compatibility 도 rank-1 매칭만 활용.
|
||||
|
||||
#### Step 7. Slide-Level Layout Planning
|
||||
composition unit 개수와 성격을 보고 slide 전체 layout 선택. *기존 Type A/B/B'/B'' 의 후속 — 8-vocabulary 로 명시화*.
|
||||
|
||||
- **8 layout vocabulary** : single, horizontal-2, vertical-2, top-1-bottom-2, top-2-bottom-1, left-1-right-2, left-2-right-1, grid-2x2
|
||||
- **Status** : ⚠ partial
|
||||
- **Code 위치** : `src/phase_z2_composition.py` 의 `select_layout_preset()` + `LAYOUT_PRESETS`
|
||||
- **Gap** : 현재 *count-based 만* (1→single, 2→horizontal-2, 3→top-1-bottom-2, 4→grid-2x2). "성격" (content_object 분포 / section_layout_signature) 미반영. 8 preset 중 horizontal-2 + single 만 실제 검증됨.
|
||||
|
||||
#### Step 8. Zone + Internal Region Ratio Planning
|
||||
선택된 layout 안에서 각 zone 의 크기 / 비율 결정 + 각 zone *내부의* Internal Region 비율 결정. *두 단계 ratio* 산정 (zone-level + region-level). *50/50 고정 X*. *slide-base / title / divider / footer / gap 임의 축소 금지*.
|
||||
|
||||
- **두 단계 ratio** :
|
||||
- zone-level : layout 의 각 zone 크기 (slide-body 안 분배)
|
||||
- region-level : 각 zone 안 Internal Region 비율 (single-region 이면 100%, multi-region 이면 Step 4 의 ratio_estimate)
|
||||
- **기준** : composition unit 중요도 / content_object 분량 / text·table·image 비중 / frame aspect / capacity / min·max zone / region 별 content type
|
||||
- **Status** : ⚠ partial
|
||||
- **Code 위치** : `compute_zone_layout()` (min_height + content_weight 분배) + `build_layout_css()` in `phase_z2_pipeline.py`
|
||||
- **Gap** : horizontal-2 만 zone-level dynamic. 나머지 7 preset 은 fr-default. **region-level ratio 미구현** (Internal Region 자체가 Step 4 부재로 입력 X). content_object 분량 기반 정밀화 미반영.
|
||||
|
||||
#### Step 9. Region-Level Frame / Display Selection
|
||||
각 *Internal Region* 에 들어갈 frame 또는 display strategy 확정. step 5 evidence 위에 composition / layout / region 제약 반영해 *최종* 선택. *unit of analysis = region*. single-region zone 은 자연스럽게 zone 1:1 frame 선택과 같음.
|
||||
|
||||
- **region 별 처리** :
|
||||
- text region → text frame 매칭 (현 runtime-verified contract set 기준 F13 / F29 / F16 등)
|
||||
- table region → table preview / details / table frame
|
||||
- image region → image area / image frame
|
||||
- details region → details / popup 전용 region
|
||||
- **Label 처리** (region 단위) :
|
||||
- use_as_is → deterministic slot mapping
|
||||
- light_edit → 같은 frame contract 유지, minor adaptation 가능
|
||||
- restructure → frame 후보 유지하되 content-to-slot 재배치 proposal 필요
|
||||
- reject → 자동 적용 X
|
||||
- **Status** : ⚠ partial — *step 5 와 분리되지 않음 + region-level 미구현 (zone 단위 만)*
|
||||
- **Code 위치** : `plan_composition()` 이 V4 rank-1 즉시 선택 (step 5 와 conflate, zone 단위)
|
||||
- **Gap** : top-k 활용 / composition 제약 반영한 final 단계가 없음. *region-level 매칭 부재* (현재 zone 단위만). restructure label 은 현재 *filter* (선택 X). MVP1_ALLOWED_STATUSES = {matched_zone, adapt_matched_zone} 만 통과.
|
||||
|
||||
#### Step 10. Frame Contract 확인
|
||||
선택된 frame 의 contract 읽어서 accepted_content_types / slots / sub_zones / cardinality / capacity / visual_hints / density envelope / asset 확인.
|
||||
|
||||
- **Status** : ⚠ partial
|
||||
- **Code 위치** : `get_contract()` + `frame_contracts.yaml` (F13/F29/F16)
|
||||
- **Gap** : `accepted_content_types` 미선언. `sub_zones` 미선언. `density envelope` 미선언.
|
||||
- **Cross-reference** : `docs/architecture/PHASE-Z-CONTENT-OBJECT-SUBZONE-SPEC.md` §3 (frame contract + Frame Slot, Layer B)
|
||||
|
||||
#### Step 11. Content Unit / Child Group → Internal Region → Frame Slot Mapping
|
||||
각 zone 안에서 *Internal Region 별로* content unit 또는 child group 을 배치 → 그 region 의 frame 내부 *Frame Slot* 에 매핑. 표 작으면 inline / 크면 preview + 자세히보기 / image aspect 유지 / 긴 원문 details / text capacity 내.
|
||||
|
||||
- **2 단계 매핑** :
|
||||
- Layer A : content unit / child group → Internal Region (Step 4 의 region 분할 결과 소비)
|
||||
- Layer B : Internal Region 안 → Frame Slot (frame contract 의 sub_zone 선언 소비)
|
||||
- **Status** : ❌ missing
|
||||
- **Cross-reference** : `docs/architecture/PHASE-Z-CONTENT-OBJECT-SUBZONE-SPEC.md` §4 (placement algorithm 2-stage: Stage A → Stage B) + §5 (display strategy). *해당 SPEC 의 `sub_zones` (YAML 필드명) = Frame Slot (Layer B). Internal Region (Layer A) 는 §2 에 정의됨.*
|
||||
|
||||
#### Step 12. Slot Payload 생성
|
||||
frame partial 에 주입할 데이터 생성. *deterministic mapper* 가 기본. *AI 는 normal path 에 없음*.
|
||||
|
||||
- **AI 가능 위치 (제한적)** : light_edit / restructure 에서 content_object → slot 배치 proposal 필요 시
|
||||
- **AI 금지** : MDX 원문 요약·삭제 / HTML·CSS 직접 생성 / 새 디자인 임의 / layout·frame 임의 선택
|
||||
- **Status** : ✅ implemented (deterministic 부분)
|
||||
- **Code 위치** : `src/phase_z2_mapper.py` (`map_with_contract`, PAYLOAD_BUILDERS, ITEM_PARSERS)
|
||||
- **Gap** : restructure label 의 AI proposal path 미구현 (현재 restructure 는 filter). content_object → sub_zone 매핑이 step 11 부재로 *implicit*.
|
||||
|
||||
### Block B — RENDER
|
||||
|
||||
#### Step 13. Render
|
||||
Jinja2 로 HTML 생성. **고정** : slide-base / slide size / title / divider / footer / slide-body. **가변** : layout / zone ratio / frame partial / slot payload / assets.
|
||||
|
||||
- **산출** : final.html / assets/ / debug.json / preview.png
|
||||
- **Status** : ✅ implemented
|
||||
- **Code 위치** : `render_slide()` in `phase_z2_pipeline.py` + `templates/phase_z2/slide_base.html` + `templates/phase_z2/families/*.html`
|
||||
|
||||
### Block C — POST-RENDER TELEMETRY / EXCEPTION HANDLING
|
||||
|
||||
> 본 block 의 핵심 — *A block (planning) 이 정밀하면 거의 trigger 안 일어남*. 이상적으로 대기 상태. exception 케이스의 *진단 + 다음 capability 안내*.
|
||||
|
||||
#### Step 14. Selenium Visual Runtime Check
|
||||
브라우저 렌더링 기준 실제 결과 검사. slide size / zone overflow / frame internal clipping / text·table·image clipping / content truncation.
|
||||
|
||||
- **Status** : ⚠ partial
|
||||
- **Code 위치** : `run_overflow_check()` in `phase_z2_pipeline.py`
|
||||
- **Gap** : 현재 *text / structural element overflow* 만 검사. image aspect mismatch / table clipping / under-fill 검사 미구현. clipped_inner 의 inner_content_signals 는 추가됨 (A1 step).
|
||||
|
||||
#### Step 15. Fit Classification
|
||||
visual fail 발생 시 원인 분류.
|
||||
|
||||
- **카테고리** : minor_overflow / structural_minor_overflow / structural_major_overflow / tabular_overflow / image_aspect_mismatch / frame_capacity_mismatch / layout_zone_mismatch / hard_visual_fail
|
||||
- **Status** : ✅ implemented (text / structural 도메인)
|
||||
- **Code 위치** : `src/phase_z2_classifier.py` (`classify_visual_runtime_check`, `CONTENT_TYPE_PATTERNS`)
|
||||
- **Cross-reference** : `docs/architecture/PHASE-Z-FIT-CLASSIFIER-ROUTER-SPEC.md` §1 / §2 / §3
|
||||
- **Gap** : image_aspect_mismatch / tabular_overflow 분류는 정의됐지만 *실제 trigger 가 step 14 의 검사 부재로 일어나지 않음*.
|
||||
|
||||
#### Step 16. Overflow Router
|
||||
fit classification 결과를 action 후보로 매핑.
|
||||
|
||||
- **매핑 예** : structural_minor_overflow → zone_ratio_retry / tabular_overflow → details_popup_candidate / image_aspect_mismatch → image_fit_candidate / frame_capacity_mismatch → frame_internal_fit_candidate
|
||||
- **Status** : ✅ implemented
|
||||
- **Code 위치** : `src/phase_z2_router.py` (`route_fit_classification`, `ACTION_BY_CATEGORY`)
|
||||
- **Cross-reference** : `docs/architecture/PHASE-Z-FIT-CLASSIFIER-ROUTER-SPEC.md` §4
|
||||
|
||||
#### Step 17. Implemented Action 실행
|
||||
구현된 action 만 실행. retry budget 제한 / 성공시만 final.html promote / 실패 candidate 는 final.html 아님 / 공통 CSS·padding·tolerance 변경 X / MDX 내용 삭제·요약 X.
|
||||
|
||||
- **Status** : ⚠ partial
|
||||
- **Implemented** : `zone_ratio_retry` (A3)
|
||||
- **Code 위치** : `src/phase_z2_retry.py` (`plan_zone_ratio_retry`, `apply_retry_to_layout_css`) + `_attempt_zone_ratio_retry` orchestrator in `phase_z2_pipeline.py`
|
||||
- **Missing actions** : `layout_adjust` / `frame_reselect` / `details_popup_escalation` / `image_fit_candidate` / `frame_internal_fit_candidate`
|
||||
- **Note (사용자 잠금)** : `frame_internal_fit_candidate` 가 *허용할 수 있는 내부 sub-mechanism* (density envelope / line rhythm / internal grid row / text block allocation 등) 은 *frame contract 가 declare 한 envelope 안* 에서만 동작하는 *내부 영역*. **별도 action label 로 등재하지 않음** — `density_adjust_candidate` 같은 이름은 *공통 CSS/padding 축소 antipattern* 을 초대할 위험이 있어 *unified label `frame_internal_fit_candidate` 하나* 로 묶음.
|
||||
|
||||
#### Step 18. Failure Classification
|
||||
action 실패 시 원인 분류.
|
||||
|
||||
- **Failure types** : donor_slack_insufficient / no_donor_candidates / rerender_still_fails / not_attempted
|
||||
- **Status** : ✅ implemented
|
||||
- **Code 위치** : `src/phase_z2_failure_router.py` (`classify_retry_failure`, `FAILURE_TYPE_DESCRIPTIONS`)
|
||||
|
||||
#### Step 19. Next Action Proposal
|
||||
실패 원인 + 원래 overflow severity *함께* 보고 다음 후보 기록. failure_type 단독 X. **overflow_category + line_equivalent + failure_type 의 결합**으로 결정.
|
||||
|
||||
- **예시 (severity-aware)** :
|
||||
- structural_minor_overflow + donor_slack_insufficient → frame_internal_fit_candidate
|
||||
- structural_major_overflow + * → details_popup_candidate
|
||||
- tabular_overflow + * → table_preview_or_details_candidate
|
||||
- frame mismatch → frame_reselect_candidate
|
||||
- **Status** : ⚠ partial
|
||||
- **Code 위치** : `src/phase_z2_failure_router.py` (`route_retry_failure`, `NEXT_ACTION_BY_FAILURE`)
|
||||
- **Gap** : 현재 *failure_type 단독* mapping (1-차원). severity (overflow_category × line_equivalent) 와의 *2-차원* mapping 미구현. `frame_internal_fit_candidate` 의 *execution contract / internal envelope* 미정의 (label 자체는 router/failure routing 에 등장하지만 *실제로 어떻게 동작하는지 + frame contract 가 declare 할 envelope 의 형식* 은 미정).
|
||||
|
||||
#### Step 20. Slide Status 결정
|
||||
final.html 존재 ≠ PASS. 정확한 상태 분류.
|
||||
|
||||
- **Status enum** : PASS / RENDERED_WITH_VISUAL_REGRESSION / PARTIAL_COVERAGE / ABORTED
|
||||
- **판단** : 모든 section coverage + visual ok → PASS / visual fail 있음 → RENDERED_WITH_VISUAL_REGRESSION / 일부 section 만 렌더 → PARTIAL_COVERAGE / 필수 단계 실패 → ABORTED
|
||||
- **Status** : ✅ implemented
|
||||
- **Code 위치** : `compute_slide_status()` in `phase_z2_pipeline.py`
|
||||
|
||||
#### Step 21. Debug / Trace 기록
|
||||
전체 의사결정을 debug.json 에 기록. 정규화 MDX / content_objects / section_layout_signature / V4 evidence / composition_units / layout / zone sizes / frames / contracts / sub_zone mapping / slot_payload / render result / visual check / fit classification / router decision / action trace / failure classification / next action proposal / slide_status.
|
||||
|
||||
- **Status** : ⚠ partial
|
||||
- **Code 위치** : `write_debug_json()` in `phase_z2_pipeline.py`
|
||||
- **Gap** : content_objects / section_layout_signature / sub_zone mapping 항목은 step 3, 4, 11 부재로 미기록. *region-level telemetry* (region count / region ratios / region-level frame matching / region-level display strategy) 도 Internal Region (Layer A) 부재로 미기록. 그 외 항목은 모두 기록됨.
|
||||
|
||||
#### Step 22. 사용자 확인 / Export
|
||||
사용자가 결과 확인. 현재 목표 = MDX → 자동 슬라이드 1 장 → status / debug. 향후 = layout 재선택 UI / top3 frame 선택 UI / zone 이동 / HTML 다운 / Gitea push.
|
||||
|
||||
- **Status** : ❌ missing (UI 영역 — 현재 범위 외)
|
||||
- **Code 위치** : 없음 (CLI 만)
|
||||
|
||||
---
|
||||
|
||||
## Status matrix 요약
|
||||
|
||||
| Block | Step | Status |
|
||||
|---|---|---|
|
||||
| A | 0. 사전 준비 | ⚠ partial |
|
||||
| A | 1. MDX 업로드 | ✅ |
|
||||
| A | 2. MDX 정규화 | ⚠ partial |
|
||||
| A | 3. Content Object 추출 | ❌ |
|
||||
| A | 4. Section Internal Composition Planning | ❌ |
|
||||
| A | 5. Matching Evidence | ⚠ partial (rank-1 only) |
|
||||
| A | 6. Composition Planning | ⚠ partial |
|
||||
| A | 7. Slide-Level Layout Planning | ⚠ partial (count-based) |
|
||||
| A | 8. Zone + Internal Region Ratio Planning | ⚠ partial (zone-level horizontal-2 만 dynamic, region-level 미구현) |
|
||||
| A | 9. Region-Level Frame / Display Selection | ⚠ merged with step 5 + region-level 미구현 |
|
||||
| A | 10. Frame Contract 확인 | ⚠ partial (no sub_zones) |
|
||||
| A | 11. Content Unit / Child Group → Internal Region → Frame Slot Mapping | ❌ |
|
||||
| A | 12. Slot Payload 생성 | ✅ (deterministic) |
|
||||
| B | 13. Render | ✅ |
|
||||
| C | 14. Selenium Visual Runtime Check | ⚠ partial (text/structural only) |
|
||||
| C | 15. Fit Classification | ✅ |
|
||||
| C | 16. Overflow Router | ✅ |
|
||||
| C | 17. Implemented Action 실행 | ⚠ partial (zone_ratio_retry only) |
|
||||
| C | 18. Failure Classification | ✅ |
|
||||
| C | 19. Next Action Proposal | ⚠ partial (1-D mapping) |
|
||||
| C | 20. Slide Status 결정 | ✅ |
|
||||
| C | 21. Debug / Trace 기록 | ⚠ partial (planning trace 누락) |
|
||||
| C | 22. 사용자 확인 / Export | ❌ (UI 미구현) |
|
||||
|
||||
**핵심 gap 위치 (❌ 표시)** :
|
||||
- Step 3 — Content Object 추출
|
||||
- Step 4 — Section Internal Composition Planning (3-way decision + Internal Region 분할)
|
||||
- Step 11 — Content Unit / Child Group → Internal Region → Frame Slot Mapping
|
||||
- Step 22 — 사용자 UI
|
||||
|
||||
**부분 구현 위치 (⚠) 의 주요 결손** :
|
||||
- Step 5 — top-k 미사용
|
||||
- Step 8 — region-level ratio 미구현 (zone-level horizontal-2 만 dynamic)
|
||||
- Step 9 — Step 5 와 conflate + region-level 매칭 부재
|
||||
- Step 10 — sub_zones 미선언 (frame contract / Layer B)
|
||||
- Step 14 — image / table 검사 부재
|
||||
- Step 17 — `zone_ratio_retry` 외 action 모두 미구현
|
||||
- Step 19 — severity-aware 2-차원 매핑 미구현
|
||||
- Step 21 — planning trace 누락 (step 3, 4, 11 부재 종속) + region-level telemetry 미기록 (Layer A 부재 종속)
|
||||
|
||||
---
|
||||
|
||||
## 기존 spec 문서 cross-reference
|
||||
|
||||
| Spec 문서 | 다루는 step |
|
||||
|---|---|
|
||||
| `docs/architecture/PHASE-Z-CATALOG-RUNTIME-DESIGN.md` | Step 0 (catalog 룰), Step 10 (frame contract), Step 12 (mapper) |
|
||||
| `docs/architecture/PHASE-Z-FRAME-STYLE-INVENTORY.md` | Step 0 (frame inventory) |
|
||||
| `docs/architecture/FRAME-INTEGRATION-MAP.md` | Step 0 (frame inventory) |
|
||||
| `docs/architecture/PHASE-Z-FIT-CLASSIFIER-ROUTER-SPEC.md` | Step 14, 15, 16, 17, 18, 19 |
|
||||
| `docs/architecture/PHASE-Z-CONTENT-OBJECT-SUBZONE-SPEC.md` | Step 3 (§1), Step 4 (§2 Internal Region / Layer A), Step 10 (§3 frame contract + Frame Slot / Layer B), Step 11 (§4 placement 2-stage + §5 display strategy). *해당 SPEC 의 `sub_zones` (YAML 필드명) = Frame Slot (Layer B). Internal Region (Layer A) 는 §2 에 정의됨.* |
|
||||
|
||||
## Memory feedback rules cross-reference
|
||||
|
||||
| Memory rule | 적용 step / 의사결정 |
|
||||
|---|---|
|
||||
| `feedback_one_step_per_turn` | 모든 step (작업 분할 discipline) |
|
||||
| `feedback_no_hardcoding` | 모든 step (특히 9, 11, 12, 17) |
|
||||
| `feedback_ai_role_separation` | Step 12 (AI 위치 제한) |
|
||||
| `feedback_ai_isolation_contract` | Step 12 (normal path AI 금지) |
|
||||
| `feedback_phase_z_spacing_direction` | Step 17 (CSS 공통 spacing 변경 금지) |
|
||||
| `feedback_artifact_status_naming` | Step 20 (slide_status enum) |
|
||||
| `feedback_auto_pipeline_first` | Block C 전체 (review/UI 개념 끼우지 말 것) |
|
||||
| `feedback_sample_budget` | Step 1 (미사용 sample 분리 보존) |
|
||||
| `feedback_detail_quality` | 모든 step (self-check) |
|
||||
| `feedback_blocks_must_be_css` | Step 13 (frame partial CSS 원칙) |
|
||||
| `feedback_recipe_variety` | Step 7, 9 (vocabulary 표현 범주) |
|
||||
| `feedback_absolute_paths` | 보고 / 문서 작성 시 |
|
||||
| `feedback_html_preview_whitebg` | Step 13 (slide-base 배경) |
|
||||
| `feedback_figma_*` | Step 0 (figma frame 변환 / asset 작업) |
|
||||
|
||||
---
|
||||
|
||||
## How to use this document
|
||||
|
||||
새 작업 시작 시 :
|
||||
1. *어느 step* 의 작업인지 식별
|
||||
2. 그 step 의 *Status* 확인 (✅ / ⚠ / ❌)
|
||||
3. 해당 step 의 cross-reference 된 spec 문서 / memory rule 확인
|
||||
4. 작업 결과가 *다른 step 에 영향* 주는지 확인 (block A 변경 → block C 의 trace 자동 변동)
|
||||
|
||||
새 spec 문서 추가 시 :
|
||||
- 본 문서의 *cross-reference 표* 에 등록 (어느 step 영역인지)
|
||||
|
||||
새 memory rule 추가 시 :
|
||||
- 본 문서의 *Memory feedback rules cross-reference* 표에 등록 (어느 step 의 의사결정인지)
|
||||
|
||||
작업 도중 — *어느 step 에 속하는지 모르는 작업이 들어오면* — 본 22-step 도면에 매핑이 안 된다는 것 자체가 *작업이 over-scoped 되었거나 새 step 정의가 필요* 하다는 신호.
|
||||
|
||||
---
|
||||
|
||||
## 본 문서의 보존 / 변경 정책
|
||||
|
||||
- 본 문서는 *기준점*. 가벼운 정정 / status 갱신은 진행 가능 (예: ❌ → ⚠ → ✅ 변동)
|
||||
- *22 step 의 추가 / 제거 / 순서 변경* 은 사용자 명시 잠금 후에만
|
||||
- 본 문서의 *3-block 구조* 는 architectural reframe lock 의 직접 반영. 변경 시 reframe 자체를 다시 봄
|
||||
@@ -0,0 +1,158 @@
|
||||
# Phase Z — pipeline status board
|
||||
|
||||
**Snapshot date** : 2026-05-04 (B1~B5 + trace-only runtime 연결 closure 반영 — Layer A telemetry first activation)
|
||||
**역할** : 현재 위치표 / grading snapshot. *지도 본문* 은 [`PHASE-Z-PIPELINE-OVERVIEW.md`](PHASE-Z-PIPELINE-OVERVIEW.md).
|
||||
|
||||
| 문서 | 역할 | 변동 |
|
||||
|---|---|---|
|
||||
| `PHASE-Z-PIPELINE-OVERVIEW.md` | 고정 지도 (22-step 도면) | 거의 안 바뀜 |
|
||||
| `PHASE-Z-PIPELINE-STATUS-BOARD.md` | 현재 진행 snapshot | 자주 갱신 |
|
||||
|
||||
본 문서가 *하지 않는* 것 :
|
||||
- 새 구현 제안 X
|
||||
- next step 추천 X
|
||||
- 우선순위 결정 X
|
||||
- A/B/C 선택지 X
|
||||
- MDX03 / MDX04 추가 분석 X
|
||||
- 코드 변경 X
|
||||
- OVERVIEW 구조 수정 X
|
||||
|
||||
---
|
||||
|
||||
## 1. Counting rule
|
||||
|
||||
```
|
||||
Step 0 = precondition (파이프라인 가동 전 사전 준비)
|
||||
Step 1~22 = runtime pipeline ("22-step pipeline" = 이 범위)
|
||||
총 항목 수 = 23 (Step 0 + Step 1~22)
|
||||
명명 = "22-step" (runtime 기준)
|
||||
```
|
||||
|
||||
Step 0 은 본체가 아닌 *준비 조건*. Step 1 (MDX 업로드) 부터가 runtime entry.
|
||||
|
||||
---
|
||||
|
||||
## 2. 22-step status board
|
||||
|
||||
| Block | Step | 이름 | Status |
|
||||
|---|---|---|---|
|
||||
| — | 0 | 사전 준비 (catalog / contract / V4 / template / asset) | ⚠ partial |
|
||||
| A | 1 | MDX 업로드 | ✅ |
|
||||
| A | 2 | MDX 정규화 | ⚠ partial |
|
||||
| A | 3 | Content Object 추출 | ⚠ partial (B1 v0 dormant module + trace-only runtime 호출, render path 미연결) |
|
||||
| A | 4 | Section Internal Composition Planning | ⚠ partial (B2 v0 dormant module + trace-only runtime 호출, render path 미연결) |
|
||||
| A | 5 | Matching Evidence 생성 | ⚠ partial (rank-1 only) |
|
||||
| A | 6 | Composition Planning | ⚠ partial |
|
||||
| A | 7 | Slide-Level Layout Planning | ⚠ partial (count-based) |
|
||||
| A | 8 | Zone + Internal Region Ratio Planning | ⚠ partial (zone-level horizontal-2 만 dynamic, region-level 은 B2 안 partial) |
|
||||
| A | 9 | Region-Level Frame / Display Selection | ⚠ partial (B4 가 catalog cover + declaration order 로 frame 선택 분담 / V4 evidence 미통합 / Step 5 와 conflate 잔존) |
|
||||
| A | 10 | Frame Contract 확인 | ⚠ partial (B3 의 accepted_content_types + sub_zones 선언 추가 — B4 만 읽음, mapper 미읽음 / density envelope 별 axis) |
|
||||
| A | 11 | Content Unit / Child Group → Internal Region → Frame Slot Mapping | ⚠ partial (B4 v0 dormant 2-stage + region 1:1 sub_zone + narrowest first + trace-only runtime 호출, render path 미연결) |
|
||||
| A | 12 | Slot Payload 생성 | ✅ (deterministic) |
|
||||
| B | 13 | Render | ✅ |
|
||||
| C | 14 | Selenium Visual Runtime Check | ⚠ partial (text/structural overflow + B5 frame_slot_metrics F29 만 / image / table 검사 부재) |
|
||||
| C | 15 | Fit Classification (A1) | ✅ |
|
||||
| C | 16 | Overflow Router (A2) | ✅ |
|
||||
| C | 17 | Implemented Action 실행 (A3) | ⚠ partial (zone_ratio_retry only) |
|
||||
| C | 18 | Failure Classification (A4-1) | ✅ |
|
||||
| C | 19 | Next Action Proposal (A4-2) | ⚠ partial (1-D mapping) |
|
||||
| C | 20 | Slide Status 결정 | ✅ |
|
||||
| C | 21 | Debug / Trace 기록 | ⚠ partial (placement_trace per-zone 기록 + frame_slot_metrics F29 기록 — render path 활성화 X / region marker partial 미주입) |
|
||||
| C | 22 | 사용자 확인 / Export | ⚠ future (UI 영역 — 현재 범위 외) |
|
||||
|
||||
범례 :
|
||||
- ✅ implemented
|
||||
- ⚠ partial
|
||||
- ❌ missing
|
||||
- ⚠ future (현 범위 외 — 후속)
|
||||
|
||||
---
|
||||
|
||||
## 3. 핵심 missing (전이 후)
|
||||
|
||||
이전 Step 3 / 4 / 11 = ❌ missing → **본 session 작업으로 ⚠ partial 로 전이**.
|
||||
|
||||
**현재 *남은* gap** :
|
||||
|
||||
```
|
||||
1. render path 의 placement_trace 활용 X
|
||||
- B4 PlacementPlan 이 trace-only — render_slide() / mapper 가 미사용
|
||||
- region-id / content_unit-id marker 가 partial template 에 미주입 (B5 후속 axis)
|
||||
|
||||
2. B4 frame_selection 의 V4 evidence 미통합
|
||||
- B4 v0 = catalog declaration order 만 (cover + first-match)
|
||||
- composition_planner 의 V4 rank-1 와 *cross-axis 비교 자료* 만 — 통합 미완
|
||||
|
||||
3. region-level / Frame Slot-level partial 측정
|
||||
- B5 v0 frame_slot_metrics = F29 1 partial 만
|
||||
- F13 / F16 marker 미적용
|
||||
|
||||
4. 그 외 잔존
|
||||
- rules 2~5 (region-preview-details / region-grid-2x2 / region-main-support /
|
||||
region-horizontal-split) 의 algorithm 미구현 (SPEC v1 §2.5 deferred)
|
||||
- frame contract 의 density envelope 미선언
|
||||
- tabular_overflow / image_aspect_mismatch 검사 부재 (Step 14)
|
||||
- layout_adjust / frame_reselect / details_popup_escalation / image_fit /
|
||||
frame_internal_fit_candidate (Step 17 missing actions) 미구현
|
||||
```
|
||||
|
||||
**Cross-cutting Layer A — 진전 단계 정리** :
|
||||
|
||||
| 단계 | 상태 |
|
||||
|---|---|
|
||||
| (a) OVERVIEW reframe (Layer A first-class lock + Universal Region Model) | ✓ |
|
||||
| (b) SPEC v1 schema (Internal Region §2 + topology vocabulary §2.5 + 2-stage placement §4) | ✓ |
|
||||
| (c) PLAN v1 (cross-ref sync) | ✓ |
|
||||
| (d) B1 v0 ContentObject extractor (dormant) | ✓ |
|
||||
| (e) B2 v0 InternalRegion planner (dormant) | ✓ |
|
||||
| (f) B3 frame_contracts.yaml extension (dormant catalog 면) | ✓ |
|
||||
| (g) B4 v0 placement planner (dormant) | ✓ |
|
||||
| (h) B5 v0 Frame Slot telemetry markers (F29 만) | ✓ |
|
||||
| (i) trace-only runtime 연결 (B1→B2→B4 real data 첫 호출 / debug.json placement_trace) | ✓ |
|
||||
| (j) **render path 활성화 (region marker partial 주입 / B4 → mapper 통합 / V4 evidence 통합)** | **❌ pending** |
|
||||
|
||||
= (a)~(i) 완료 + (j) 가 *남은 핵심 axis* (B5 후속 / runtime 통합).
|
||||
|
||||
**Step 22** 는 별도 범주 (UI 영역 — 현재 자동 파이프라인 범위 외).
|
||||
|
||||
---
|
||||
|
||||
## 4. 구조 적절성 검토 (brief)
|
||||
|
||||
> snapshot — 22-step *재구성 / 합치기 / 쪼개기 제안 X*. OVERVIEW 영역.
|
||||
|
||||
- **3-block 구조 (A 계획 / B 렌더 / C 사후 telemetry) 적절**. 위계 추가 (Zone Internal Region) 후에도 block 경계는 변동 없음
|
||||
- **Step 3~4 가 Step 5 보다 앞** 인 순서 적절. content_object 와 internal composition decision (3-way) 이 frame matching 의 입력이어야 함
|
||||
- **Step 5 (evidence 생성) 와 Step 9 (final frame / display 선택) 가 분리** 된 구조 적절. 현재 conflate 된 건 구현 결손이지 도면 결손 아님. Step 9 의 unit of analysis = *region* 으로 reframe (OVERVIEW)
|
||||
- **A1~A4 는 post-render telemetry layer**. 진짜 fit policy 의 자리는 Block A (composition planning, region 분할 포함). C block 은 *exception 처리 + 진단 안내*
|
||||
- **Universal Region Model 적용 후에도 step numbering 보존** : Layer A 도입은 step 추가가 아니라 Step 4 / 8 / 9 / 11 의 *granularity unit shift* 로 흡수됨. step 0 ~ 22 그대로
|
||||
- **Layer A trace-only runtime 활성화 = boolean 차원 X** : B1~B4 가 *real MDX runtime 위에서 호출* 되나 *render path 미대체*. debug.json 의 placement_trace = *진단 telemetry only* — final.html / canonical SHA 미영향. *render 활성화* 는 별 axis (B5 후속)
|
||||
|
||||
---
|
||||
|
||||
## 5. AI 사용 위치 (runtime 기준)
|
||||
|
||||
```
|
||||
runtime AI = Step 12 의 light_edit / restructure 1 곳만
|
||||
├ 입력 : content_object + frame contract + Internal Region 배치 + Frame Slot 명세
|
||||
├ 출력 : content → Internal Region / Frame Slot proposal
|
||||
└ 금지 : MDX 원문 요약·삭제 / HTML·CSS 직접 생성 / layout·zone·region·frame 임의 선택
|
||||
```
|
||||
|
||||
Step 0 (사전 준비) 의 Figma → HTML 변환은 *precondition phase 의 작업* — runtime AI 아님.
|
||||
|
||||
다른 step 에서의 AI 호출은 본 도면 안에 *없음*.
|
||||
|
||||
---
|
||||
|
||||
## 6. 현재 병목 (한 줄)
|
||||
|
||||
> 현재 Phase Z 의 *Layer A pre-render planning* (Step 3 / 4 / 11) 은 본 session 작업으로 ❌ → ⚠ partial 전이 (B1/B2/B4 dormant module + trace-only runtime 호출). *Layer A telemetry 의 first activation* — debug.json 의 placement_trace per-zone + frame_slot_metrics F29 partial 기록. 단 **render path 활성화는 미완** : B4 PlacementPlan 이 mapper output 을 *대체하지 않고* trace-only / region-id / content_unit_id marker 가 partial template 에 *미주입* / B4 frame_selection 이 V4 evidence *미통합*. 핵심 다음 axis = **(B5 후속) render path 의 placement_trace 활용 + region marker runtime activation + V4 통합**. *runtime contract-registered / verified frame set 이 text-frame 중심* 한계는 잔존 (frame inventory audit / refinement 별 axis).
|
||||
|
||||
---
|
||||
|
||||
## 사용 방법
|
||||
|
||||
- 새 작업 들어오면 → 본 board 의 *어느 step* 의 status 를 바꾸는 작업인지 식별
|
||||
- 작업이 *Step 매핑이 안 되면* → over-scoped 또는 새 step 정의 필요 (OVERVIEW 영역)
|
||||
- ✅ → ⚠ → ❌ status 전이 / 갱신 시 → 본 board 만 수정. OVERVIEW 는 step 추가/제거/순서 변경 시에만
|
||||
@@ -0,0 +1,69 @@
|
||||
# Typography Tokens — 초안 v1
|
||||
|
||||
## Global Hierarchy Tokens
|
||||
|
||||
슬라이드 전체의 공통 글자 위계. 모든 블록이 이 기준을 따른다.
|
||||
|
||||
| 위계 | 토큰명 | font-size | font-weight | line-height | 용도 |
|
||||
|------|--------|-----------|-------------|-------------|------|
|
||||
| 대목차 | `--font-slide-title` | 22px | 700 | 1.4 | 슬라이드 상단 제목 |
|
||||
| 중목차 | `--font-zone-title` | 13px | 700 | 1.4 | zone 제목 (## 대목차 하위) |
|
||||
| 소목차 | `--font-sub-title` | 12px | 700 | 1.45 | 블록 내 소제목, 카드 제목 |
|
||||
| 본문 | `--font-body` | 11px | 400~500 | 1.55 | 블릿, 설명 텍스트 |
|
||||
| 캡션 | `--font-caption` | 10px | 400 | 1.4 | 각주, 출처, 보조 텍스트 |
|
||||
| footer | `--font-footer` | 20px | 700 | 1.2 | 핵심 인사이트 pill |
|
||||
|
||||
---
|
||||
|
||||
## Component Semantic Token 후보
|
||||
|
||||
위계 6종으로 안 덮이는 역할. 가까운 위계에서 기본값을 가져오되, 필요시 override.
|
||||
|
||||
| 역할 | 기반 위계 | 예상 override | 비고 |
|
||||
|------|-----------|---------------|------|
|
||||
| body-strong (본문 강조) | 본문 (11px) | weight: 600~700 | 본문 heading, 인라인 강조 |
|
||||
| detail-link (자세히보기) | 캡션 (10px) | weight: 500, color: muted | 링크 텍스트 |
|
||||
| pill-label | 소목차 (12px) | weight: 700, color: white | pill/badge 안 라벨 |
|
||||
| table-header | 소목차 (12px) | weight: 700, color: white, bg: dark | 표 헤더 셀 |
|
||||
| table-cell | 본문 (11px) | - | 표 데이터 셀 |
|
||||
| compare-badge | 소목차 (12px) | weight: 700 | 비교 블록 VS 뱃지 |
|
||||
| callout | 소목차 (12px) | weight: 700, color: accent | 강조 인용 |
|
||||
| overline | 캡션 (10px) | weight: 600, letter-spacing | 상단 라벨 |
|
||||
|
||||
---
|
||||
|
||||
## Source
|
||||
|
||||
현재 코드에서 추출한 실제 사용값 기준.
|
||||
|
||||
| 출처 | 대목차 | 중목차 | 소목차 | 본문 | 캡션 | footer |
|
||||
|------|--------|--------|--------|------|------|--------|
|
||||
| slide-base.html | 22px | - | - | - | - | 20px |
|
||||
| block_assembler.py zone title | - | 13px | - | - | - | - |
|
||||
| block_assembler.py direct render | - | - | 12px | 11px | - | - |
|
||||
| block_assembler.py .bul | - | - | - | 11px | - | - |
|
||||
| FontHierarchy (legacy) | - | - | key_msg 14 | core 12 | sidebar 10 | - |
|
||||
|
||||
---
|
||||
|
||||
## FontHierarchy 매핑 (보조 체계)
|
||||
|
||||
기존 FontHierarchy는 역할 기반이므로, 위계 기반으로 매핑하여 보조로 유지.
|
||||
|
||||
| FontHierarchy | 위계 매핑 | 비고 |
|
||||
|---------------|-----------|------|
|
||||
| key_msg (14px) | 소목차~중목차 사이 | 강조 메시지용, component token 후보 |
|
||||
| core (12px) | 소목차 | 기본 블록 제목급 |
|
||||
| bg (11px) | 본문 | 배경/보조 텍스트 |
|
||||
| sidebar (10px) | 캡션 | 사이드바/첨부 |
|
||||
|
||||
---
|
||||
|
||||
## 상태
|
||||
|
||||
- [x] inventory 수집 완료
|
||||
- [x] global hierarchy v1 초안
|
||||
- [x] component token 후보 분리
|
||||
- [ ] typography.css 파일 작성
|
||||
- [ ] spacing tokens 정의
|
||||
- [ ] color tokens 정의 (공통 + 의미색)
|
||||
|
Before Width: | Height: | Size: 936 KiB |
@@ -0,0 +1,101 @@
|
||||
# Phase X-B: 유형 B 템플릿 추가
|
||||
|
||||
> 최종 업데이트: 2026-04-06
|
||||
> 전제: 유형 A(배경+본심+첨부+결론) 기존 코드 건드리지 않음
|
||||
|
||||
---
|
||||
|
||||
## 유형 B 구조
|
||||
|
||||
02번 MDX (DX의 시행 목표 및 기대효과) 기준.
|
||||
MDX 원본 구조:
|
||||
```
|
||||
title: DX의 시행 목표 및 기대효과 ← 슬라이드 제목 (frontmatter)
|
||||
## 1. DX의 궁극적 목표 ← 상단 (level=2)
|
||||
- 안전과 품질 / 생산성 향상 / 소통과 신뢰 ← 소제목 카드
|
||||
 ← 상단 우측 이미지
|
||||
## 2. DX 기반 Process 혁신에 따른 주체별 기대효과 ← 하단 대목차 (level=2)
|
||||
### 2.1 업무 수행 과정(Process)의 변화 ← 하단 좌측 (level=3)
|
||||
### 2.2 DX 시행 주체별 기대효과 ← 하단 우측 (level=3) — 표 데이터
|
||||
:::note[핵심 요약]
|
||||
* 고품질의 성과품, 비용 절감... ← 결론 (원본 그대로)
|
||||
:::
|
||||
```
|
||||
|
||||
슬라이드 레이아웃:
|
||||
```
|
||||
┌──────────────────────────────────────────┐
|
||||
│ DX의 시행 목표 및 기대효과 (원본 title) │
|
||||
├───────────────────────┬──────────────────┤
|
||||
│ DX의 궁극적 목표 │ │
|
||||
│ ┌안전과 품질──────────┐│ [이미지] │
|
||||
│ │• 불릿 ││ DX의 궁극적 │
|
||||
│ ├생산성 향상──────────┤││ 목표 │
|
||||
│ │• 불릿 ││ │
|
||||
│ ├소통과 신뢰──────────┤│ │
|
||||
│ │• 불릿 ││ │
|
||||
│ └────────────────────┘│ │
|
||||
├──────────────────────────────────────────┤
|
||||
│ DX 기반 Process 혁신에 따른 주체별 기대효과 │ ← 대목차
|
||||
├───────────┬──────────────────────────────┤
|
||||
│ 2.1 업무 │ 2.2 DX 시행 주체별 기대효과 │
|
||||
│ 수행 과정 │ [바로가기 →] (팝업 링크) │
|
||||
│ 변화 │ ┌ Kei 요약 표 ──────────┐ │
|
||||
│ • 생산방식 │ │ 구분│발주자│시공자│설계자│ │
|
||||
│ • 인지검토 │ │ ...│ ...│ ...│ ...│ │
|
||||
│ • 협업구조 │ └──────────────────────┘ │
|
||||
│ • 검증대응 │ │
|
||||
├───────────┴──────────────────────────────┤
|
||||
│ 결론: 고품질의 성과품, 비용 절감... (원본) │
|
||||
└──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 진행 현황
|
||||
|
||||
### X-B-1: KEI_PROMPT 유형 B 옵션 추가 — ✅ 완료
|
||||
### X-B-2: 검증기 완화 — ✅ 완료
|
||||
### X-B-3: space_allocator 유형 B 컨테이너 생성 — ✅ 완료
|
||||
### X-B-4: assemble_stage2 유형 B 조립 — ✅ 완료 (code_assembled)
|
||||
### X-B-5: pipeline.py 분기 — ✅ 완료
|
||||
|
||||
### X-B-6: 검증 — ❌ 미완료
|
||||
|
||||
**code_assembled(assemble_stage2):**
|
||||
- 제목/대목차/소목차/텍스트: MDX 원본에서 직접 가져옴 ✅
|
||||
- 팝업 링크 + Kei 요약 표 ✅
|
||||
- 이미지 + 캡션 ✅
|
||||
- 카드형 소제목 ✅
|
||||
- **하지만 렌더링에서 잘림** — 컨테이너 크기 vs 내용 크기 불일치
|
||||
|
||||
**파이프라인(before→filled→after):**
|
||||
- **유형 B에서 동작 안 함** — block_assembler가 고정 4역할만 처리
|
||||
- filled가 거의 빈 HTML (2997bytes)
|
||||
- 이걸 해결해야 Selenium 측정 → 재배분이 가능
|
||||
|
||||
---
|
||||
|
||||
## 다음 세션 핵심 작업
|
||||
|
||||
**1. block_assembler 유형 B 지원**
|
||||
- `assemble_slide_html()`이 유형 B 역할도 처리
|
||||
- 또는 유형 B 전용 함수 추가
|
||||
- filled/after가 제대로 생성되어야 Selenium 측정 가능
|
||||
|
||||
**2. 컨테이너 크기 맞춤**
|
||||
- 현재 렌더링 잘림 → Selenium 측정 후 재배분으로 해결
|
||||
- 이건 1번이 해결되면 자동으로 동작
|
||||
|
||||
**3. 01번(유형 A) 깨지지 않는지 확인**
|
||||
|
||||
---
|
||||
|
||||
## 핵심 원칙
|
||||
|
||||
- 하드코딩 절대 금지
|
||||
- HTML 결과물 고치지 말고 파이프라인 프로세스 고칠 것
|
||||
- 제목/텍스트는 원본 MDX에서 그대로 (Kei가 바꾸지 않음)
|
||||
- Kei가 재구성하는 건 빈 공간 채우기(표 요약)만
|
||||
- 유형 A 코드 건드리지 않고 유형 B 추가
|
||||
- normalized.sections에서 직접 텍스트 가져옴 (Kei structured_text 대신)
|
||||
@@ -0,0 +1,309 @@
|
||||
# Phase X-BX': 유형 B 미완료 사항 정리
|
||||
|
||||
> 최종 업데이트: 2026-04-07
|
||||
> 전제: **유형 A 코드 절대 건드리지 않음.** A는 완벽하게 동작 중. 수정도 재검증도 하지 않음.
|
||||
> 유형 B의 code_assembled + 파이프라인만 수정.
|
||||
> **02번 MDX 먼저 → 03번 확장** 순서로 진행.
|
||||
|
||||
---
|
||||
|
||||
## MDX 원본 위치
|
||||
|
||||
`D:\ad-hoc\cel\src\content\docs\Civil DX\BIM과 DX의 이해\`
|
||||
|
||||
---
|
||||
|
||||
## 근본 원인
|
||||
|
||||
Type A는 Kei가 역할명을 `"배경"`, `"본심"`, `"첨부"`, `"결론"`으로 내려주고,
|
||||
하류 코드가 `containers["배경"]` 처럼 **역할명 글자**로 매칭한다. → 동작함.
|
||||
|
||||
Type B는 Kei가 역할명을 `"필수요건"`, `"과정혁신"` 등으로 내려주는데,
|
||||
하류 코드가 여전히 `containers["배경"]`을 찾는다. → **키가 없어서 빈 것.**
|
||||
|
||||
**해결:** Type B일 때는 역할명 글자가 아니라 `containers`에 있는 키를 순회하고,
|
||||
zone 정보(`top`, `bottom_left` 등)로 위치를 결정한다.
|
||||
|
||||
```python
|
||||
# Type A (기존 그대로):
|
||||
for role in ["배경", "본심", "첨부", "결론"]:
|
||||
container = containers[role]
|
||||
|
||||
# Type B (분기 추가):
|
||||
for role in containers:
|
||||
zone = containers[role].zone # top, bottom_left, bottom_right, footer
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## XBX-1: 들여쓰기 계층
|
||||
|
||||
### 현상
|
||||
MDX의 2단 계층(`* > *`)이 동일 레벨로 평탄화됨.
|
||||
|
||||
```
|
||||
MDX 원본: 현재 HTML:
|
||||
- 안전과 품질 (소제목) → • 안전과 품질 ← 소제목인데 불릿과 동일
|
||||
- 시설물의 요구 성능을... → • 시설물의 요구 성능을... ← 구분 없음
|
||||
```
|
||||
|
||||
### 목표
|
||||
```
|
||||
■ 안전과 품질 ← 소제목 (bold, 색상 구분)
|
||||
• 시설물의 요구 성능... ← 본문 불릿 (들여쓰기)
|
||||
```
|
||||
|
||||
### 수행 방향
|
||||
|
||||
**1단계: normalizer에서 불릿 depth 보존**
|
||||
|
||||
현재 `src/mdx_normalizer.py`의 section content:
|
||||
```
|
||||
"**안전과 품질**\n시설물의 요구 성능을..." ← flat, depth 정보 없음
|
||||
```
|
||||
|
||||
수정 후:
|
||||
```
|
||||
"- **안전과 품질**\n - 시설물의 요구 성능을..." ← depth 마커 보존
|
||||
```
|
||||
|
||||
markdown-it의 `list_item_open` 토큰에 이미 indent 정보 있음 (88번째 줄).
|
||||
section content 수집 시 indent level을 보존하면 됨.
|
||||
|
||||
**2단계: 조립 로직에서 depth별 스타일 분기**
|
||||
|
||||
`scripts/assemble_stage2.py` `_assemble_type_b` + `src/block_assembler.py` `_assemble_slide_html_type_b`:
|
||||
- depth 1 (`- `) → 소제목 스타일 (bold, 색상 구분, 카드)
|
||||
- depth 2 (` - `) → 본문 불릿 (들여쓰기, normal weight)
|
||||
|
||||
### 검증
|
||||
02번 상단 "안전과 품질/생산성 향상/소통과 신뢰" 3개 소제목이 카드로 분리,
|
||||
각각의 하위 불릿 2줄이 들여쓰기되어 보임.
|
||||
|
||||
---
|
||||
|
||||
## XBX-2: overflow → 콘텐츠 맞춤 프로세스
|
||||
|
||||
### 현상
|
||||
상단 zone(255px)에 소제목 3개 + 불릿 6줄 + 이미지 → overflow.
|
||||
하단 우측에 표 데이터가 너무 많아서 overflow.
|
||||
|
||||
### 프로세스 (네가 말한 것)
|
||||
```
|
||||
넘침 감지 → 최대 몇 줄까지 가능? → 몇 자 이내로 정리 → Kei에게 요약 요청 → 재수취 후 정리
|
||||
```
|
||||
|
||||
### 왜 안 되는가 (원인 3개)
|
||||
|
||||
**원인 1: Selenium 측정 실패**
|
||||
- `slide_measurer.py` 144줄: 2.2MB HTML을 `data:` URI로 로드 → 브라우저 크기 제한
|
||||
- Type A는 ~214KB라 동작, Type B는 이미지 base64 포함 2.2MB라 실패
|
||||
- **수정:** 임시 파일로 저장 후 `file://` URI로 로드 (크기 제한 없음)
|
||||
|
||||
**원인 2: overflow 분기에 Type B zone 없음**
|
||||
- `pipeline.py` 538-553줄: `sidebar`와 `body`만 처리
|
||||
- Type B zone(`top`, `bottom`)은 분기 없음 → overflow 감지돼도 무시됨
|
||||
- **수정:** `if layout_template == "B":` 분기 추가. top/bottom overflow 시 처리
|
||||
|
||||
**원인 3: calculate_fit에서 Type B 역할 인식 불가**
|
||||
- `fit_verifier.py` 307줄: `role_font_map = {"본심": "core", "배경": "bg", ...}`
|
||||
- Type B 역할명이 이 dict에 없어서 항상 `"core"` fallback
|
||||
- overflow 계산이 부정확 → `needs_escalation`이 항상 `False`
|
||||
- **수정:** `if layout_template == "B":` 분기. zone 기반 font 매핑
|
||||
|
||||
### 수행 순서
|
||||
|
||||
1. **Selenium 측정 수정** — data URI → 임시파일 방식 (slide_measurer.py)
|
||||
2. **overflow 분기 추가** — Type B zone 처리 (pipeline.py)
|
||||
3. **calculate_fit Type B 지원** — zone 기반 font 매핑 (fit_verifier.py)
|
||||
4. **에스컬레이션 → Kei 요약 요청** — 이미 있는 코드 활용 (pipeline.py 584-606)
|
||||
5. **검증** — 02번 파이프라인 돌려서 상단 overflow 해소 확인
|
||||
|
||||
### 검증
|
||||
- Selenium 측정에서 상단/하단 zone overflow 감지
|
||||
- overflow 시 Kei에게 요약 요청 → 줄어든 콘텐츠로 재조립
|
||||
- 결과 스크린샷에서 overflow 없음
|
||||
|
||||
---
|
||||
|
||||
## XBX-3: 하단 구조 — 중제목 별도 행
|
||||
|
||||
### 현상
|
||||
"DX 기반 Process 혁신에 따른 주체별 기대효과"가 별도 행 → 공간 낭비.
|
||||
|
||||
```
|
||||
현재: 목표:
|
||||
┌──────────────────────────────┐ ┌─────────────┬──────────────┐
|
||||
│ DX 기반 Process 혁신에 따른...│ ← 별도행 │ 2.1 업무 수행│ 2.2 DX 시행 │
|
||||
├──────────────┬───────────────┤ │ 과정의 변화 │ 주체별 기대효과│
|
||||
│ 2.1 업무 수행 │ 2.2 DX 시행 │ │ (불릿) │ (표) │
|
||||
│ 과정의 변화 │ 주체별 기대효과│ └─────────────┴──────────────┘
|
||||
└──────────────┴───────────────┘ 중제목은 2분할 상단에 작게 표시
|
||||
```
|
||||
|
||||
### 수행 방향
|
||||
|
||||
`scripts/assemble_stage2.py` `_assemble_type_b` + `src/block_assembler.py` `_assemble_slide_html_type_b`:
|
||||
- 하단 대목차(level=2)를 별도 행으로 배치하지 않음
|
||||
- 2분할 각 칸의 상단에 작은 라벨로 표시하거나, 2분할 위에 한 줄 라벨로 통합
|
||||
- 절약된 높이를 2분할 콘텐츠에 할당
|
||||
|
||||
### 검증
|
||||
하단 영역 전체가 2분할 콘텐츠로 사용됨. 중제목이 별도 행을 차지하지 않음.
|
||||
|
||||
---
|
||||
|
||||
## XBX-4: 하단 좌/우 높이 불균형
|
||||
|
||||
### 현상
|
||||
02번 컨테이너:
|
||||
- bottom_left (업무 프로세스 변화): **124px**
|
||||
- bottom_right (주체별 기대효과): **321px**
|
||||
|
||||
높이가 2.5배 차이.
|
||||
|
||||
### 수행 방향
|
||||
|
||||
`src/space_allocator.py`의 `build_containers_type_b` (544-556줄):
|
||||
- 현재 코드에서 `height_px=bottom_h`로 동일하게 주고 있음
|
||||
- **문제는 Kei가 준 weight가 다른 것** → weight에 의해 top/bottom 비율이 달라지고,
|
||||
그 결과 bottom_h 자체가 줄어드는 구조인지 추적 필요
|
||||
- 하단 좌/우는 무조건 **동일 높이**(`bottom_h`)로 고정
|
||||
|
||||
### 검증
|
||||
하단 좌/우 컨테이너가 동일 높이로 나옴.
|
||||
|
||||
---
|
||||
|
||||
## XBX-5: before→filled→after 파이프라인 연결
|
||||
|
||||
### 현상
|
||||
Type B의 filled HTML이 2,742 bytes (거의 빈 HTML). Type A는 214KB.
|
||||
|
||||
### 원인
|
||||
하류 코드가 `containers["배경"]`, `containers["본심"]` 처럼 **Type A 역할명 글자**로 매칭.
|
||||
Type B의 역할명(`"필수요건"`, `"과정혁신"` 등)은 이 키에 없어서 빈 것.
|
||||
|
||||
### 수행 방향
|
||||
|
||||
**원칙: Type A 코드 그대로 두고, `if layout_template == "B":` 분기만 추가.**
|
||||
|
||||
#### 5-1. `src/step_visualizer.py` (9곳+)
|
||||
|
||||
현재:
|
||||
```python
|
||||
COLORS = {"배경": "#dc2626", "본심": "#2563eb", "첨부": "#16a34a", "결론": "#7c3aed"}
|
||||
for role in ["배경", "본심", "첨부", "결론"]:
|
||||
container = containers[role]
|
||||
```
|
||||
|
||||
수정: Type B 분기 추가. 기존 Type A 코드는 **한 글자도 안 건드림.**
|
||||
```python
|
||||
if layout_template == "B":
|
||||
for role in containers:
|
||||
zone = containers[role].zone
|
||||
color = ZONE_COLORS.get(zone, "#333") # zone 기반 색상
|
||||
# ... Type B 시각화
|
||||
else:
|
||||
# 기존 Type A 코드 그대로
|
||||
for role in ["배경", "본심", "첨부", "결론"]:
|
||||
...
|
||||
```
|
||||
|
||||
수정 대상 함수 (9곳):
|
||||
- `_gen_stage_1_5a` (271줄)
|
||||
- `_gen_stage_1_5a_content` (297줄)
|
||||
- `_gen_stage_1_5b` (334줄)
|
||||
- `_gen_stage_1_7` (371줄)
|
||||
- `_gen_stage_1_8_fit_before` (419줄)
|
||||
- `_gen_stage_1_8_fit_after` (465줄)
|
||||
- `_gen_stage_1_8_blocks` (534줄)
|
||||
- `_gen_stage_2` (650줄, 683줄)
|
||||
|
||||
#### 5-2. `src/fit_verifier.py`
|
||||
|
||||
- `ROLE_ZONE_MAP` (488-493줄) — 이미 부분 수정됨. containers에 zone 있으면 그걸 사용.
|
||||
- `role_font_map` (307줄) `{"본심": "core", ...}` — Type B 분기 추가:
|
||||
zone 기반 매핑 (`"top" → "core"`, `"bottom_left" → "core"` 등)
|
||||
- `role_line_height` (308줄) — 동일하게 분기
|
||||
- **Type A 코드 안 건드림.** `ROLE_ZONE_MAP`, `role_font_map`은 그대로 두고 fallback으로만 사용.
|
||||
|
||||
#### 5-3. `src/renderer.py`
|
||||
|
||||
- `_find_h` fallback 이미 추가됨. Type A는 `_find_h("배경")` 그대로 동작.
|
||||
- Type B에서 `body_row_h` 계산이 맞는지 확인 필요 — Type B는 body_row가 없고 top+bottom 구조.
|
||||
- 필요시 `if layout_template == "B":` 분기 추가.
|
||||
|
||||
#### 5-4. `src/slide_measurer.py`
|
||||
|
||||
- CSS 클래스 `area-*`로 zone 탐색 → **역할명 하드코딩 없음. 수정 불필요.**
|
||||
- `_assemble_slide_html_type_b`가 `area-top`, `area-bottom`, `area-footer` 클래스를 생성하므로
|
||||
Selenium 측정이 그대로 동작.
|
||||
|
||||
### 검증
|
||||
02번 MDX로 파이프라인 실행 → filled HTML이 10KB+ → Selenium 측정 정상 → after HTML 생성.
|
||||
|
||||
---
|
||||
|
||||
## XBX-6: Sonnet HTML 재구성 프로세스 분리
|
||||
|
||||
### 현상
|
||||
Stage 2(`src/pipeline.py` 901-957줄)에서 Sonnet(`generate_with_retry`)이 HTML 재구성.
|
||||
Type B에서는 품질 불안정.
|
||||
|
||||
### 수행 방향
|
||||
|
||||
`src/pipeline.py` stage_2 함수에 Type B 분기 추가:
|
||||
```python
|
||||
async def stage_2(context: PipelineContext) -> dict:
|
||||
if context.analysis.layout_template == "B":
|
||||
# Type B: code_assembled 결과를 직접 사용, Sonnet 재구성 스킵
|
||||
from src.block_assembler import assemble_slide_html
|
||||
generated = assemble_slide_html(context)
|
||||
return {"generated_html": generated}
|
||||
|
||||
# Type A: 기존 Sonnet 재구성 코드 그대로
|
||||
from src.content_verifier import generate_with_retry
|
||||
...
|
||||
```
|
||||
|
||||
- Sonnet 코드 삭제하지 않음
|
||||
- Type B일 때만 스킵
|
||||
- code_assembled HTML은 `assemble_slide_html(context)`로 생성 (이미 동작 확인됨)
|
||||
|
||||
### 검증
|
||||
- Type A: 기존대로 Sonnet 재구성 → 결과 동일
|
||||
- Type B: code_assembled 직접 사용 → 결과가 스크린샷에서 정상
|
||||
|
||||
---
|
||||
|
||||
## 작업 순서
|
||||
|
||||
**02번 먼저 완성 → 03번 확장:**
|
||||
|
||||
1. **XBX-1** (들여쓰기) — normalizer depth 보존 + 조립 로직 분기
|
||||
2. **XBX-3** (하단 구조) — 중제목 별도행 → 라벨로 통합
|
||||
3. **XBX-4** (하단 높이) — 좌/우 균등 확인 + 수정
|
||||
4. **XBX-5** (파이프라인 연결) — step_visualizer/fit_verifier/renderer Type B 분기
|
||||
5. **XBX-2** (overflow) — 파이프라인 연결 후 Selenium으로 자동 확인
|
||||
6. **XBX-6** (Sonnet 분리) — pipeline.py Type B 분기
|
||||
7. **03번 확장** — 03번 MDX에서도 동작 확인 (표 보존, 3단 계층 등)
|
||||
|
||||
---
|
||||
|
||||
## 02번 run 정보
|
||||
|
||||
- 최신 run: `data/runs/20260406_121405`
|
||||
- 스크린샷: `steps/code_assembled_02_2x.png`
|
||||
- containers: top=255px(w=847px), bottom_left=124px, bottom_right=321px, footer=83px
|
||||
|
||||
---
|
||||
|
||||
## 핵심 원칙
|
||||
|
||||
- 하드코딩 절대 금지
|
||||
- HTML 결과물 고치지 말고 파이프라인 프로세스 고칠 것
|
||||
- 제목/텍스트는 원본 MDX에서 그대로
|
||||
- **유형 A 코드 절대 건드리지 않음** — A는 완벽하게 동작 중. 수정도 재검증도 하지 않음.
|
||||
- Type B 코드는 기존 코드에 분기(`if layout_template == "B"`) 추가로만 구현
|
||||
- 검증은 반드시 렌더링(스크린샷)으로
|
||||
@@ -0,0 +1,142 @@
|
||||
# Phase X-C: 서브존 프리셋 기반 범용 레이아웃
|
||||
|
||||
> 최종 업데이트: 2026-04-07
|
||||
> 전제: Type A, Type B 건드리지 않음. Type C로 새 접근.
|
||||
> 의존: Phase X-BX' 완료 후 시작 (zone 기반 코드 전환 완료 필요)
|
||||
|
||||
---
|
||||
|
||||
## 핵심 아이디어
|
||||
|
||||
**AI는 "고르는 것"에만 쓰고, "만드는 것"은 코드가 한다.**
|
||||
|
||||
### 고정 구조
|
||||
|
||||
모든 슬라이드는 3개 zone:
|
||||
```
|
||||
┌───────────────────────────┐
|
||||
│ header (제목) │ ← 고정
|
||||
├───────────────────────────┤
|
||||
│ body (본문) │ ← 서브존 프리셋 적용
|
||||
├───────────────────────────┤
|
||||
│ footer (핵심 요약) │ ← 고정
|
||||
└───────────────────────────┘
|
||||
```
|
||||
|
||||
### 서브존 프리셋
|
||||
|
||||
body 안의 배치를 row 조합으로 정의:
|
||||
|
||||
```
|
||||
row 유형:
|
||||
F = 전체폭 (1칸)
|
||||
H = 2분할
|
||||
T = 3분할
|
||||
Q = 4분할
|
||||
|
||||
프리셋 예시:
|
||||
S1: F → 1단 전체폭
|
||||
S2: H → 2단 (좌/우)
|
||||
S3: T → 3단 균등
|
||||
S4: F+H → 상단 전체폭 + 하단 2분할 (현재 Type B에 해당)
|
||||
S5: H+F → 상단 2분할 + 하단 전체폭
|
||||
S6: H+H → 상하 각 2분할 (2x2)
|
||||
S7: F+T → 상단 전체폭 + 하단 3분할
|
||||
S8: T+F → 상단 3분할 + 하단 전체폭
|
||||
S9: F+F → 전체폭 2단 (현재 Type A에 가까움)
|
||||
```
|
||||
|
||||
### Kei 역할 (최소화)
|
||||
|
||||
1. **꼭지 추출** — 콘텐츠를 몇 개 덩어리로 나눌지
|
||||
2. **꼭지 간 관계** — 비교/나열/종속/독립
|
||||
3. **프리셋 선택** — 번호로 고르기 (또는 코드가 자동 매핑)
|
||||
|
||||
### 코드 역할
|
||||
|
||||
1. 프리셋 → zone/sub-zone px 계산 (사칙연산)
|
||||
2. 텍스트량 기반 비율 계산
|
||||
3. before→filled→after 파이프라인 (Selenium 측정 → 재배분)
|
||||
4. MDX 원본 텍스트 배치
|
||||
5. 블록 선택 (프리셋별 적합 블록 매핑)
|
||||
|
||||
---
|
||||
|
||||
## 질문: Kei가 프리셋을 고를 수 있는가?
|
||||
|
||||
### 자동 매핑 (코드가 결정) — 안정적이지만 제한적
|
||||
```python
|
||||
if len(topics) == 1: preset = "S1" # 1단
|
||||
if len(topics) == 2: preset = "S2" # 2분할
|
||||
if len(topics) == 3: preset = "S3" # 3단
|
||||
if len(topics) == 4: preset = "S6" # 2x2
|
||||
```
|
||||
→ 꼭지 관계 무시. 비교 3개 + 정리 1개 같은 경우 대응 못 함.
|
||||
|
||||
### Kei 선택 — 유연하지만 불안정 위험
|
||||
```json
|
||||
{"topics": [...], "preset": "S4", "reason": "상단 3개 비교 + 하단 종합"}
|
||||
```
|
||||
→ 블록 선택도 불안한데 프리셋 선택이 안정적일지 미지수.
|
||||
|
||||
### 하이브리드 (유력) — 코드가 후보 제시, Kei가 선택
|
||||
```
|
||||
코드: "꼭지 4개이므로 후보: S4, S6, S7"
|
||||
Kei: "비교 관계이므로 S4"
|
||||
```
|
||||
→ 선택지를 3개 이하로 좁히면 Kei가 잘 고를 가능성 높음.
|
||||
|
||||
---
|
||||
|
||||
## 기술적 구현 방향
|
||||
|
||||
### sub-zone px 계산
|
||||
|
||||
```python
|
||||
def calculate_sub_zones(preset: str, body_width: int, body_height: int, gap: int):
|
||||
rows = parse_preset(preset) # "F+H" → [F, H]
|
||||
row_count = len(rows)
|
||||
row_height = (body_height - gap * (row_count - 1)) // row_count
|
||||
|
||||
zones = []
|
||||
for i, row_type in enumerate(rows):
|
||||
col_count = {"F": 1, "H": 2, "T": 3, "Q": 4}[row_type]
|
||||
col_width = (body_width - gap * (col_count - 1)) // col_count
|
||||
for j in range(col_count):
|
||||
zones.append({
|
||||
"row": i, "col": j,
|
||||
"width": col_width, "height": row_height,
|
||||
})
|
||||
return zones
|
||||
```
|
||||
|
||||
### 비율 조정
|
||||
```python
|
||||
# 텍스트량 기반 비율
|
||||
text_lengths = [len(topic.text) for topic in row_topics]
|
||||
total = sum(text_lengths)
|
||||
ratios = [l / total for l in text_lengths]
|
||||
# 최소 20%, 최대 60% 제한
|
||||
ratios = [max(0.2, min(0.6, r)) for r in ratios]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 단계별 진행 계획
|
||||
|
||||
1. **X-C-1: 프리셋 정의** — S1~S9 구조 + px 계산 함수
|
||||
2. **X-C-2: 자동 매핑 먼저** — 꼭지 수 → 프리셋 (코드만으로)
|
||||
3. **X-C-3: 조립 범용화** — zone 기반으로 어떤 프리셋이든 조립
|
||||
4. **X-C-4: Kei 선택 실험** — 하이브리드 방식 테스트
|
||||
5. **X-C-5: before→filled→after 연결** — X-BX'의 zone 기반 코드 활용
|
||||
6. **X-C-6: 검증** — 01/02/03번 + 새 MDX로 범용성 테스트
|
||||
|
||||
---
|
||||
|
||||
## Type A/B와의 관계
|
||||
|
||||
- Type A, B는 기존 코드 그대로 유지
|
||||
- Type C는 별도 경로로 동작
|
||||
- 추후 Type C가 안정화되면 A/B를 C의 프리셋으로 흡수 가능:
|
||||
- Type A ≈ S9 (F+F) + sidebar
|
||||
- Type B ≈ S4 (F+H)
|
||||
@@ -0,0 +1,111 @@
|
||||
# Phase X': 유형 B 파이프라인 개선
|
||||
|
||||
> 최종 업데이트: 2026-04-06
|
||||
> 전제: 유형 A 건드리지 않음. 유형 B 파이프라인 프로세스 수정.
|
||||
|
||||
---
|
||||
|
||||
## 현재 상태
|
||||
|
||||
- 유형 A (배경+본심+첨부+결론): ✅ 동작 (01번 MDX)
|
||||
- 유형 B (상단+하단2분할+결론): **code_assembled만 동작, 파이프라인(before→filled→after) 미연결**
|
||||
|
||||
## 완료된 것
|
||||
|
||||
### X'-1: 제목 원본 MDX에서 가져오기 ✅
|
||||
- `context.normalized.title` 사용 (Kei title 대신)
|
||||
- 파일: `src/pipeline.py` Stage 1A
|
||||
|
||||
### X'-2: 들여쓰기 계층 ✅ (code_assembled에서만)
|
||||
- `###` 소제목 → 카드형 분리
|
||||
- 본문 불릿 indent 적용
|
||||
- 파일: `scripts/assemble_stage2.py`
|
||||
|
||||
### X'-3: 이미지 캡션 ✅
|
||||
- `normalized.images` alt text에서 추출
|
||||
- 파일: `scripts/assemble_stage2.py`
|
||||
|
||||
### X'-4: 상단 균등배분 ✅
|
||||
- `justify-content:space-between`
|
||||
- 파일: `scripts/assemble_stage2.py`
|
||||
|
||||
### X'-5: 카드 디자인 ✅
|
||||
- 다크 그라데이션 + 밝은 텍스트
|
||||
- 파일: `scripts/assemble_stage2.py`
|
||||
|
||||
### X'-6: 표 요약 ✅ (code_assembled에서만)
|
||||
- `normalized.tables` → pipeline V'-2에서 Kei 요약 → context 저장
|
||||
- `_assemble_type_b` 하단 우측에 표출
|
||||
- 파일: `src/pipeline.py`, `scripts/assemble_stage2.py`
|
||||
|
||||
### MDX sections 계층 ✅
|
||||
- `mdx_normalizer`: `###` (h3) 소목차도 section으로 분리
|
||||
- `_assemble_type_b`: `normalized.sections`에서 직접 텍스트 가져오기
|
||||
- 대목차/소목차 계층 반영
|
||||
|
||||
---
|
||||
|
||||
## 핵심 미해결 문제
|
||||
|
||||
### 유형 B의 before→filled→after 파이프라인이 연결 안 됨
|
||||
|
||||
**증거:**
|
||||
- FILLED: 2997bytes, 한글 80자 (유형 A는 214KB)
|
||||
- `block_assembler.assemble_slide_html()`이 고정 4역할(배경/본심/첨부/결론)만 처리
|
||||
- 유형 B의 자유 역할명(DX_궁극적_목표, 프로세스_변화 등)을 처리 못 함
|
||||
- 결과: filled/after가 거의 빈 HTML
|
||||
|
||||
**해결 방향:**
|
||||
- `block_assembler.assemble_slide_html()`이 유형 B 역할도 처리하도록
|
||||
- 또는 유형 B 전용 filled/after 함수 추가
|
||||
- `_assemble_type_b`(assemble_stage2)는 code_assembled 전용이므로, 파이프라인의 filled/after에는 별도 로직 필요
|
||||
|
||||
### 렌더링에서 잘림
|
||||
|
||||
**증거:**
|
||||
- code_assembled에 모든 내용이 HTML로 있지만 브라우저에서 보면 잘림
|
||||
- overflow:hidden + 컨테이너 크기 < 내용 크기
|
||||
- 상단 카드가 잘림, 결론이 안 보임
|
||||
|
||||
**해결 방향:**
|
||||
- 컨테이너 크기 계산에서 내용 크기를 고려
|
||||
- 또는 Selenium 측정 후 재배분 (이건 filled→after 파이프라인이 동작해야 가능)
|
||||
|
||||
---
|
||||
|
||||
## Kei가 하는 일 (명확히 정리)
|
||||
|
||||
1. **꼭지 찾기 + 그루핑** — MDX 구조 분석
|
||||
2. **유형 선택 (A/B)** — 콘텐츠에 맞는 레이아웃
|
||||
3. **블록 선택** — 컨테이너에 맞는 블록 타입
|
||||
4. **공란에 표/팝업 요약** — 원문 최대 유지
|
||||
5. **bold 키워드 판단** — 문맥 기반
|
||||
|
||||
**나머지는 전부 MDX 원본에서 가져옴:**
|
||||
- 제목, 대목차, 중목차, 소목차, 텍스트 — 원본 그대로
|
||||
- 핵심 요약 — 원본 그대로
|
||||
- Kei가 텍스트를 재작성하지 않음
|
||||
|
||||
---
|
||||
|
||||
## 다음 세션 작업 순서
|
||||
|
||||
1. **유형 B filled/after 파이프라인 연결** — block_assembler 또는 별도 함수
|
||||
2. **컨테이너 크기 vs 내용 크기 맞춤** — Selenium 측정 기반 재배분
|
||||
3. **렌더링 잘림 해결** — overflow 처리
|
||||
4. **01번(유형 A) 깨지지 않는지 확인**
|
||||
|
||||
---
|
||||
|
||||
## 관련 파일
|
||||
|
||||
| 파일 | 역할 | 유형 B 상태 |
|
||||
|------|------|------------|
|
||||
| `src/kei_client.py` | KEI_PROMPT (유형 A/B 선택) | ✅ |
|
||||
| `src/validators.py` | 검증기 (유형 B 완화) | ✅ |
|
||||
| `src/space_allocator.py` | 컨테이너 생성 (build_containers_type_b) | ✅ |
|
||||
| `src/pipeline.py` | 파이프라인 분기 (layout_template) | ✅ 분기만 |
|
||||
| `src/pipeline_context.py` | Analysis.layout_template | ✅ |
|
||||
| `src/mdx_normalizer.py` | ### 소목차 section 분리 | ✅ |
|
||||
| `scripts/assemble_stage2.py` | _assemble_type_b (code_assembled) | ✅ |
|
||||
| `src/block_assembler.py` | assemble_slide_html (filled/after) | ❌ 유형 B 미지원 |
|
||||
@@ -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>
|
||||
@@ -0,0 +1,29 @@
|
||||
# Legacy / 임시 파일
|
||||
block_index.faiss
|
||||
block_metadata.json
|
||||
figma_beps_full.json
|
||||
figma_center_elements.json
|
||||
figma_center_full.json
|
||||
figma_frames.json
|
||||
figma-analysis/
|
||||
figma-assets/
|
||||
figma-screenshots/
|
||||
figma_ref/
|
||||
previews/
|
||||
FIGMA-COMPONENT-EXTRACTION-PLAN.md
|
||||
FIGMA-CONVERSION-REVIEW.md
|
||||
FIGMA-DESIGN-LANGUAGE.md
|
||||
FIGMA-EXTRACTION.md
|
||||
INSIGHT-GRADIENT.md
|
||||
PHASE-FIGMA-BLOCKS.md
|
||||
PLAN.md
|
||||
RESEARCH.md
|
||||
|
||||
# Selenium 렌더 결과 / 비교 스크린샷
|
||||
block-tests/_renders/
|
||||
block-tests/*.png
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
scripts/__pycache__/
|
||||
@@ -0,0 +1,131 @@
|
||||
# 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. **프로모션은 사용자 승인 후 진행** — 1:1 HTML 검증 완료 후, 사용자 승인을 받아 `templates/blocks/new/`에 블록을 생성하고 `templates/blocks/svg/`에 이미지를 정리한다. catalog.yaml 등록과 blocks_index.md 업데이트까지 에이전트가 수행한다. 단, 사용자 승인 없이 프로모션하지 않는다.
|
||||
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` — 1:1 변환 결과
|
||||
- `block-tests/{slug}_flat.md` — 플래튼/이상 탐지/변형 축 메모
|
||||
- `assets/shared/...` — 공유 자산 캐시
|
||||
- `blocks_index.md` 한 줄 추가
|
||||
|
||||
**출력 (프로모션, 사용자 승인 후):**
|
||||
- `templates/blocks/new/{pattern}.html` — AI가 재디자인 가능한 블록 (CSS + Jinja2)
|
||||
- `templates/blocks/svg/{name}.png/svg` — 블록 공용 이미지
|
||||
- `templates/catalog.yaml` — 블록 등록 (content_structure, when, not_for)
|
||||
- `blocks_index.md` 상태 → promoted
|
||||
|
||||
## 폴더 구조
|
||||
|
||||
```
|
||||
figma_to_html_agent/ ← 에이전트 작업 영역 (staging)
|
||||
├── CLAUDE.md ← 이 파일 (에이전트 명세)
|
||||
├── PROCESS.md ← 10단계 운영 절차 (변환 핸드북)
|
||||
├── MATH.md ← 수학 공식 레퍼런스
|
||||
├── RULES.md ← CSS 보정 규칙 (R1~R16)
|
||||
├── PROCESS-CONTROL.md ← "찍어맞추기 금지" 규칙
|
||||
├── README.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 추상화 (legacy, 현재 미사용)
|
||||
|
||||
────────────────────────────────────────────────────────
|
||||
프로모션 (사용자 승인 후 에이전트가 실행)
|
||||
────────────────────────────────────────────────────────
|
||||
|
||||
design_agent/templates/
|
||||
├── blocks/
|
||||
│ ├── new/ ← Figma 추출 블록 (에이전트가 작성)
|
||||
│ ├── svg/ ← 블록 공용 이미지 (에이전트가 정리)
|
||||
│ ├── slide-base.html ← 고정 슬라이드 배경
|
||||
│ └── cards/, emphasis/, ... ← 기존 블록
|
||||
└── catalog.yaml ← 블록 등록 (에이전트가 업데이트)
|
||||
```
|
||||
|
||||
**중요:** 프로모션(블록 생성, 이미지 정리, catalog 등록)은 사용자가 1:1 HTML을 검증하고 승인한 후에만 진행한다. 사용자 승인 없이 templates/ 를 수정하지 않는다.
|
||||
|
||||
## 금지 사항
|
||||
|
||||
- 시행착오 px 조정 (1씩 늘려보기 등)
|
||||
- 사용자에게 "맞나요?" 반복 질문 (스스로 검증)
|
||||
- line-height 등 CSS 속성을 감으로 보정 (폰트 메트릭에서 수학적 도출)
|
||||
- 흰 텍스트 스트로크 (`-webkit-text-stroke: white`) 사용
|
||||
- 블록 배경을 검정으로 표시 (미리보기는 항상 흰색 배경)
|
||||
- **이미지 해석으로 gradient 방향 판단** (멀티모달 금지, 데이터로만 판단 — PROCESS-CONTROL.md 참조)
|
||||
- **한 번에 여러 값 동시 수정** (gradient 각도와 border-radius 동시 변경 금지)
|
||||
- **장식 요소를 이미지 슬롯(img src)으로 넣기** — gradient bar, ribbon, pill, badge, 오버레이 등은 CSS로 구현. AI가 색상/크기를 조정할 수 없는 이미지 의존 블록은 쓸 수 없다
|
||||
- **사용자 승인 없이 templates/ 수정** — 프로모션은 사용자 승인 후에만
|
||||
- **여러 프레임을 한 세션에 변환** (1세션 1프레임 원칙)
|
||||
- **plus-darker 블렌드 사용** (Safari 전용 → multiply로 교체, RULES.md R10)
|
||||
- **Figma 인벤토리/지문/군집 같은 사전 분류** (work-creating-work, 패턴은 bottom-up으로 발견)
|
||||
- **`design_agent/templates/` 직접 수정** (프로모션 게이트는 사용자 전용. 에이전트는 staging까지만)
|
||||
- **사용자에게 "templates/ 에 옮겨드릴까요?" 같은 제안** (월권. 사용자가 알아서 함)
|
||||
@@ -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` | **이 문서**. 테스트 결과, 인사이트, 다음 세션 이어갈 지점 | 신규 |
|
||||
@@ -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 순환 궤도 |
|
||||
@@ -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 등록
|
||||
@@ -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를 각각 수동 계산하지 않는다. **래퍼를 회전**시킨다.
|
||||
- 작동하는 값은 건드리지 않는다. 사용자가 지적한 것만 수정한다.
|
||||
@@ -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`로 교체했는가
|
||||
@@ -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을 같은 폭으로 위/아래 비교.
|
||||
@@ -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 블록 선택 단계와 자동 연결
|
||||
↓
|
||||
사용자 콘텐츠 → 존 크기 → 패턴 매칭 → 블록 선택 → 슬롯 채우기 → 렌더
|
||||
```
|
||||
@@ -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**
|
||||
@@ -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 표시됨 |
|
||||
@@ -0,0 +1,165 @@
|
||||
# Figma → HTML Agent
|
||||
|
||||
Figma 디자인 프레임을 **수학적으로 정확하게** HTML/CSS로 변환하고, design_agent의 **블록 라이브러리**로 축적하는 독립 에이전트.
|
||||
|
||||
---
|
||||
|
||||
## 이 에이전트는 뭘 하는가?
|
||||
|
||||
1. Figma 파일에서 프레임을 선택하면
|
||||
2. MCP(Figma Dev Mode)로 구조/스타일/스크린샷을 가져와서
|
||||
3. 수학적 계산(scale, gradient 변환, shadow 등)으로 1:1 HTML/CSS를 만들고
|
||||
4. AI가 재디자인할 수 있는 블록(순수 CSS + Jinja2 슬롯)으로 변환하여
|
||||
5. design_agent의 블록 라이브러리(`templates/blocks/`)에 편입한다
|
||||
|
||||
**핵심:** AI redesigner가 색상/크기/구조를 조정할 수 있도록, 장식 요소는 **CSS로 구현**하고 이미지는 crop/곡선/일러스트/사진만 남긴다.
|
||||
|
||||
---
|
||||
|
||||
## 폴더 구조
|
||||
|
||||
```
|
||||
figma_to_html_agent/
|
||||
├── README.md ← 이 파일 (사용법)
|
||||
├── CLAUDE.md ← 에이전트 명세 (11개 원칙)
|
||||
├── PROCESS.md ← 10단계 변환 절차 핸드북
|
||||
├── MATH.md ← 수학 공식 (§1~9)
|
||||
├── RULES.md ← CSS 보정 규칙 (R1~R16)
|
||||
├── PROCESS-CONTROL.md ← 작업 규율 (찍어맞추기 금지 등)
|
||||
├── blocks_index.md ← 변환 완료 도서관 + 디자인 인사이트
|
||||
│
|
||||
├── scripts/
|
||||
│ └── gradient_math.py ← SVG → CSS gradient 수학 변환 도구
|
||||
│
|
||||
├── block-tests/ ← 1:1 HTML 변환물 (원본 검증용)
|
||||
│ ├── {slug}.html ← Figma 원본과 100% 동일한 정적 HTML
|
||||
│ ├── {slug}_flat.md ← 플래튼/이상탐지/변형축 메모
|
||||
│ └── assets/ ← 이미지 에셋
|
||||
│ ├── shared/ ← 해시 기반 공유 캐시
|
||||
│ └── frame_{id}/ ← 프레임 전용 (legacy)
|
||||
│
|
||||
└── templates_staging/ ← Jinja2 템플릿 초안 (legacy, 현재 미사용)
|
||||
```
|
||||
|
||||
### 산출물 최종 위치 (design_agent 본체)
|
||||
|
||||
```
|
||||
design_agent/
|
||||
└── templates/
|
||||
└── blocks/
|
||||
├── new/ ← Figma 추출 블록 (8개)
|
||||
├── svg/ ← 블록 공용 이미지 (33개)
|
||||
├── slide-base.html ← 고정 슬라이드 배경 (16:9)
|
||||
└── cards/, emphasis/, headers/, media/, visuals/, BEPs/, redesign/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 사용법
|
||||
|
||||
### 사전 준비
|
||||
|
||||
1. **Figma Desktop** 앱에서 대상 파일을 열고 **Dev Mode** 활성화
|
||||
2. `.mcp.json`에 Figma Desktop SSE 서버 등록 확인:
|
||||
```json
|
||||
{ "url": "http://127.0.0.1:3845/sse" }
|
||||
```
|
||||
3. Claude Code 세션 시작
|
||||
|
||||
### 변환 실행 (10단계)
|
||||
|
||||
```
|
||||
STEP 0 에이전트가 blocks_index.md 읽기 → 기존 패턴 확인
|
||||
STEP 1 get_metadata — 구조 + bbox (XML)
|
||||
STEP 2 get_design_context — gradient/filter/font (React+Tailwind)
|
||||
STEP 3 get_screenshot — Figma 원본 PNG (검증용)
|
||||
STEP 4 자산 다운로드 — block-tests/assets/shared/ 캐시
|
||||
STEP 5 flat.md 작성 — bottom-up 플래튼 + 이상탐지 + 변형축
|
||||
STEP 6 gradient 수학 변환 — scripts/gradient_math.py
|
||||
STEP 7 HTML 작성 — 순수 CSS 우선, transform: scale() 균일 축소
|
||||
STEP 8 검증 — Figma 스크린샷과 비교
|
||||
STEP 9 저장 — block-tests/{slug}.html + flat.md
|
||||
STEP 10 인덱스 업데이트 — blocks_index.md 1줄 추가
|
||||
```
|
||||
|
||||
**상세:** [PROCESS.md](PROCESS.md) 참조
|
||||
|
||||
### 블록 프로모션 (block-tests → templates/blocks/)
|
||||
|
||||
1:1 HTML이 검증되면:
|
||||
|
||||
1. **CSS 전환** — SVG/PNG 장식 요소를 CSS gradient/border-radius/shadow로 변환
|
||||
- CSS 전환 대상: gradient bar, ribbon, pill, badge, 오버레이 등
|
||||
- 이미지 유지 대상: crop/프레임 배치(R16), 곡선 아크, 아이콘, 일러스트, 실사 사진
|
||||
2. **블록 작성** — `templates/blocks/new/{pattern}.html`
|
||||
- flex layout, width: 100%, Jinja2 슬롯
|
||||
- 수학적 계산 주석 (Figma px → scale → CSS 값)
|
||||
- 이미지는 `svg/` 폴더에서 참조 (`{{ slot | default('svg/파일명') }}`)
|
||||
3. **이미지 정리** — `templates/blocks/svg/`에 의미 있는 이름으로 저장
|
||||
4. **catalog.yaml 등록** — content_structure, when, not_for 작성
|
||||
5. **blocks_index.md 상태** — `staged` → `promoted` 업데이트
|
||||
|
||||
---
|
||||
|
||||
## 핵심 원칙 요약
|
||||
|
||||
| # | 원칙 | 설명 |
|
||||
|---|------|------|
|
||||
| 1 | 수학적 계산만 | Figma 좌표 → scale → CSS. 시행착오 px 조정 금지 |
|
||||
| 2 | Bottom-up | leaf 노드부터 → 2개씩 묶기 → 계층 쌓기 |
|
||||
| 3 | 순수 CSS 우선 | AI가 색상/크기 조정할 수 있게. SVG는 곡선/필터만 |
|
||||
| 4 | 이미지 해석 금지 | gradient 방향은 데이터로. "보니까 ~인 것 같다" 금지 |
|
||||
| 5 | 하드코딩 금지 | 결과물을 고치지 말고 프로세스를 고친다 |
|
||||
| 6 | 전면 재작성 금지 | 80점에서 2개 고칠 때 구조를 갈아엎지 말 것 |
|
||||
| 7 | 모든 슬롯 optional | 블록이 다양한 MDX에 매칭될 수 있게 |
|
||||
|
||||
**상세:** [CLAUDE.md](CLAUDE.md), [PROCESS-CONTROL.md](PROCESS-CONTROL.md), [RULES.md](RULES.md)
|
||||
|
||||
---
|
||||
|
||||
## 도구
|
||||
|
||||
| 도구 | 용도 |
|
||||
|------|------|
|
||||
| Figma MCP `get_metadata` | 프레임 구조 + 절대 좌표 (XML) |
|
||||
| Figma MCP `get_design_context` | gradient/filter/font (React+Tailwind 코드) |
|
||||
| Figma MCP `get_screenshot` | Figma 원본 PNG |
|
||||
| `scripts/gradient_math.py` | SVG linearGradient → CSS linear-gradient 수학 변환 |
|
||||
| Pillow | 이미지 픽셀 분석 (gradient 색상 추출, 이미지 해석 아님) |
|
||||
|
||||
---
|
||||
|
||||
## 수학 공식 요약
|
||||
|
||||
| 공식 | 용도 | 상세 |
|
||||
|------|------|------|
|
||||
| `S = 1280 / W_figma` | 균일 축소 scale | MATH.md §1 |
|
||||
| `CSS_angle = 90 - Figma_angle` | gradient 각도 변환 | MATH.md §2 |
|
||||
| SVG `<linearGradient>` → CSS | gradient stop % 변환 | gradient_math.py |
|
||||
| `plus-darker → multiply` | blend mode 호환 | RULES.md R10 |
|
||||
| bbox 비율 ≠ 텍스트 비율 → 회전 | rotation 감지 | RULES.md R11 |
|
||||
|
||||
---
|
||||
|
||||
## 현재 상태 (2026-04-13)
|
||||
|
||||
### 프로모션 완료 블록 (8개)
|
||||
|
||||
| 패턴 | 원본 프레임 | 블록 위치 |
|
||||
|------|-----------|---------|
|
||||
| statement-pill-highlight | 1171281207 | blocks/new/ |
|
||||
| stacked-arrow-list | 1171281180 | blocks/new/ |
|
||||
| split-panel-numbered | 1171281202 | blocks/new/ |
|
||||
| issues-paired-rows | 1171281194 | blocks/new/ |
|
||||
| compare-vs-rows | 1171281195 | blocks/new/ |
|
||||
| quadrant-2x2-issues | 1171281193 | blocks/new/ |
|
||||
| cards-3col-persona | 1171281191 | blocks/new/ |
|
||||
| cycle-3way-intersect | 1171281211 | blocks/new/ |
|
||||
|
||||
### 공용 이미지 (templates/blocks/svg/, 33개)
|
||||
|
||||
화살표, 아크, 뱃지, 아이콘, 배경 텍스처, 사진 등. 블록에서 `svg/파일명`으로 참조.
|
||||
|
||||
### 고정 슬라이드 배경
|
||||
|
||||
`templates/blocks/slide-base.html` — 16:9 (1280×720), 상단 제목 + 구분선 + 본문 영역 + 하단 결론 pill.
|
||||
@@ -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개 | 첫 적용. 상/하 배치 반전 패턴 발견. |
|
||||
|
After Width: | Height: | Size: 269 KiB |
|
After Width: | Height: | Size: 229 KiB |
|
After Width: | Height: | Size: 352 KiB |
|
After Width: | Height: | Size: 229 KiB |
|
After Width: | Height: | Size: 676 KiB |