Author SHA1 Message Date
kmlee1 bfe9225967 On main: B4 role reframe + F29 expansion experiments — held during Option E (phase_z2_pipeline/classifier/placement_planner/content_extractor) 2026-05-07 11:14:49 +09:00
kmlee1 5210aea00f index on main: 761a43d Add Phase Z B4 source-shape-aware placement 2026-05-07 11:14:49 +09:00
kmlee1 761a43da5e Add Phase Z B4 source-shape-aware placement
- enable B1/B2/B4 source-shape-aware F13 placement behind env flag
- align F13 placement_trace with mapper top_bullets cardinality
- preserve canonical render output when flag is off
2026-05-07 05:26:57 +09:00
kmlee1 8a201337f7 Add Phase Z B4 gatekeeper feature flag
- gate V4/B4 mismatch zones via PHASE_Z_B4_GATEKEEPER env (default OFF)
- record mismatch as adapter_needed_units with reason and mismatch_detail
- preserve render path byte-identical when flag unset
2026-05-06 17:01:47 +09:00
kmlee1 aca6a992b9 Add Phase Z matching architecture anchor doc
- consolidate evidence-based principles for Step 5/6/7 and B-axis matching layer
- record CompositionUnit, candidate-based composition, and 4-tier terminology
- defer bridge architecture and slot_payload evolution as scope-locked open items
2026-05-06 15:46:52 +09:00
kmlee1 565e6b092e Add Phase Z classifier placement diagnostics
- consume debug_zones[i].placement_trace in classify_visual_runtime_check
- surface per-zone diagnostic in fit_classification.placement_diagnostics
- preserve canonical render SHA and existing classifier output schema
2026-05-04 17:40:21 +09:00
kmlee1 b6b9173d2b Update CLAUDE anchor for Phase Z Layer A telemetry
- align Phase Z hierarchy terms with Layer A and Frame Slot planning
- reframe Type A/B/B'/B'' as legacy layout hints
- record trace-only render path activation as the current anchor model
2026-05-04 15:22:24 +09:00
kmlee1 4320f5e838 Update Phase Z status board for Layer A runtime trace
- mark content object, internal region, and placement steps as partial
- record B1-B5 and trace-only runtime activation status
- refresh current missing items and bottleneck summary
2026-05-04 10:52:06 +09:00
kmlee1 425a3054c1 Add Phase Z placement trace telemetry
- run B1/B2/B4 placement planning in trace-only mode
- record placement_trace per rendered zone in debug output
- preserve existing render output and visual routing behavior
2026-05-04 10:32:26 +09:00
kmlee1 02a6d44944 Add Phase Z frame slot telemetry markers
- add frame slot markers to F29 runtime partial
- collect per-cell frame slot metrics in visual runtime check
- preserve existing visual status and failure routing behavior
2026-05-04 10:05:03 +09:00
kmlee1 3f843d73f7 Add Phase Z Layer A placement planner
- add dormant placement planner integrating B1 / B2 / B3
- region 1:1 sub_zone mapping with narrowest-accepts-first heuristic
- frame selection by accepted_content_types coverage + declaration order
2026-05-04 09:34:49 +09:00
kmlee1 4144dc4c21 Add Phase Z runtime templates
- add slide base template for Phase Z rendering
- add family partials for F13, F29, and F16 frames
2026-05-04 09:34:27 +09:00
kmlee1 2ec8fc5a77 Add Phase Z Layer A planning scaffold
- add Internal Region model to Phase Z architecture docs and specs
- add frame contract content type and Frame Slot declarations
- add dormant content object extractor and internal region planner
2026-05-04 08:21:50 +09:00
kmlee1 e7848b602d Add Phase Z runtime foundation
- add visual fit classifier, router, retry, and failure routing modules
- add composition planner and catalog-driven mapper
- add Phase Z pipeline orchestration and architecture docs
2026-05-04 08:21:28 +09:00
kmlee1andClaude Opus 4.6 79f0c55745 README: MDX 상태 수정 — 슬라이드 변환은 모두 완료, 파이프라인 자동화가 개선 대상
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:45:14 +09:00
kmlee1andClaude Opus 4.6 620da8de13 README 정리: 안 된 것 섹션 제거, MDX 01 추가, AS-IS/TO-BE 차이 명확화
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:14:56 +09:00
kmlee1andClaude Opus 4.6 b60a089e6c README를 프로젝트 설명 중심으로 재구성
- 무엇인가 → 구성/구현 → 문제점 → 개선 방향(AS-IS/TO-BE) → 현황 → 다음 단계
- 설계/계획 내용은 docs/architecture/ 문서로 분리 참조

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:08:02 +09:00
kmlee1andClaude Opus 4.6 a385ba08c0 README 업데이트: 토큰 체계, 폴더 구조, 설계 문서, 역할 분리 반영
- 토큰 기반 CSS 체계 섹션 추가 (2층 구조, 글자 위계, 블록 작성 규칙)
- 스타일 폴더 구조 명시 (tokens/themes/base/blocks)
- 실행 원칙 추가 (direct-fit, recipe-path, fit 루프)
- Source of Truth 명시
- 설계 문서 링크 (IMPROVEMENT-PLAN, TOKENS-v1, BLOCK-RULES)
- 역할 분리 (figma_to_html_agent / design_agent)
- 향후 개선 방향을 진행 중/다음 단계로 분리

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:01:24 +09:00
kmlee1andClaude Opus 4.6 8e577b3163 토큰 기반 CSS 체계 구축 + slide-base 스타일 분리
- tokens: typography(35변수), spacing(28변수), colors(41변수) 정의
- slide-base.html: 인라인 style 제거, Jinja include로 토큰/CSS 조립
- slide-base.css: 모든 직접값을 토큰 변수 참조로 전환 (직접값 0)
- block_assembler.py: Template → Environment.from_string (include 지원)
- TOKENS-v1.md: 위계 기준표 초안 + component token 후보
- BLOCK-RULES.md: 블록 작성 규칙 (spacing 문구 실제 토큰과 일치)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 13:56:03 +09:00
kmlee1andClaude Opus 4.6 7a2615fb4b 개선 설계 문서 추가: 토큰 기반 통일 + 블록 구조 부품화 + 2경로 파이프라인
- 목표: MDX → BEPs 매칭 → 슬라이드 자동 생성
- 핵심 축: 표현 기준 통일, 블록 역할 재정의, direct-fit/recipe 2경로화
- 6단계 진행 계획: 토큰 → slide-base 분리 → 규칙 문서 → 폴더 정리 → 블록 전환 → 파이프라인 연결
- 역할 분리: figma_to_html_agent(원재료) / design_agent(조립 시스템)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 12:02:51 +09:00
kmlee1andClaude Opus 4.6 46e53beeaf README 재정리: AS-IS / TO-BE 프로세스 구조 명시
- 현재 프로세스(AS-IS) 7단계 정리
- 개선 프로세스(TO-BE) 5단계 정리
- 핵심 차이: 꼭지 추출 시점, redesign 반복 조정, 빈 공간 재분배, 시각 검증
- 프로젝트 구조/산출물/향후 방향 포함

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 16:53:51 +09:00
kmlee1 1ff6c6cbb2 Add remaining samples, tooling, and local project assets 2026-04-15 18:02:17 +09:00
kmlee1 05d43a7999 Refine README and detail preview layout 2026-04-15 17:59:46 +09:00
kmlee1 66c00924ed Add Type B slide pipeline and recipe rendering updates 2026-04-15 16:39:50 +09:00
kmlee1andClaude Opus 4.6 51548fdc41 figma_to_html_agent 추가 + MCP/Claude 설정
figma_to_html_agent/:
- Figma MCP 기반 블록 추출 에이전트 (CLAUDE.md, PLAN.md, PROCESS.md 등)
- block-tests/: Figma→HTML 변환 결과물 (bim-3roles-cards 등)
- templates_staging/: Jinja2 템플릿 + meta.yaml + example.yaml
- figma-analysis/, figma-assets/: Figma 분석 데이터 + 에셋
- scripts/: gradient_math.py 등 유틸리티

설정:
- .mcp.json: Figma MCP 서버 연결 설정
- .claude/settings.json: Claude Code 프로젝트 설정

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 11:00:31 +09:00
kmlee1andClaude Opus 4.6 360cd8e44c 블록 템플릿 업데이트: 수정 3개 + 신규 17개 + catalog.yaml 재정비
수정:
- compare-2col-badge, compare-detail-gradient, hero-icon-cards 개선

신규 블록:
- cards: category-strip-table, system-2col-center, hero-icon-cards_1
- emphasis: checklist-dark
- visuals: cycle-orbit
- new/: cards-3col-persona, compare-vs-rows, cycle-3way-intersect 등 8개
- redesign/: card_hero-icon-cards_1
- svg/: arc_left, arrow_conclusion, arrow_down, line_divider
- BEPs: process-product-2col SVG + 테스트

catalog.yaml 전면 재정비 (슬롯 스키마, height_cost, 카테고리 정리)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:58:09 +09:00
kmlee1andClaude Opus 4.6 c42e01f060 문서 정리: Phase 히스토리 md를 docs/history/로 이동 + 오래된 테스트/에셋 정리
- 루트의 IMPROVEMENT-PHASE-*.md, PHASE-*.md 등 45개 → docs/history/로 이동
- docs/block-tests/ 오래된 블록 테스트 HTML 삭제 (figma_to_html_agent로 대체)
- docs/figma-analysis/, docs/figma-assets/, docs/figma-screenshots/ 정리
- docs/test-*.html 등 초기 테스트 파일 정리
- 참고 페이지/ 스크린샷 정리

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:56:23 +09:00
kmlee1andClaude Opus 4.6 d57860578f PIPELINE.md 신규 작성 + ARCHITECTURE_OVERVIEW.md deprecated 처리
파이프라인 현황 문서 신규 작성:
- Stage 0~4 전체 흐름, Type A/B/B'/B'' 구분, MDX↔유형 매핑
- 단계별 파일/함수 맵, 데이터 흐름, 현재 구현 상태
- ARCHITECTURE_OVERVIEW.md는 3/27 스냅샷으로 archived 처리

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:51:01 +09:00
kmlee1andClaude Opus 4.6 05703c8e72 WIP: hero-icon-cards_1 블록 + 오답노트 + figma 관련 파일
- hero-icon-cards_1.html: hero-icon-cards 변형 (icon → 소제목+불릿 계층)
- compare-detail-gradient.html: 하단 2열 비교 블록 (Figma Frame 4 기반)
- 오답노트.md: 절대 하지 말아야 하는 실수 목록
- figma_to_html.py: Figma→HTML 변환 스크립트
- static/figma-assets/: Figma export 이미지 (배지, 화살표)
- 주의: compare-detail-gradient CSS 폰트 크기가 임의 수정됨 — 원본 복원 필요

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 17:14:09 +09:00
kmlee1andClaude Opus 4.6 076aeb0403 Type B'' 추가: 참고 이미지 스타일 (색상바+여백, border 없음)
- block_assembler_b2.py: B'' 전용 조립 함수 (별도 파일)
- 상단: 색상 바 제목 + 소제목(accent 색상) + 불릿(들여쓰기)
- 하단: 색상 바 제목 + 표(있으면) + 불릿
- border/gradient 박스 없음, 여백과 폰트로 구분
- 이제부터 스타일 세부 조정은 하나씩 반영 예정

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 11:51:10 +09:00
kmlee1andClaude Opus 4.6 3d1194a562 03번 들여쓰기 계층 적용: D1=bold 1단 들여쓰기, D2=불릿 2단 들여쓰기
- block_assembler B': D1(<strong>) → padding 0.5x, bold, 불릿 없음
- block_assembler B': D2(일반) → padding 1x, 불릿, 폰트 -2px
- 남은 문제: 상단/하단 콘텐츠 잘림 (공간 부족)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 11:26:11 +09:00
kmlee1andClaude Opus 4.6 cba2ec2be7 03번 B' 개선: 카드 flex:1 균등 + 결론 텍스트 중복 제거
- block_assembler B': 카드 div에 flex:1 추가 → 가로 균등 높이/폭
- block_assembler: [핵심요약:] 이후 라인 break → 결론 텍스트가 하단에 섞이는 문제 해결
- 남은 문제: 하단 좌 콘텐츠 잘림, 상단 자연(여건) 불릿 잘림

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 11:15:26 +09:00
kmlee1andClaude Opus 4.6 b13df8b176 03번 B' 정상 동작: 가로 3단 카드 + overflow 해소 + 하단 균형
- block_assembler B': 카드 3개 이상 + 이미지 없음 → 가로(row) 배치
- block_assembler B': section title이 카드 제목, D1은 카드 내 bold 불릿
- block_assembler: overflow:auto → overflow:hidden, [핵심요약:] 마커 필터
- block_assembler: \x01 바이트 수정
- pipeline: Selenium 실측 기반 zone 간 재배분 (allocated-scrollHeight로 slack 계산)
- pipeline: surplus 최대 50%만 이전 (하단 최소 공간 보장)
- pipeline: bottom_left/bottom_right → Selenium bottom zone 매핑
- kei_client: 상단은 팝업 대상 제외, 하단에서만 팝업 분리

결과: 02번/03번 모두 overflow 없이 정상 출력

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:32:14 +09:00
kmlee1andClaude Opus 4.6 bc7c08e575 B' overflow 루프 동작: Kei popup 결정 → 상단 소제목만 유지 + 하위불릿 팝업
- kei_client: 에스컬레이션 prompt 개선 — 소제목 유지 필수, overflow 영역만 대상
- block_assembler B': 상단 popup_roles 체크 추가 — 소제목만 남기고 하위불릿 제거
- block_assembler: \x01 바이트 수정 (r-string 역참조)
- 결과: 03번 top overflow 358px → 143px (루프 2회차에서 popup 반영)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:20:23 +09:00
kmlee1andClaude Opus 4.6 571b057f19 Selenium 실측 overflow 정보를 에스컬레이션 report에 추가
- build_escalation_report 결과에 Selenium overflow px 정보 추가
- Kei가 실측 overflow를 인식하고 popup 결정을 내림 (7건)
- 미완: Kei popup 결정이 상단/하단좌 조립에 미반영 + 결정 과격 (전부 팝업)
- 다음: popup 결정 조립 반영 + Kei 가이드 개선 (일부만 팝업, 핵심은 유지)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:05:47 +09:00
kmlee1andClaude Opus 4.6 51f61012c3 WIP: overflow 루프 구현 + Selenium 기반 에스컬레이션 트리거
- pipeline: filled→측정→Kei 재판단→재조립 루프 (최대 3회)
- pipeline: Selenium overflow가 있으면 calculate_fit과 무관하게 에스컬레이션
- 문제: build_escalation_report가 Selenium 측정 결과를 포함하지 않아 Kei가 빈 결정 반환
- 다음: content family 기반 범용 파이프라인 설계 필요 (Phase X-C)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:59:28 +09:00
kmlee1andClaude Opus 4.6 b2a49f55ef Type B' 추가: 03번 MDX용 레이아웃 (표 렌더링 + 불릿 전용 하단)
- block_assembler: _assemble_slide_html_type_b_prime 추가
  - 하단 좌: normalized.tables를 표로 렌더링 (셀 중복 불릿 제거)
  - 하단 우: 불릿만 (table_summaries 미사용)
- pipeline: layout_template 체크를 in ("B", "B'")로 확장
- 결과: 03번 표가 표로 렌더링됨, 하단 우에 잘못된 표 제거

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:28:10 +09:00
kmlee1andClaude Opus 4.6 f568e5c95d 이미지 마크다운 필터 추가: ![alt](path) 패턴도 content_lines에서 제거
- block_assembler + assemble_stage2: 기존 [이미지:] 패턴에 ![markdown image 패턴 추가
- 02번 상단 "소통과 신뢰" 카드에 이미지 경로가 불릿으로 표시되던 문제 해결

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 07:45:32 +09:00
kmlee1andClaude Opus 4.6 095abdf9af XBX-2 완료: overflow 프로세스 정상 동작
- slide_measurer: data URI → 임시파일 방식 (대용량 HTML 측정 가능)
- pipeline: Type B zone 간 재배분 (top↔bottom 공간 이전)
- pipeline: overflow 분기에 top/bottom zone 추가
- kei_client: 에스컬레이션 prompt 개선
  - 텍스트 원문 보존 원칙 명시 (삭제/요약/압축 금지)
  - action을 popup만으로 제한
  - 실제 역할명 목록을 prompt에 전달
- block_assembler: Kei popup 결정 반영 (해당 역할 콘텐츠 → 팝업 링크)

결과: 02번 상단 카드 3개 모두 표시, 하단 우측 표 → 팝업 분리

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 07:39:02 +09:00
kmlee1andClaude Opus 4.6 028f611070 XBX-2 수행 방향 상세화: overflow 프로세스 원인 3개 + 수행 순서
- Selenium 측정 실패 (data URI 크기 제한)
- overflow 분기에 Type B zone 없음
- calculate_fit에서 Type B 역할 인식 불가

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 06:56:58 +09:00
kmlee1andClaude Opus 4.6 17e77e310f Phase X-BX' XBX-1,3,5,6 완료: 유형 B 파이프라인 정상 동작
- XBX-1: normalizer 불릿 depth 보존 (D1/D2 마커) + 조립 로직 계층 반영
- XBX-3: 하단 구조 개선 — 하나의 큰 박스 안에 중제목 헤더 + 세로 구분선 2분할
- XBX-5: before→filled→after 파이프라인 연결 확인 (filled 2.2MB, 측정/재배분 정상)
- XBX-6: Type B에서 Sonnet 재구성 + renderer 스킵 — code_assembled 직접 사용
- final.html: 4,934 bytes → 2.2MB (Type B 정상 출력)
- Type A 코드 한 글자도 안 건드림

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 06:00:18 +09:00
kmlee1andClaude Opus 4.6 82f25caa6e Phase X-BX' + X-C 계획 문서 정리
- PHASE-X-BX.md: 유형 B 미완료 6개 task 수행 방향 상세 (02번 먼저 → 03번 확장)
- PHASE-X-C.md: 서브존 프리셋 기반 범용 레이아웃 방향

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 05:16:12 +09:00
kmlee1andClaude Opus 4.6 d4eaec694c 유형 B 파이프라인 연결: block_assembler type B 조립 + zone 기반 전환 시작
- block_assembler: _assemble_slide_html_type_b 추가 (filled/after용 HTML 생성)
- fit_verifier: redistribute()가 ROLE_ZONE_MAP 대신 containers zone 사용
- renderer: render_slide_from_html()에 zone 기반 높이 탐색 추가
- pipeline: 팝업 HTML CSS를 콘텐츠 유형별(table/list/text) 분기
- run_from_stage1b: MDX 파일 하드코딩 제거 + layout_template 전달 추가

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 04:39:02 +09:00
kmlee1andClaude Opus 4.6 ef9bae7711 03번 분석: 하단 좌/우 내용 풍부, 현재 조립 부족
03번 하단 구조:
- 좌(2.1): 3개 소주제(Digital화+표, GIS+BIM, Solution) 각각 불릿 있음
- 우(2.2): 3개 소주제(품질향상, 정보물추가, 효율화) 각각 불릿 있음
- 결론: 원본 그대로

문제: _assemble_type_b가 내용을 제대로 조립 못 함
다음 세션: 하단 좌/우 내용 정확히 조립 + 렌더링 확인

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 13:40:37 +09:00
kmlee1andClaude Opus 4.6 4f0105926d 03번 MDX sections 매핑 수정: 상단 level=2 합침 + 하단 대목차 정확히 찾기
- _assemble_type_b: 상단에 해당하는 모든 level=2 section을 합침
  (03번처럼 기술/사람/자연이 별도 section으로 분리된 경우 대응)
- 하단 대목차: level=3 바로 앞의 level=2 section으로 정확히 찾기
- 03번 결과: 상단 카드(기술/사람/자연) + 하단(과정혁신/결과변화) 정상
- 02번 영향 없음

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 13:33:05 +09:00
kmlee1andClaude Opus 4.6 42d60e44a5 문서 정리: PHASE-X-B, PHASE-X-PRIME, 메모리 업데이트
현재 상태:
- 유형 A: ✅ 동작
- 유형 B: code_assembled만 동작, 파이프라인(filled/after) 미연결
- 핵심 문제: block_assembler가 고정 4역할만 처리 → 유형 B 지원 필요

다음 세션:
1. block_assembler 유형 B 지원
2. 컨테이너 크기 맞춤 (Selenium 측정 기반)
3. 유형 A 깨지지 않는지 확인

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 12:27:47 +09:00
kmlee1andClaude Opus 4.6 3719704d75 X' 핵심 수정: MDX sections에서 직접 텍스트 가져오기 + normalizer ### 지원
핵심 변경:
- mdx_normalizer: ### (h3) 소목차도 section으로 분리 (기존 ## 만)
- _assemble_type_b: Kei structured_text 대신 normalized.sections에서 직접 텍스트
- 대목차/소목차 계층 구조 그대로 반영

결과:
- 슬라이드 제목: 원본 MDX frontmatter 그대로
- 대목차: "DX 기반 Process 혁신에 따른 주체별 기대효과"
- 소목차 좌: "업무 수행 과정(Process)의 변화"
- 소목차 우: "DX 시행 주체별 기대효과" + 팝업 링크 + Kei 요약 표
- 캡션: normalized.images alt text

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 12:22:09 +09:00
kmlee1andClaude Opus 4.6 6b17f448eb Phase X'-6 추가: 본문 표 요약 프로세스 (미완성)
- pipeline.py: normalized.tables에서 본문 표 감지 → Kei 요약 요청
- assemble_stage2: _assemble_type_b 하단 우측에 table_summaries 표출
- 검증: 4열x3행 표 생성 확인

미해결:
- 들여쓰기 계층이 PNG와 다름 (대제목→소제목→본문 indent)
- 상단 컨테이너 내용 잘림
- 하단 우측: 표를 불릿으로 풀지 말고 팝업 링크 + 요약 표로
- [DX 시행 주체별 기대효과 바로가기 →] 팝업 처리

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 11:57:46 +09:00
kmlee1andClaude Opus 4.6 56fd9fa71e Phase X'-1~5 완료: 제목/들여쓰기/캡션/빈칸/카드 디자인
X'-1: 제목 원본 MDX frontmatter에서 가져오기 (Kei가 바꾸지 않음)
X'-2: 들여쓰기 계층 (소제목→불릿 indent 적용)
X'-3: 이미지 캡션 normalized.images alt text에서 추출
X'-4: 상단 컨테이너 justify-content:space-between
X'-5: 카드 디자인 다크 그라데이션 + 밝은 텍스트

X'-6 미완료: 본문 표(팝업 아닌)를 하단 우측에 Kei 요약 배치 → 다음 세션

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 11:40:53 +09:00
kmlee1andClaude Opus 4.6 c4d7212ff3 Phase X' 계획: 유형 B 파이프라인 개선 6건 정리
X'-1: 제목 원본에서 가져오기 (Kei가 바꾸지 않도록)
X'-2: 들여쓰기 계층 (대/중/소제목+본문)
X'-3: 이미지 캡션 원본 형식
X'-4: 상단 빈칸 균등 배분
X'-5: 카드 디자인 개선
X'-6: 표 요약 (하단 우측 Kei 요약)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 11:33:18 +09:00
kmlee1andClaude Opus 4.6 a8fe20e08e Phase X-B 진행중: 유형 B 조립 + 텍스트 보존 강화 + 원본 MDX 복구
X-B-3~5 완료:
- space_allocator: build_containers_type_b() 추가
- assemble_stage2: _assemble_type_b() 추가 (소제목 카드형)
- pipeline.py: layout_template 분기 (A/B)
- pipeline_context: Analysis.layout_template 필드
- validators: 유형 B 검증 완화

텍스트 보존 강화:
- KEI_PROMPT: 제목 원본 그대로, 텍스트 재작성 금지
- KEI_STRUCTURED_TEXT_PROMPT: 소제목 유지, 원본 문장 그대로

원본 MDX 복구:
- samples/mdx_batch/02.mdx: 표 데이터 누락 수정 (원본에서 재복사)

미해결 (다음 세션):
- 들여쓰기: 대제목→중제목→소제목→본문 계층 구조
- 이미지 캡션: [그림 제목] 형식 (대괄호 포함)
- 상단 컨테이너: 빈칸 위로 붙이기
- 카드 디자인: 안전과품질/생산성향상/소통과신뢰 디자인 개선
- 제목: Kei가 원본 제목 바꾸는 문제 잔존

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 11:28:03 +09:00
kmlee1andClaude Opus 4.6 bc7829b08b Phase X-B-1,2 완료: Kei 유형 A/B 선택 + 검증기 완화
X-B-1: KEI_PROMPT에 유형 B 옵션 추가
- 유형 A: 기존 배경/본심/첨부/결론 (참조자료 있는 콘텐츠)
- 유형 B: 본심1(상단)+본심2(하단2분할)+결론 (본문만으로 구성)
- Kei가 콘텐츠 보고 A/B 선택, layout_template 필드로 반환
- 검증: 01번→A, 02번→B 정확히 선택

X-B-2: 검증기 완화
- 유형 A: 본심 필수 유지
- 유형 B: 결론(footer)만 필수, 자유 역할명 허용
- 섹션 수 차이 허용 확대 (유형 B: 4)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 10:10:22 +09:00
kmlee1andClaude Opus 4.6 c9677a69f8 V'-2/V'-4 수정: 표 행 수 계산 + footer 최소 높이 + 이미지 비율
- V'-2: 표 공간 계산에 V'-4(결론 위까지 채움) 높이 반영
  → Kei에게 정확한 행 수 전달 (1행 → 5행)
- V'-2: 이미지 높이를 실제 비율로 계산 (sub_layout 고정값 대신)
  → 200/2.73 = 73px (기존 172px → 공간 100px 확보)
- footer 최소 높이: design tokens 기반 동적 계산
  → weight 0.05일 때 26px → 53px 보장
- assemble_stage2: 이미지 높이도 실제 비율 반영

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 09:38:28 +09:00
1512 changed files with 58013 additions and 3755 deletions

No files matched your search

+134
View File
@@ -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"
]
}
}
+3 -10
View File
@@ -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"
}
}
}
+9 -1
View File
@@ -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의 아키텍처, 파이프라인, 코드 구조를 체계적으로 이해
+111 -14
View File
@@ -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 붙여넣기 또는 파일 업로드)
+304
View File
@@ -0,0 +1,304 @@
# Design Agent 파이프라인 현황
> **최종 갱신:** 2026-04-13
> **목적:** 새 세션의 AI가 이 문서만 읽으면 파이프라인 전체를 이해하고 작업할 수 있도록 한다.
---
## 1. 전체 흐름 요약
```
MDX 입력
↓
[Stage 0] MDX 정규화 (코드)
↓
[Stage 1A] Kei 실장 — 꼭지 추출 (AI: Opus)
→ layout_template: A 또는 B 선택
↓
[Stage 1B] 컨셉 구체화 (AI: Opus)
→ relation_type, expression_hint, source_data
↓
[Stage 1B-ST] 구조화 텍스트 생성 (AI: Opus)
→ structured_text per topic
↓
[Stage 1.5a] 컨테이너 계산 (코드: 결정론적)
→ FontHierarchy, ContainerSpec, Preset
↓
[Stage 1.7] 블록 레퍼런스 선택 (코드 + AI 1회)
→ relation_type → 카테고리 → 필터 → 블록 결정
↓
[Stage 1.8] 적합성 검증 + 보강 (코드 + Selenium + AI)
→ overflow 감지 → Kei 에스컬레이션 → 재배분
↓
[Stage 1.5b] 디자인 예산 계산 (코드)
↓
[Stage 2] HTML 생성 (Type에 따라 다름)
→ Type B/B'/B'': block_assembler (코드)
→ Type A: Sonnet 재구성 (AI, 미완성)
↓
[Stage 3] 렌더링 조립 (Type A만, Jinja2)
↓
[Stage 4] 검증 (Selenium + Opus Vision)
→ overflow 측정 + 스크린샷 품질 평가
↓
최종 HTML 출력 (data/runs/{id}/final.html)
```
---
## 2. 레이아웃 유형 (Type A / B / B' / B'')
### 2.1 Kei가 선택하는 유형: A와 B
Kei 프롬프트(`src/kei_client.py:34-46`)에서 A 또는 B를 선택한다.
| 유형 | 조건 | Zone 구조 |
|------|------|-----------|
| **Type A** | 참조자료(용어 정의, 부록 등)가 별도로 존재 | body(배경+본심) + sidebar(첨부) + footer(결론) |
| **Type B** | 본문 흐름만. 배경/첨부가 없거나 억지로 만들어야 하면 | top + bottom_left + bottom_right + footer |
### 2.2 Type B 변형: B'과 B''
B'과 B''은 **Kei가 선택하지 않는다.** 특정 MDX 테스트 과정에서 하드코딩한 변형이다.
| 변형 | 생성 경위 | 차이점 | 코드 위치 |
|------|----------|--------|----------|
| **B** | 범용 | 상단(전체폭 텍스트+이미지) + 하단 2분할 + 결론 | `block_assembler.py:461` `_assemble_slide_html_type_b()` |
| **B'** | 03번 MDX 테스트 중 생성 | 상단이 세로 카드 형태 + 하단에 표 렌더링 + 불릿 전용 | `block_assembler.py:885` `_assemble_slide_html_type_b_prime()` |
| **B''** | B'에서 스타일 변형 | border/gradient 없음. 색상바+여백으로 구분 | `block_assembler_b2.py:9` `_assemble_slide_html_type_b_double_prime()` |
**분기 코드** (`block_assembler.py:370-378`):
```python
if ctx.analysis.layout_template == "B":
return _assemble_slide_html_type_b(ctx, title_text)
if ctx.analysis.layout_template == "B'":
return _assemble_slide_html_type_b_prime(ctx, title_text)
if ctx.analysis.layout_template == "B''":
return _assemble_slide_html_type_b_double_prime(ctx, title_text)
```
### 2.3 향후 방향
B'/B''은 **범용화가 필요하다.** 현재는 03번 콘텐츠 구조(카드형+표)를 B 조립 함수가 커버하지 못해서 만든 땜질이다. 궁극적으로는 B 하나로 다양한 콘텐츠 구조를 커버하거나, AI가 서브타입을 판단하게 해야 한다.
---
## 3. MDX 샘플 ↔ 유형 매핑
| MDX | 파일 | 콘텐츠 성격 | 선택 유형 | 상태 |
|-----|------|-----------|----------|------|
| **01번** | `samples/mdx/01. 건설산업 DX의 올바른 이해(0127).mdx` | 용어 혼용 문제 + 용어 정의(참조) | **Type A** | ⚠ Stage 2 미완성 (Sonnet 의존) |
| **02번** | `samples/mdx/02. DX의 시행 목표 및 기대효과.mdx` | 본문 흐름 (3대 목표) | **Type B** | ✅ 동작 |
| **03번** | `samples/mdx/03. DX 시행을 위한 필수 요건 및 혁신 방안.mdx` | 카드형 구조 + 표 + 불릿 | **Type B'** | ✅ 동작 (하드코딩) |
---
## 4. 단계별 상세
### Stage 0: MDX 정규화
- **파일:** `src/mdx_normalizer.py` → `normalize_mdx_content()`
- **입력:** raw MDX 텍스트
- **출력:** `NormalizedContent` (sections, images, tables 분리)
### Stage 1A: Kei 실장 — 꼭지 추출
- **파일:** `src/kei_client.py` → `classify_content()`
- **AI:** Opus (Kei API)
- **입력:** 정규화된 텍스트
- **출력:** `Analysis` (title, core_message, layout_template, total_pages, page_structure, topics)
- **핵심 판단:**
- 꼭지 5개 이내 추출
- 각 꼭지에 purpose, layer, role, emphasis, direction 부여
- layout_template = "A" 또는 "B" 선택
- page_structure에 역할별 weight(비중) 배정
### Stage 1B: 컨셉 구체화
- **파일:** `src/kei_client.py` → `refine_concepts()`
- **AI:** Opus
- **출력:** topics에 relation_type, expression_hint, source_data 추가
### Stage 1B-ST: 구조화 텍스트 생성
- **파일:** `src/kei_client.py` → `generate_structured_text()`
- **AI:** Opus
- **출력:** topic별 structured_text (마크다운 형태)
### Stage 1.5a: 컨테이너 계산 (결정론적)
- **파일:** `src/space_allocator.py`
- **함수:**
- Type A → `calculate_container_specs()`
- Type B/B'/B'' → `build_containers_type_b()`
- 공통 → `calculate_font_hierarchy()`, `select_preset()`
- **입력:** page_structure의 weight, slide 크기(1280×720)
- **출력:** 역할별 `ContainerSpec` (width_px, height_px, zone)
- **로직:** weight × available_px = 각 zone px 확정
### Stage 1.7: 블록 레퍼런스 선택
- **파일:** `src/block_reference.py` → `select_and_generate_references()`
- **로직 (코드 결정론적 + AI 1회):**
1. relation_type → 블록 카테고리 매핑
2. expression_hint 키워드 매칭
3. 컨테이너 크기 적합성 필터
4. role/zone 제약 (sidebar → visuals/media 제외)
5. catalog.yaml 존재 검증 (유령 블록 차단)
6. 후보 2-3개 → Kei 1회 호출로 최종 선택
- **출력:** 역할별 `BlockReference` (block_id, design_reference_html)
### Stage 1.8: 적합성 검증 + 보강
- **파일:** `src/fit_verifier.py` → `calculate_fit()`
- **로직:**
1. 텍스트 분량 vs 할당 공간 계산
2. Selenium으로 실제 높이 측정 (3회 루프)
3. overflow 시 → Kei 에스컬레이션 (`call_kei_fit_escalation()`)
- 팝업 분리 판단, zone 간 재배분
4. 보강 제안: bold 키워드, 팝업 요약 등
### Stage 2: HTML 생성
**Type B/B'/B'' (코드 조립):**
- **파일:** `src/block_assembler.py` → `assemble_slide_html()`
- 역할별 `assemble_role_html()` 호출 → 블록 HTML 조립
- structured_text + design_reference_html 결합
- 이미지/팝업 embed
- **즉시 완성 HTML 반환**
**Type A (AI 재구성, 미완성):**
- **파일:** `src/content_verifier.py` → `generate_with_retry()`
- Sonnet에 phase_t_context 전달 → CSS + 레이아웃 생성
- **현재 검증 불완전**
### Stage 3: 렌더링 조립 (Type A만)
- **파일:** `src/renderer.py` → `render_slide_from_html()`
- Type B는 Stage 2에서 완전한 HTML이므로 스킵
### Stage 4: 검증
- **파일:** `src/slide_measurer.py`
- `measure_rendered_heights()` — Selenium 실측
- `capture_slide_screenshot()` — 스크린샷 캡처
- **파일:** `src/kei_client.py` → `vision_quality_gate()`
- Opus 멀티모달: 스크린샷 보고 시각 품질 평가
---
## 5. 핵심 파일 맵
```
src/
├── main.py ← FastAPI 서버, POST /api/generate
├── pipeline.py ← 파이프라인 오케스트레이터 (generate_slide)
├── pipeline_context.py ← PipelineContext 데이터 클래스
├── config.py ← 설정 (API key, 슬라이드 크기)
│
├── mdx_normalizer.py ← Stage 0: MDX → NormalizedContent
├── kei_client.py ← Stage 1A/1B/1B-ST: Kei API 호출 + 프롬프트
├── space_allocator.py ← Stage 1.5a: 컨테이너 px 계산
├── block_reference.py ← Stage 1.7: 블록 선택 (relation_type 기반)
├── fit_verifier.py ← Stage 1.8: 적합성 검증 + Selenium 루프
├── block_assembler.py ← Stage 2: Type B/B' HTML 조립
├── block_assembler_b2.py ← Stage 2: Type B'' HTML 조립
├── content_verifier.py ← Stage 2: Type A HTML (Sonnet, 미완성)
├── renderer.py ← Stage 3: Jinja2 렌더링 (Type A만)
├── slide_measurer.py ← Stage 4: Selenium 측정 + 스크린샷
├── validators.py ← Kei 응답 검증 (A/B별 구조 확인)
│
├── image_utils.py ← 이미지 크기 측정 + data URI 변환
├── svg_calculator.py ← SVG 다이어그램 좌표 계산
└── sse_utils.py ← SSE 스트리밍 유틸
templates/
├── slide-base.html ← 슬라이드 기본 구조 (Jinja2, CSS Grid)
├── catalog.yaml ← 블록 라이브러리 정의 (50+개)
└── blocks/ ← 블록 HTML 템플릿
├── headers/ (8개)
├── cards/ (17개)
├── emphasis/ (12개)
├── tables/ (8개)
├── visuals/ (6개)
├── media/ (4개)
└── BEPs/ (6개)
```
---
## 6. 데이터 흐름 (PipelineContext)
```
PipelineContext:
raw_content ← 원본 MDX
normalized ← NormalizedContent (sections, images, tables)
analysis ← Analysis (title, core_message, layout_template, page_structure, topics)
topics ← list[Topic] (relation_type, expression_hint, structured_text 포함)
page_structure ← PageStructure (roles → {topic_ids, weight, zone})
containers ← dict[role → ContainerSpec(width_px, height_px)]
font_hierarchy ← FontHierarchy (key_msg, core, bg, sidebar 폰트 크기)
references ← dict[role → list[BlockReference]]
sub_layouts ← dict[role → SubLayout]
fit_result ← 역할별 fit_status, 재배분값
enhancement_result ← bold_keywords, popup_summaries 등
generated_html ← Stage 2 출력
rendered_html ← Stage 3 출력 (완전 HTML)
measurement ← Selenium 측정값
quality_score ← 0-100
```
---
## 7. 현재 구현 상태 (Phase Y-11~13, 2026-04-15)
> Phase Y: slide-base 기반 파이프라인 재설계. 상세: `docs/history/PHASE-Y-PLAN.md`
### 파이프라인 흐름 (현재)
```
[Stage 0] MDX → normalized.sections (source of truth)
[Stage 1A] Kei 꼭지 추출 (영역/zone 판단 안 함)
[Phase Y] 코드: normalized → 대목차 추출 → group schema 분류 → 블록 매칭 → 영역 확정
[Stage 1.5a] space_allocator: weight → zone px (% 기반)
[Stage 1.7] block_reference: tag_match → schema_match → fallback 순서
[Stage 1.8] assembler(measure_mode) → Selenium 측정 → fit 루프
[Stage 2] assembler(slide-base + 블록) → final HTML
[Stage 4] Selenium overflow + 비전 (-1 미평가)
```
### MDX별 상태
| MDX | 상태 | 비고 |
|-----|------|------|
| **03** | ✅ 동작 | prerequisites-3col + pp2. 텍스트 누락 없음. 회귀 기준. |
| **02** | ⚠ schema 1차 | top: parallel_3_with_image. bottom: 분류 정교화 필요. |
| **01** | ⬜ 미착수 | Type A. 별도 작업. |
### 핵심 원칙 (확립됨)
- source of truth = normalized.sections (Stage 0)
- 영역 = 코드가 결정 (Kei 아님). sub_titles 기반 + group schema.
- 블록 CSS에 최종 고정값. slide_font_css는 공통 레이아웃 계약만.
- zone = % 기반, block = height:100%.
- 글씨 크기 고정. fit은 padding → 내용량 → font 1단계(responsive tier).
- 기존 경로 삭제 금지. 새 schema 점진적 추가. MDX 03 회귀 기준.
- 하드코딩 금지. 프로세스가 결과를 만드는 구조.
3. **블록 글씨 크기 하드코딩 (px 고정)**
- 블록 CSS에 font-size가 Figma 원본 px로 고정
- 컨테이너 크기에 따라 조정 불가 → overflow 원인
- CSS 변수(`var(--block-font-heading)`)로 전환 → assembler가 zone 크기에 따라 계산
### 미해결 프로세스
1. **overflow 시 font 조정 루프** — 재배분만으로 부족할 때 font/padding 줄이기 (Y-5)
2. **Sonnet redesign 경로** — tag 매칭 실패 시 블록 단위 redesign → 저장 (Y-6)
---
## 8. 검증 계획
업데이트된 템플릿이 파이프라인에서 제대로 동작하는지 확인한다.
| MDX | 유형 | 검증 포인트 |
|-----|------|-----------|
| **01번** | Type A | 업데이트된 블록 + slide-base.html로 조립 정상 동작 |
| **02번** | Type B | 업데이트된 블록 선택 + 조립 + overflow 없음 |
| **03번** | Type B' | 카드/표 구조가 업데이트된 템플릿으로 정상 렌더링 |
### 검증 방법
1. 각 MDX를 파이프라인에 투입
2. 중간 산출물(step1_analysis.json 등) 확인 — 블록 선택이 의도대로인지
3. 최종 HTML(final.html) 렌더링 — overflow, 시각 품질 확인
4. 업데이트 전/후 비교
+30
View File
@@ -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이 브라우저에서 정상 렌더링
---
## 의존 관계
```
+99 -278
View File
@@ -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) | 블록 작성 규칙 (에이전트 간 계약서) |
Binary file not shown.
-105
View File
@@ -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": ""},
}
+162
View File
@@ -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에 슬롯/구조/호환 정보 등록
+183
View File
@@ -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 메인 대상에서 제외한다. 정체는 미확인.
+157
View File
@@ -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단계) ↓
블록화 시 이 규칙에 맞춰 전환
```
File diff suppressed because it is too large. Load diff
@@ -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 추가/제거/순서 변경 시에만
+69
View File
@@ -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 정의 (공통 + 의미색)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 936 KiB

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+101
View File
@@ -0,0 +1,101 @@
# Phase X-B: 유형 B 템플릿 추가
> 최종 업데이트: 2026-04-06
> 전제: 유형 A(배경+본심+첨부+결론) 기존 코드 건드리지 않음
---
## 유형 B 구조
02번 MDX (DX의 시행 목표 및 기대효과) 기준.
MDX 원본 구조:
```
title: DX의 시행 목표 및 기대효과 ← 슬라이드 제목 (frontmatter)
## 1. DX의 궁극적 목표 ← 상단 (level=2)
- 안전과 품질 / 생산성 향상 / 소통과 신뢰 ← 소제목 카드
![DX의 궁극적 목표](이미지) ← 상단 우측 이미지
## 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 대신)
+309
View File
@@ -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"`) 추가로만 구현
- 검증은 반드시 렌더링(스크린샷)으로
+142
View File
@@ -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)
+111
View File
@@ -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 미지원 |
File renamed without changes.
+611
View File
@@ -0,0 +1,611 @@
# Phase Y: slide-base 기반 블록 조립 파이프라인 재설계
> **작성일:** 2026-04-14
> **목적:** assembler를 slide-base.html 기반으로 재작성. 블록 선택 → 배치 → 측정 → 조정 루프 완성.
> **근거:** Phase X-BX까지 assembler가 slide-base를 무시하고 HTML을 처음부터 생성. 블록 선택(1.7)이 조립(Stage 2)에 반영 안 됨. Stage 4 품질 점수 거짓말. 전체 파이프라인 연결 끊김.
---
## 핵심 전환
```
[이전] assembler가 HTML 전체를 하드코딩 생성. 블록 무시. slide-base 미사용.
[이후] slide-base.html 위에 tag 매칭된 블록을 배치. 사전계산 + 실측 조정 루프.
```
---
## 전체 흐름 (2026-04-15 재설계)
```
[1] slide-base.html 로드
├── title → .slide-title
├── 핵심요약(:::note) → .slide-footer (footer_text)
└── .slide-body (590px 가용) → 영역(zone)들이 여기에 배치됨
[2] Stage 1A: Kei 꼭지 추출 (영역 없이, 꼭지만)
├── Kei에게 zone/영역 판단을 시키지 않음
├── 꼭지별 title, purpose, layer, relation_type만 추출
└── 핵심요약은 conclusion_text로 분리
[3] 코드: MDX ## 파싱 → 꼭지-대목차 매핑
├── MDX에서 ## 대목차 목록 추출 (## 없는 도입부도 포함)
├── 각 꼭지의 source_data/title이 어느 ## 아래에 속하는지 매핑
└── 대목차별 꼭지 묶음 생성
[4] 코드: 대목차별 묶음으로 블록 tag 매칭 시도 (영역 확정 단계)
├── 묶음별 item_count + 꼭지 title → catalog tag 검색
├── 매칭됨 → 이 묶음 = 하나의 영역 (코드가 확정, 블록도 확정)
└── 매칭 안 됨 → Kei에게 이 꼭지들의 영역 판단 요청
├── "이 꼭지들을 어떻게 묶을지?"
├── "sidebar로 뺄 것이 있는지?" → Type A 결정
└── Kei는 주어진 꼭지 목록 안에서만 판단 (새 영역 이름 만들지 않음)
[5] 영역 확정 + 콘텐츠 소스 결정
├── 영역 제목 = MDX 원본 ## 제목 그대로
├── 영역 콘텐츠 = normalized.sections에서 MDX 원본 텍스트
├── Kei structured_text가 아님 (Kei는 구조 판단만)
└── sidebar 지정된 영역 → Type A / 나머지 → Type B
[6] 사전 계산: 영역별 비중 → 대략적 px 배정
└── 블록 후보 필터링 (너무 큰 블록 제외)
[7] .slide-body에 블록 배치 + 실측 조정 루프
├── 블록 템플릿 Jinja2 렌더링 (슬롯에 MDX 원본 텍스트 삽입)
├── slide-base + 블록 HTML 조합
├── Selenium 측정 (measure_mode: overflow:auto) → overflow 확인
├── overflow 시:
│ ├── ① font/padding 조정 (CSS 변수, 코드)
│ ├── ② 간격 축소
│ ├── ③ 텍스트 압축 (최후 수단, AI 1회)
│ └── 재측정 (최대 3회 루프)
└── overflow 없음 → 확정
[8] 최종 HTML 출력
└── slide-base + 확정된 블록들 = final.html (overflow:hidden)
[9] 품질 검증
├── Selenium overflow 측정
├── 비전 모델 평가 (가능할 때만)
└── 미평가 시 -1 (거짓말 안 함)
```
### 핵심 변경 (이전 대비)
| 항목 | 이전 (문제) | 이후 (재설계) |
|------|-----------|-------------|
| 영역/zone 결정 | Kei가 zone 구조를 만듦 → 매번 다름, 오인 | 코드가 ##파싱 + 블록매칭으로 먼저 확정. 안 되는 것만 Kei |
| 콘텐츠 소스 | Kei structured_text (재구성) | MDX 원본 (normalized.sections) |
| 영역 제목 | Kei가 축약 ("필수요건") | MDX 원본 ## 제목 그대로 |
| Kei 역할 | 꼭지+영역+zone+텍스트 전부 | 꼭지 추출 + 성격 판단(sidebar/팝업)만 |
| 블록 매칭 시점 | 영역 확정 후 | 영역 확정 전 (블록이 영역을 결정) |
---
## 태스크 목록
### Y-1: Kei 프롬프트 — zone = 대목차 (완료)
- [x] zone = `##` 대목차 단위, `###` 소목차 = zone 안 블록
- [x] 2단계 판단: 먼저 zone 잡고 → 용어정의 있으면 Type A
- [x] 결론/핵심요약 = slide-base footer (별도 zone 아님)
- [ ] Kei 프롬프트에서 결론 zone 제거 (page_structure에서 제외)
### Y-2: space_allocator — zone=bottom 지원
- [ ] `bottom` zone 전체폭 처리 추가
- [ ] Type A/B에 따라 .slide-body 내 레이아웃 분기
- [ ] 결론 zone 미생성 (slide-base footer로 처리)
### Y-3: block_reference — tag 매칭 강화
- [x] tag 기반 0순위 매칭 추가
- [x] item_count 범위("2-3") 처리
- [ ] content_pattern 가중치 추가 (item_count만으로 매칭 방지)
- [ ] 결론 role 매칭 제외 (slide-base가 처리)
### Y-4: assembler 재작성 — slide-base 기반
- [ ] slide-base.html 로드 → Jinja2 렌더링
- [ ] title, footer_text(핵심요약) 삽입
- [ ] .slide-body에 zone별 블록 HTML 삽입
- [ ] render_block_for_role()로 블록 렌더링
- [ ] 블록 CSS를 slide-base <style>에 합침
- [ ] 기존 하드코딩 assembler 제거 (fallback 아님)
### Y-5: 사이즈 조정 루프
- [ ] 사전 계산: zone 비중 → px 배정 → 블록 필터링
- [ ] 실측 조정: Selenium 측정 → overflow → font/padding 조정
- [ ] 루프 최대 3회
- [ ] overflow 해소 안 되면 텍스트 압축 (AI 1회)
### Y-6: Sonnet redesign 경로
- [ ] tag 매칭 실패 시 유사 블록 선택
- [ ] Sonnet에 블록 HTML + 콘텐츠 구조 전달 → 블록 단위 redesign
- [ ] redesign 결과를 templates/blocks/redesign/에 저장
- [ ] catalog.yaml, INDEX.md 자동 업데이트
### Y-7: step_visualizer 정합성
- [ ] 각 stage step HTML이 실제 파이프라인 데이터와 일치
- [ ] stage_1_8_blocks → 실제 블록 렌더링 (샘플 아님)
- [ ] stage_2 → slide-base + 블록 조합 결과 표시
### Y-8: Stage 4 품질 검증
- [x] 비전 실패 시 -1 (거짓말 방지)
- [x] 비전 미평가 시 차단 안 함, 경고만
- [ ] Selenium overflow 검사 정확도 검증
### Y-9: 검증 — MDX 03 end-to-end
- [ ] MDX 03 파이프라인 실행
- [ ] 각 stage 전후 데이터 연결 확인
- [ ] 최종 결과물이 85점 수준인지 시각 확인
---
## 의존 관계
```
Y-1 (Kei 프롬프트) ──→ Y-2 (space_allocator) ──→ Y-3 (block_reference)
↓
Y-4 (assembler 재작성)
↓
Y-5 (사이즈 루프)
↓
Y-6 (Sonnet redesign)
↓
Y-7 (step_visualizer)
↓
Y-8 + Y-9 (검증)
```
## 블록 tag 업데이트 (별도 작업)
- 사용자가 다른 클로드와 진행
- catalog.yaml에 tags 필드 추가
- content_pattern, item_count, content_example 등
- Phase Y와 병렬 진행 가능
---
## 2026-04-14 진행 결과
### 완료된 것
| 태스크 | 상태 | 비고 |
|--------|------|------|
| Y-1 Kei 프롬프트 | ✅ | zone=대목차(##) 단위. 결론=conclusion_text 필드. page_structure에서 제거. |
| Y-2 space_allocator | ✅ | zone=bottom 전체폭 지원. 결론 zone 미생성. slide-body 590px 기준. |
| Y-3 block_reference | ✅ | tag 매칭 0순위. item_count+content_example AND 조건. 결론 매칭 제외. |
| Y-4 assembler | ✅ | slide-base.html 로드 → zone별 블록 렌더링 → .slide-body에 배치. measure_mode 분리. |
| Y-7 step_visualizer | ✅ | 1_5a~1_8 모두 slide-base 기반 _wrap(). _hdr/_box/_calc_coords 제거. |
| Y-8 Stage 4 | ✅ | 비전 실패 시 -1. 거짓말 방지. |
| validator | ✅ | 결론 zone 필수 검증 제거. |
| slide_measurer | ✅ | zone- 클래스 감지 추가 (area-만 보던 것 수정). |
### 검증된 것 (MDX 03 파이프라인 실행)
```
Kei → zone 2개 (필수요건[1,2,3] + 혁신과변화[4,5]) + conclusion_text ✅
space_allocator → top 295px + bottom 287px (합 582px, gap 8px = 590px) ✅
block_reference → prerequisites-3col (tag_match) + compare-detail-gradient (tag_match) ✅
assembler → slide-base + 블록 렌더링 → .slide-body에 배치 ✅
Selenium → zone별 overflow 감지 (bottom +190px) ✅
Stage 4 → -1 미평가 ✅
```
### 근본 설계 오류 (2026-04-14 발견)
**근본 오류: 콘텐츠 소스가 잘못됨**
- 현재: Kei가 structured_text를 재구성 → assembler가 이걸 블록에 넣음
- 올바른 방향: **assembler는 normalized.sections(MDX 원본 텍스트)에서 직접 가져옴**
- Kei 역할: 구조 판단만 (zone 분류, 팝업 분리, 블록 선택). **텍스트 재구성 안 함.**
- zone 제목도 Kei가 줄인 role_name이 아니라, MDX 원본 `##` 제목을 그대로 사용
- 데이터 흐름 변경:
```
[현재] MDX → Kei structured_text(재구성) → assembler
[올바름] MDX → normalized.sections(원본) → assembler
Kei → 구조 판단(zone/블록/팝업) → assembler에 지시만
```
- 수정 대상:
1. assembler: `topic.structured_text` 대신 `normalized.sections`에서 원본 텍스트 가져오기
2. zone 제목: `role_name` 대신 `normalized.sections`의 `##` 제목 사용
3. Kei의 topic_ids로 어떤 section이 어떤 zone에 가는지 매핑
### 미해결 오류 (수정 중)
**오류 1: 블록 색상이 Figma 원본과 다름** → nth-child로 수정 완료 (인라인 style 제거)
**오류 2: ### 마크다운 헤더 그대로 노출** → _parse_topic_to_items에서 ### 스킵 수정 완료
**오류 3: 글씨 크기 px 고정** → CSS 변수 전환 수정 완료
**오류 4: slide-base HTML 주석이 출력에 노출** → re.sub로 주석 제거 수정 완료
**오류 5: zone-bottom 중복 (body가 2번 삽입)** → 주석 안 {% block body %} 제거로 수정
**오류 6: zone 제목이 Kei 축약본** → 근본 오류. normalized.sections에서 원본 제목 가져와야 함
### 미해결 오류 (원래 3건, 추가 발견 포함)
**오류 1: 블록 색상이 Figma 원본과 다름** (수정 완료)
- prerequisites-3col 블록: 3열이 각각 다른 색(파랑/금/초록)이어야 하는데 전부 같은 색
- 원인: 블록 템플릿 CSS의 `|default()` 값이 단일 색상(파랑)만 있음
- assembler에서 색상을 전달하는 방식은 잘못됨
- 해결: **블록 템플릿 CSS 자체에 열별 색상을 가지고 있어야 함** (`:nth-child(1)`, `:nth-child(2)`, `:nth-child(3)`로 각각 다른 색)
- 또는 catalog.yaml에 블록 디자인 속성(색상 팔레트)을 정의하고 assembler가 읽어서 전달
- assembler는 콘텐츠만 전달. 디자인은 블록이 가지고 있거나 catalog에서 오는 것.
**오류 2: 하단 zone에 `### 과정의 혁신`, `### 결과의 변화` 마크다운 헤더가 그대로 노출**
- `_parse_topic_to_items()`에서 `### ` 접두사를 heading으로 포함시킴
- topic.title이 이미 "과정(Process)의 혁신"인데, structured_text 첫 줄에 또 `### 과정(Process)의 혁신`이 있음
- 해결: `_parse_topic_to_items()`에서 `### `으로 시작하는 줄은 무시 (topic.title과 중복)
- 또는 structured_text 파싱 시 `### ` 접두사 제거
**오류 3: 블록 글씨 크기가 하드코딩 (px 고정)**
- 블록 템플릿 CSS에 `font-size: 27px`, `font-size: 21px` 등 Figma 원본 크기가 고정
- 컨테이너 크기에 따라 font가 조정되어야 하는데 고정이라 overflow 발생
- 해결 방향:
1. 블록 CSS에서 font-size를 CSS 변수(`var(--block-font-heading)`)로 변환
2. assembler가 zone 크기에 따라 CSS 변수 값을 계산하여 전달
3. overflow 루프에서 CSS 변수를 줄여가며 재측정
- 이것이 Y-5(사이즈 조정 루프)의 핵심
### 미해결 프로세스
**overflow 재배분 루프가 실질적으로 안 동작**
- Selenium이 overflow +190px를 감지했지만, 재배분할 surplus zone이 없음 (top도 꽉 참)
- 재배분만으로는 해결 안 됨 → font/padding 조정이 필요
- 현재 파이프라인에 font 조정 로직 없음
- Y-5에서 구현 필요:
1. overflow 감지 → font-size 1~2px 줄이기 → 재렌더링 → 재측정
2. 최대 3회 루프
3. 그래도 안 되면 텍스트 압축 (AI 1회)
**Sonnet redesign 경로 (Y-6) 미구현**
- tag 매칭 실패 시 유사 블록 기반 Sonnet redesign → templates/blocks/redesign/에 저장
- 현재는 tag 매칭 실패 시 기존 relation_type 방식으로 fallback
**콘텐츠 소스 전환 (Y-10)** → ✅ 완료 (2026-04-15)
- normalized.sections를 단일 소스로 확정
- section_parser: raw MDX 파싱 → normalized.sections 기반으로 변경
- assembler: _find_section_content() → normalized.sections에서 가져옴
- D1:/D2: 포맷 파싱 지원 추가
- Kei structured_text 의존 제거
---
## 2026-04-15 진행 결과
### 구조 안정화 1차 완료
| 항목 | 상태 | 비고 |
|------|------|------|
| process wiring | ✅ 복구됨 | normalized.sections → section shape → block assignment → slide-base → final html |
| overflow control | ✅ 통과 | Selenium zone별 측정 동작, overflow 0 (최신 run) |
| single source of truth | ✅ 확정 | normalized.sections (Stage 0). raw MDX 직접 사용 안 함. |
| layout_template 결정 | ✅ 코드 | sidebar 유무로 A/B 자동 결정 (Kei 의존 아님) |
| 영역 확정 | ✅ 코드 | sub_titles 기반 블록 매칭 → 영역 확정 (Kei zone 판단 제거) |
| validator 정리 | ✅ | page_structure 검증을 section_parser 후로 이동. Type B purpose 모순 = 경고만. |
| **semantic block matching** | ❌ 미해결 | prerequisites-3col 대신 category-strip-table 선택됨 |
| **slot filling** | ❌ 미해결 | 블록 slot에 텍스트가 비어있음 (껍데기만) |
| quality gate | ⚠ 부분 | vision 404. Selenium만 동작. |
### 핵심 미해결: 블록 매칭 정확도 + slot 채움
**1. 블록 매칭이 엉뚱한 블록을 선택**
- top: prerequisites-3col(원하는 것) → category-strip-table(선택됨)
- bottom: compare-detail-gradient(원하는 것) → dark-bullet-list(선택됨)
- 원인: tag 매칭 점수에서 category-strip-table이 더 높은 점수를 받음
- 해결: tag 매칭 점수 로직 보정 필요
- prerequisites-3col의 content_example에 "기술/사람/자연"이 정확히 매칭되면 최우선
- compare-detail-gradient의 content_example에 "과정/결과"가 매칭되면 최우선
**2. slot 채움이 비어있음**
- category-strip-table 블록의 slot에 기술/사람/자연 데이터가 안 들어감
- 원인: _build_slot_data()가 sub_title별로 normalized.sections에서 content를 찾는데,
sub_title "기술(디지털)"의 content가 대목차 "DX 시행을 위한 필수 요건"의 합친 content에 있어서
개별 sub_title별로 분리 안 됨
- 해결: normalized.sections에서 sub_title별 개별 content를 직접 가져와야 함
(major_sections의 합친 content가 아니라, 개별 level=2 section)
**3. section shape와 topic 매핑 불일치**
- bottom의 topic_ids=[4]만 있음 (5가 빠짐)
- 원인: Kei가 꼭지를 매번 다르게 만들어서 매핑이 흔들림
- 해결: topic 매핑도 sub_titles 기반으로 안정화 필요
### 추가 발견 (2026-04-15 후반)
**블록 매칭은 해결됨 (sub_titles 기반 + min_height 감점 제거):**
- top: prerequisites-3col ✅ (sub_titles 3개 매칭)
- bottom: compare-detail-gradient ✅ (sub_titles 2개 매칭)
**하지만 결과물 품질이 안 맞음:**
1. 결론 텍스트 3번 중복 (블록 slot + footer)
2. bottom 좌측 텍스트 안 보임 (D1 only → desc 빈 배열)
3. 텍스트 중복 렌더링
4. zone height px 고정 (참고는 % 기반)
5. font_scale 방식으로 글자 과도 축소 (참고는 글자 크기 고정)
6. 블록이 zone height:100%로 안 채워짐
7. weight가 content 글자 수 기준 (중요도가 아님)
8. bottom 블록으로 cdg가 선택됐지만, 참고 결과물은 pp2 사용
**근본 원인:**
- cdg로도 가능하지만, pp2가 이 콘텐츠 구조(비대칭: 표+불릿 vs 불릿)에 더 적합
- pp2는 catalog 미등록 → tag 매칭 불가
- 블록 글씨 크기가 Figma 원본(18px) → 슬라이드 적용(12px) 변환 필요
- 블록 payload schema가 확정 안 됨 → slot 데이터가 제대로 안 채워짐
- fit 루프가 구조/payload 이전에 실행돼서 의미 없음
---
## Phase Y-11: 블록 자산 → payload → layout → fit → 검증 (2026-04-15 확정)
> **원칙:** 구조 → payload → layout → fit 순서. 하드코딩 금지. 전체 프로세스 구조 속에서 정리.
> **핵심:** "프로세스가 결과를 만드는" 구조. 결과를 보고 프로세스를 땜질하지 않음.
### 전체 파이프라인 (Y-11 반영)
```
[1] slide-base.html 로드 (title + footer)
[2] Kei: 꼭지 추출만 (영역/zone 안 함)
[3] 코드: normalized.sections → 대목차 추출 → 꼭지 매핑
[4] 코드: shape 기반 block selection
sub_titles 수 + 구조 타입 → catalog tag 매칭 → 블록 확정
[5] payload 조립
블록별 payload schema에 맞게 normalized data 변환
[6] payload contract 검증 ← 새로 추가
필수 slot 비어있지 않은지, 결론이 body에 안 섞였는지
[7] layout 조립
zone % 기반 + block height:100% + 고정 글씨 크기
[8] fit 루프
overflow → padding/spacing 먼저 → 팝업 분리 → font 축소(최후)
[9] 최종 검증
block 선택 + 글자 누락 + 결론 위치 + overflow + 밀도
```
### Y-11 태스크 목록
**[Y-11a] pp2 블록 자산 정리**
- BEPs/process-product-2col.html → blocks/redesign/ 이동
- slot 구조: left_title, right_title, left_compare(asis/tobe), left_sections[], right_sections[]
- 블록 목적, 사용 조건, 미사용 조건 문서화
- HTML/CSS의 글씨 크기를 슬라이드 적용값(header:13px, mid:12px, body:11px)으로 확정
**[Y-11b] pp2 catalog.yaml 등록 + tag**
- content_pattern: "2-section-asymmetric-compare-table-and-bullets"
- item_count: 2
- content_example에 구조 설명 (문서명 하드코딩 금지)
- slide_font 필드에 슬라이드 적용 글씨 크기 기록
**[Y-11c] block selection 규칙 shape 기반 재정의**
- sub_titles 3개 + 병렬 → prerequisites-3col
- sub_titles 2개 + 비대칭(표+불릿) → pp2
- sub_titles 2개 + 대칭 비교 → cdg
- sidebar/reference → 해당 전용 블록
- pipeline Phase Y + Stage 1.7 block_reference 일관 적용
**[Y-11d] pp2 payload schema + 파이프라인 연결**
- payload 구조 확정 (left_title, right_title, left_compare, left/right_sections)
- _build_slot_data()에서 블록별 payload 생성
- D1/D2 → payload schema 변환 규칙 (D1 only 표 복원 포함)
**[Y-11e] 본문 데이터 정리 규칙**
- 결론/핵심요약 → footer 전용, 블록 payload에 절대 안 섞기
- 표 → left_compare.left_items/right_items
- 불릿 → sections[].bullets
- D1 only 평탄화 → 표 구조 복원
**[Y-11f] zone wrapper 정리**
- zone height: % 기반 (px 아님)
- block wrapper: height:100%
- zone 제목: 13px, margin-bottom:8px
- zone 간 여백: margin-bottom:1%
- 글씨 크기: catalog slide_font 값 (font_scale 아님)
**[Y-11g] 블록 내부 typography 규칙**
- prerequisites-3col: heading 12px, desc 11px, vlabel 14px
- pp2: header 13px, mid_title 12px, body 11px
- bullet 기호, padding-left, text-indent, line-height
- catalog.yaml slide_font에 기록, assembler가 읽어서 적용
**[Y-11h] payload contract 검증 (fit 전 게이트)** ✅ 구현
- 필수 slot 비어있지 않은지
- conclusion이 body payload에 안 들어갔는지
- 같은 데이터가 compare와 section에 중복 주입 안 되는지 ← 추가 필요
---
### Y-11 1차 검증 결과 (2026-04-15, run 20260415_091309)
**block selection: ✅ 통과**
- top: prerequisites-3col (tag_match)
- bottom: process-product-2col (tag_match)
- overflow: top 0, bottom 0
- font_scale: 1.0 (축소 안 함)
**payload/contract/layout: ❌ 미완**
아래 항목들이 남아있음 → Y-12로 정리:
### Phase Y-12: payload 정제 → contract → layout → asset → validation
> block selection은 통과. 이제 "선택된 블록에 정확한 데이터를 정확한 형태로 넣는" 단계.
**[Y-12a] payload normalization (정제)**
- `**` `****` 마크다운 잔여 토큰 제거 (final 출력 전 cleanup gate)
- top desc: `/`로 이어진 plain text → `<div class="bul">• ...</div>` 불릿 구조
- 표 잔여 토큰 (D1: As-is, D1: 구분 등) 정리
- `[핵심요약: ...]` stray note 제거
**[Y-12b] block contract assembly (중복 제거)**
- left_compare에 들어간 데이터가 left_sections에 다시 들어가지 않게
(현재: Analogue 기반 업무의 Digital화가 compare 제목 + section mid-title에 중복)
- compare → left_sections 분리 규칙: 표 항목은 compare에만, 나머지는 sections에만
- pp2 좌우 section packing 규칙 고정
**[Y-12c] layout/style contract** — 공통 레이아웃 계약 + 블록별 내부 contract
공통 들여쓰기 계층 (모든 슬라이드에 적용):
```
대제목 (slide-base) ← left: 52px (고정)
중제목 (zone 제목) ← 대제목과 같은 시작선 (padding-left: 12px)
블록 wrapper ← 중제목보다 안쪽 (padding: 0 12px 0 24px)
소제목 ← 블록 내부 기준선
불릿 ← 소제목보다 안쪽
두번째줄 ← 첫줄 문장 시작선 정렬 (hanging indent: padding-left:14px; text-indent:-14px)
```
블록별 내부 contract:
- p3c: bar 56px, vlabel-area 56px, section left:60px
- pp2: display:flex 좌/우 병렬, 소제목 행 정렬, body padding 6px 16px
- 불릿: `.bul`, `.pp2-body-text`, `.cdg-bullet` 공통 hanging indent
**[Y-12d] asset packaging**
- 배경 텍스처 (svg/bg_slide_texture.png) → base64 내장 또는 data URI
- 화살표 이미지 (arrow) → base64
- final.html이 단독으로 열어도 asset 깨지지 않게
**[Y-12e] final validation**
- 글자 누락 없음
- markdown residue (`**`, `****`) 없음
- 본문 중복 없음 (compare/section 중복)
- 결론이 footer에만 있음
- 좌우 정렬 이상 없음
- asset 깨짐 없음
- overflow 없음
**[Y-12f] 파이프라인 실행 + 참고 비교**
- MDX 03 실행 → ✅ 완료 (run 20260415_105516, 텍스트 누락 없음)
- mdx03_final/final.html과 비교
- 하드코딩 없이 프로세스로 도달한 결과인지 확인
---
## Phase Y-13: Group Schema 계층 (fit 블록 없을 때의 프로세스)
> **근거:** MDX 02를 돌렸을 때, fit한 블록이 없어서 venn-diagram 같은 엉뚱한 fallback으로 빠짐.
> fit 블록이 있으면 Y-11~12 프로세스로 충분. 없을 때 "바로 fallback 아무거나"가 아니라
> **중목차 → 소목차 관계 판단 → group schema → 블록 선택/조합** 경로가 필요.
### 핵심 프로세스
```
[1] section group 추출
## 중목차 기준으로 하나의 section group으로 묶기
(이미 extract_major_sections()가 sub_titles를 제공)
[2] group relation classifier
같은 중목차 안의 소목차들의 관계 판단:
- 병렬 목표 (안전/생산성/소통) → parallel_3
- 비교 (과정혁신/결과변화) → compare_2
- 순서/프로세스 → process_list
- 독립 카드 → independent_cards
- 보조 설명 → summary_with_visual
Kei가 판단 지원 (코드만으로 어려움)
[3] group schema enum
relation → schema 변환:
- parallel_3 → 3열 카드/표/요약
- compare_2 → 2열 비교
- process_list → 단계/변화 목록
- summary_with_visual → 텍스트+이미지
[4] schema → block matcher
schema에 맞는 블록을 catalog에서 찾기
├── 정확히 맞는 블록 있음 → 사용
├── 유사 블록 → Sonnet redesign → blocks/redesign/ 저장
└── 없음 → composition (메인 블록 + 보조 블록 조합)
fallback 아무거나 금지
[5] payload 조립 (group schema 기준)
group schema → block payload 변환
이미지 포함 여부, bullet 정리, 표 축약, popup 분기
[6] layout / fit
zone 비율, padding, indent, overflow, popup fallback
```
### Y-13 진행 결과 (2026-04-15)
**Y-13a~d: 구현 완료**
구현된 것:
- `classify_group_relations()`: D1: 개수로 병렬 항목 감지 + 키워드 기반 schema 분류
- schema 세분화: `compare_asymmetric_2col` (표+비대칭), `process_plus_visual` (불릿+시각) 추가
- 기존 `process_list` 유지 (삭제 안 함, 점진적 추가)
- `GROUP_SCHEMA_BLOCK_MAP`: 새 schema → 블록 후보 매핑 추가
- pipeline + block_reference에서 tag 실패 시 schema 후보로 선택
**MDX 03 회귀 검증 ✅:**
```
top: parallel_3 → prerequisites-3col ✅ (이전과 동일)
bottom: compare_asymmetric_2col → process-product-2col ✅ (pp2 유지)
```
**MDX 02 현재 상태:**
```
top: parallel_3_with_image → prerequisites-3col (이미지 배치 미해결)
bottom: compare_2 → compare-detail-gradient (<DxEffect> 감지 안 됨 → process_plus_visual 미적용)
```
### 미해결 + 방향
**1. tag_match와 schema_match 동등 비교 (향후)**
- 현재: tag 실패 시에만 schema fallback
- 향후: 둘 다 점수화해서 동등 비교
- schema_match를 block selection의 1급 기준으로 승격 예정
**2. GROUP_SCHEMA_BLOCK_MAP → 선언형 이동 (향후)**
- 현재: section_parser.py에 dict 하드코딩
- 향후: catalog.yaml 또는 별도 schema 파일로 선언형 관리
**3. Kei는 보조 힌트**
- 구조적 근거(D1: 수, sub_titles 수, 키워드) = 1순위
- Kei = 보조 의미 힌트 (하드 의존 안 함)
**4. composition 경로 (향후)**
- 단일 블록으로 안 되는 경우에만
- 지금은 먼저 단일 블록 경로를 정교화
- composition은 검증 결과를 보고 필요한 경우에만 추가
**5. MDX 02 bottom 분류 정교화** → ✅ 해결
- `<DxEffect />`가 normalized에서 제거됨 → sub_titles 키워드("기대효과")로 분기
- content + sub_text 합쳐서 키워드 검색하도록 수정
- MDX 02 bottom: `process_plus_visual` → checklist-dark 후보
**6. section_parser 책임 분리 (향후)**
- 현재: group 추출 + relation 분류 + schema enum + block 후보 다 있음
- 향후: group_schema.py 별도 모듈로 분리 예정
**7. 메인/popup 2단 표현 계약 (파이프라인 공통)**
- 콘텐츠가 zone에 다 안 들어갈 때의 공통 규칙
- MDX의 `<DxEffect />` 같은 시각 컴포넌트, 큰 표, 과다 불릿 대응
- 구조:
```
메인 HTML (zone 안):
- 존 크기에 맞는 요약형 (2~3행 요약, 핵심 포인트)
- "자세히보기" 링크/버튼
popup HTML (별도 파일):
- 전체 표, 전체 bullet, 컴포넌트 원형 구조
- 예: detail_dx_effect.html (run 폴더 안)
```
- popup 분기 조건:
- 표가 크다 (행 5개 이상)
- 시각 컴포넌트가 있다 (`<DxEffect />` 등)
- 존 높이에 안 맞는다 (overflow)
- 본문에 넣으면 가독성이 깨진다
- 검증 규칙:
- popup으로 보낸 경우 본문엔 최소 요약이 남아 있어야 함 (빈칸 금지)
- 링크 대상 파일이 실제 생성돼 있어야 함
- Astro 컴포넌트 연결:
- `<DxEffect />` → `samples/src/components/dx.astro`에 연결
- 파이프라인은 Astro를 실행하지 않음
- dx.astro를 읽어서 HTML로 변환 → popup 파일로 생성
- 메인에는 요약형 카드 + 자세히보기 링크
- fit 루프와의 관계:
- overflow 발생 → font 축소(최후) 전에 popup 분리를 먼저 시도
- 순서: padding 조정 → popup 분리 → font 1단계 축소
**8. tag_match / schema_match 동등 비교 (향후)**
- 현재: tag 실패 시에만 schema fallback
- 향후: 둘 다 점수화해서 동등 비교
- schema_match를 block selection의 1급 기준으로 승격
### 의존 관계
```
Y-12 (payload/layout) → Y-13 (group schema) → Y-14 (popup 2단 표현)
MDX 03: Y-12로 충분 ✅ (fit 블록 있음, 회귀 검증 통과)
MDX 02: Y-13 분류 완료 ✅, 블록 선택 후 popup 경로 필요 (Y-14)
MDX 01: Type A — 별도 작업
```
File renamed without changes.
File renamed without changes.
File renamed without changes.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

-806
View File
@@ -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>
-401
View File
@@ -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>
-328
View File
@@ -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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

-206
View File
@@ -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>
-222
View File
@@ -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>
-4
View File
@@ -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

-4
View File
@@ -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

-4
View File
@@ -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

-4
View File
@@ -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

-402
View File
@@ -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>
+29
View File
@@ -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__/
+131
View File
@@ -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 순환 궤도 |
+674
View File
@@ -0,0 +1,674 @@
# Figma → HTML 블록 변환 프로세스
> 2026-04-07 확립. Figma 디자인을 design_agent 블록으로 변환하는 정확한 방법론.
---
## 1. 전체 워크플로우
```
[Step 1] Figma API로 파일 구조 추출
↓
[Step 2] 프레임별 렌더링 이미지(PNG) 다운로드
↓
[Step 3] 노드별 상세 데이터 추출 (좌표, 색상, 폰트, 크기)
↓
[Step 4] 디자인 언어 분석 (공통 패턴 vs 콘텐츠 전용 구분)
↓
[Step 5] 블록 설계 (슬롯, 동적 규칙, schema)
↓
[Step 6] 수학적 계산 (Figma 좌표 → 스케일 → CSS값)
↓
[Step 7] HTML/CSS 구현
↓
[Step 8] 비교 리뷰 (Figma PNG vs HTML, 같은 폭으로 위/아래 배치)
↓
[Step 9] 피드백 반영 → Step 6~8 반복
↓
[Step 10] Jinja2 템플릿화 + catalog.yaml 등록
```
---
## 2. Figma API 사용법
### 2.1 파일 구조 가져오기
```bash
curl -s -H "X-Figma-Token: {TOKEN}" \
"https://api.figma.com/v1/files/{FILE_KEY}" \
| python -m json.tool
```
### 2.2 특정 노드 상세 데이터
```bash
curl -s -H "X-Figma-Token: {TOKEN}" \
"https://api.figma.com/v1/files/{FILE_KEY}/nodes?ids={NODE_IDS}&geometry=paths"
```
### 2.3 노드 이미지 렌더링 (PNG)
```bash
curl -s -H "X-Figma-Token: {TOKEN}" \
"https://api.figma.com/v1/images/{FILE_KEY}?ids={NODE_IDS}&format=png&scale=2"
```
- `scale=2`: 2배 해상도로 다운로드 (선명도 확보)
- 응답의 `images` 객체에 각 노드 ID별 S3 URL 제공
### 2.4 추출해야 하는 핵심 데이터
| 데이터 | API 필드 | 용도 |
|-------|---------|------|
| 위치 | `absoluteBoundingBox.x, .y` | 요소 간 관계 계산 |
| 크기 | `absoluteBoundingBox.width, .height` | 스케일 계산 |
| 텍스트 | `characters` | 콘텐츠 확인 |
| 폰트 | `style.fontFamily, .fontSize, .fontWeight` | 타이포그래피 |
| 색상 | `fills[].color` | 색상 팔레트 |
| 테두리 | `strokes[], strokeWeight` | 박스 스타일 |
| 라운드 (단일) | `cornerRadius` | 4꼭짓점 동일 border-radius |
| **라운드 (개별)** | **`rectangleCornerRadii`** | **[TL, TR, BR, BL] 꼭짓점별 다른 반지름. pill-shape 등 비대칭 라운드 감지** |
| **형상 경로** | **`fillGeometry[].path`** | **SVG path 문자열. `C`/`Q`/`A` 명령어 존재 시 곡선 형상 → `clip-path: path()` 또는 SVG로 구현** |
| **호/부채꼴** | **`arcData`** | **원호, 부채꼴 등 호 형상 파라미터** |
| 이미지 | `fills[].imageRef` | 이미지 자산 식별 |
---
## 3. 수학적 계산 (핵심)
### 3.1 스케일 팩터
```
슬라이드 콘텐츠 폭 = 1280px - padding(40px × 2) = 1200px
scale = 1200 / figma_frame_width
```
| Figma 프레임 | 폭 | 스케일 |
|-------------|-----|--------|
| Frame 2 | 1808px | 0.6637 |
| Frame 3 | 1807px | 0.6641 |
| Frame 4 | 3848px | 0.3118 |
### 3.2 요소 간 정렬 계산
**절대 원칙: Figma 좌표 차이값 → 스케일 적용 → CSS값**
```python
# 예: 리본 접힘선과 박스 테두리 정렬
badge_y = 1431 # Figma에서 badge 이미지 top Y
box_y = 1449 # Figma에서 box top Y
fold_offset = box_y - badge_y # = 18px (Figma 기준)
# 스케일 적용
fold_offset_css = round(fold_offset * scale) # = 12px (CSS)
```
**금지: "좀 더 올려볼게요" 식의 시행착오 px 조정**
### 3.3 이미지 자산 크기 계산
```python
# Figma 원본 크기에 스케일 적용
ribbon_width_css = round(badge_img_width * scale)
ribbon_height_css = round(badge_img_height * scale)
# 비율 계산 (CSS에서 width만 지정하면 height는 자동)
aspect_ratio = badge_img_width / badge_img_height
```
### 3.4 패딩/여백 계산
```python
# 리본이 박스 안에 들어오는 높이 = 리본 전체 높이 - 접힘선 오프셋
ribbon_inside_box = ribbon_height_css - fold_offset_css
# 박스 상단 패딩 = 리본 침입 높이 + 여유
box_padding_top = ribbon_inside_box + 6 # 6px 여유
```
### 3.5 실제 계산 예시 (Frame 2)
```
입력 (Figma 원본):
badge 이미지: 508×94px, y=1431
box: y=1449
frame width: 1808px
계산:
scale = 1200/1808 = 0.6637
ribbon_w = 508 × 0.6637 = 337px
ribbon_h = 94 × 0.6637 = 62px
fold_offset = (1449-1431) × 0.6637 = 12px
ribbon_below_fold = 62 - 12 = 50px
box_padding_top = 50 + 6 = 56px
CSS 출력:
.ribbon { width: 337px; top: -12px; }
.box { padding-top: 56px; }
```
---
## 4. 이미지 자산 처리
### 4.1 CSS로 만들면 안 되는 것
| 요소 | 이유 | 처리 |
|------|------|------|
| 3D 리본/두루마리 | 입체감, 그림자, 곡면 → CSS 불가 | Figma에서 PNG 추출 |
| 복잡한 그라디언트 배경 | 다중 정지점, 비선형 → CSS 근사 불가 | 이미지 사용 |
| 아이콘 이미지 | 디자이너가 만든 고유 자산 | 원본 이미지 사용 |
### 4.2 CSS로 만들 수 있는 것
| 요소 | CSS 구현 |
|------|---------|
| 단색/2색 그라디언트 배경 | `linear-gradient()` |
| 둥근 모서리 테두리 박스 | `border + border-radius` |
| 텍스트 스타일 | `font-size, font-weight, color` |
| 그리드/플렉스 레이아웃 | `display: grid / flex` |
| 구분선 | `border` or `background` |
### 4.3 이미지 추출 및 저장
```bash
# Figma API로 특정 노드 이미지 추출
curl -s -H "X-Figma-Token: {TOKEN}" \
"https://api.figma.com/v1/images/{FILE_KEY}?ids={NODE_ID}&format=png&scale=2"
# 다운로드 → static/figma-assets/ 에 저장
curl -s -o static/figma-assets/{name}.png "{S3_URL}"
```
저장 위치: `static/figma-assets/`
---
## 5. 비교 리뷰 페이지 작성법
### 5.1 레이아웃
```
같은 폭으로 위/아래 배치 (좌/우 아님 — 크기 차이 문제)
┌─ 빨간 테두리 ──────────────┐
│ Figma Original (PNG) │
└─────────────────────────────┘
─ 구분선 ─
┌─ 초록 테두리 ──────────────┐
│ HTML Block │
└─────────────────────────────┘
```
### 5.2 HTML 스케일링
```css
.html-inner {
width: 1280px; /* 슬라이드 원본 크기 */
transform-origin: top left;
transform: scale(0.74); /* 960px 컨테이너에 맞춤: 960/1280 */
}
```
### 5.3 비교 리뷰 파일 위치
`data/figma_ref/comparison.html`
---
## 6. Jinja2 템플릿 변환 규칙
### 6.1 고정값 → 변수
```html
<!-- Figma 원본의 텍스트 → Jinja2 변수 -->
<span>정책 달성</span> → <span>{{ badge_title }}</span>
<span>Engn. Solution</span> → <span>{{ left_title }}</span>
```
### 6.2 반복 요소 → 루프
```html
<!-- N개 카드 → for loop -->
{% for card in cards %}
<div class="card">{{ card.title }}</div>
{% endfor %}
```
### 6.3 이미지 자산 → 슬롯
```html
<!-- 리본 이미지: 색상에 따라 다른 자산 사용 가능 -->
<img src="{{ ribbon_image | default('figma-assets/badge_solution.png') }}">
```
### 6.4 계산된 CSS → CSS 변수
```html
<!-- 수학적 계산 결과를 CSS 변수로 -->
<div style="--ribbon-width: {{ ribbon_width }}px; --fold-offset: {{ fold_offset }}px;">
```
---
## 7. 디자인 언어 vs 콘텐츠 전용 구분
### 디자인 언어 (블록에 포함, 재사용 가능)
- 색상 팔레트 (warm 테마: 브라운, 틸, 베이지)
- 타이포그래피 위계 (크기, 굵기 단계)
- 레이아웃 구조 (2열 비교, N열 카드 등)
- 장식 요소 (3D 리본, 둥근 컨테이너)
### 콘텐츠 전용 (블록에 포함하지 않음)
- 특정 텍스트 ("디지털전환은 사용자...")
- 특정 아이콘 이미지 (brain, thunder 등)
- 도메인 전문 용어 (DfMA, Engn. Solution)
---
## 8. 파일 구조
```
design_agent/
├── static/figma-assets/ ← Figma에서 추출한 이미지 자산
│ ├── badge_policy.png (틸 3D 리본)
│ ├── badge_solution.png (빨간 3D 리본)
│ ├── box_policy_container.png
│ ├── box_solution_cards.png
│ └── arrow_asis_tobe.png (As-Is→To-Be 화살표)
├── data/figma_ref/ ← 비교 리뷰용
│ ├── comparison.html (Figma vs HTML 비교 페이지 — 전체 블록)
│ ├── frame2_1-5.png (Page 1 Figma 원본)
│ ├── frame3_1-35.png
│ ├── frame4_1-49.png
│ ├── strip_table.png (Page 2 필수조건 테이블)
│ ├── checklist_dark.png (Page 3 체크리스트)
│ ├── system_2col.png (Page 3 시스템 구성)
│ └── cycle_orbit.png (Page 4 순환 궤도)
├── templates/blocks/cards/ ← 카드 블록 (Figma 신규 5개)
│ ├── hero-icon-cards.html (Page 1 — 히어로 + N열 아이콘 카드)
│ ├── compare-2col-badge.html (Page 1 — 3D 리본 배지 + 2열 비교)
│ ├── compare-detail-gradient.html (Page 1 — 그라디언트 상세 2열 비교)
│ ├── category-strip-table.html (Page 2 — 컬러 스트립 N열 테이블)
│ └── system-2col-center.html (Page 3 — 중앙 라벨 + 좌/우 항목)
├── templates/blocks/emphasis/ ← 강조 블록 (Figma 신규 1개)
│ └── checklist-dark.html (Page 3 — 체크 아이콘 + 제목:설명 리스트)
├── templates/blocks/visuals/ ← 비주얼 블록 (Figma 신규 1개)
│ └── cycle-orbit.html (Page 4 — 3D 원 투영 순환 궤도 다이어그램)
├── FIGMA-DESIGN-LANGUAGE.md ← 디자인 언어 분석 결과
├── FIGMA-EXTRACTION.md ← 이 문서
└── PHASE-FIGMA-BLOCKS.md ← 블록 설계 명세
```
---
## 9. 고급 레이아웃 패턴
### 9.1 좌/우 열 섹션 Y선 정렬 (CSS Grid 행 공유)
2열 비교에서 좌/우 섹션 제목이 같은 Y선에 있어야 할 때:
**문제**: 각 열을 독립 flex-column으로 만들면, 좌측 섹션 본문이 길면 우측 다음 섹션이 밀림.
```
flex-column (잘못):
좌: [제목1] [긴본문] [제목2]
우: [제목1] [짧은본문] [제목2] ← 제목2가 좌측과 Y가 다름
```
**해결**: CSS Grid 2열 × N행으로 행을 공유하면 자동 정렬.
```css
.block {
display: grid;
grid-template-columns: 1fr 1fr; /* 2열 */
grid-template-rows: auto auto auto auto; /* 헤더 + N행 */
}
```
```
Grid (올바름):
[좌 헤더] [우 헤더] ← Row 0
[좌 섹션1] [우 섹션1] ← Row 1 (행 높이 = max(좌,우))
[좌 섹션2] [우 섹션2] ← Row 2 (Y선 자동 정렬!)
```
**실제 계산 (Frame 4)**:
```
Figma Y좌표:
Row 1: 좌 1166, 우 1166 → 0px 차이 (이미 정렬)
Row 2: 좌 1529, 우 1467 → 62px 차이 (Grid가 해결)
Row 3: 좌 1845, 우 1845 → 0px 차이 (이미 정렬)
원인: Row 1 좌측에 As-Is→To-Be 구조가 있어서 본문이 62px 더 높음
```
### 9.2 As-Is → To-Be 수평 서브 레이아웃
한 섹션 안에서 변환 전/후를 수평 배치할 때:
```html
<div class="asis-tobe">
<div class="asis">
<div class="bullet">이전 상태 1</div>
<div class="bullet">이전 상태 2</div>
</div>
<img src="arrow.png" class="arrow" alt="→">
<div class="tobe">
<div class="bullet">변환 후 1</div>
<div class="bullet">변환 후 2</div>
</div>
</div>
```
```css
.asis-tobe { display: flex; align-items: center; gap: 8px; }
.asis, .tobe { flex: 1; }
.arrow { width: 60px; height: auto; flex-shrink: 0; }
```
**Figma 좌표로 검증**:
```
As-Is: x=2737, w=539
Arrow: x=3375, w=252
To-Be: x=3687, w=672
→ 세 요소가 같은 Y(1269)에 수평 배치됨을 좌표로 확인
```
### 9.3 3D 리본/두루마리 배지 정렬 공식
리본 이미지의 접힘선(fold-back)이 박스 테두리와 정확히 일치해야 할 때:
```
┌── 리본 이미지 ──────────────┐
│ 접힘 삼각형 (fold) │ ← fold_offset (이미지 top에서)
│ 리본 본체 │
│ │
└──────────────────────────────┘
════════════════════════════════ ← 박스 top border (여기에 fold가 일치해야 함)
┌── 박스 ──────────────────────┐
│ padding-top = ribbon_below │
│ 콘텐츠 시작 │
계산:
fold_offset = (box_y - badge_y) × scale → CSS: top 값
ribbon_below = ribbon_height - fold_offset → 박스 안 침입 높이
box_padding_top = ribbon_below + 여유(6px) → 콘텐츠 겹침 방지
```
**핵심**: 리본을 올리거나 내리는 게 아니라, **박스의 위치를 계산**하는 것.
- `top: -fold_offset` → 리본 접힘선 = 박스 top border
- 리본은 그대로, 박스와의 관계만 수학적으로 결정
---
## 10. 순환 궤도 다이어그램 (cycle-orbit) 수학 공식
### 10.1 핵심 개념: 3D 원 → Z축 기울임 → 2D 투영
타원이 아니라 **3D 원을 Z축으로 기울인 것**. 토성의 고리와 같은 원리.
```
3D 공간: 2D 투영 (화면):
y y
| / z |
| / |
| / θ=80° (기울임) |
|/______ x |______ x
원 (R=400) 타원 (rx=400, ry=69)
→ Z축으로 80° 뒤로 눕힘 → y축이 cos(80°)=0.1736으로 압축
```
### 10.2 기본 공식
```python
import math
R = 400 # 3D 원 반지름
cx, cy = 500, 200 # 원 중심 (SVG 좌표)
theta = 80 # Z축 기울임 각도 (°)
tilt = math.radians(theta)
# 투영된 타원 파라미터
rx = R # x축은 변하지 않음
ry = R * math.cos(tilt) # y축만 cos(θ)로 압축
# 원 위의 한 점 (각도 α)을 2D로 투영
def project(alpha_deg):
a = math.radians(alpha_deg)
x = cx + R * math.cos(a)
y = cy + R * math.sin(a) * math.cos(tilt)
return round(x), round(y)
```
### 10.3 N개 노드 배치
```python
N = 3 # 노드 개수 (3, 4, 5, 6 모두 가능)
start_angle = 270 # 상단부터 시작
# 기본 간격: 360°/N
base_gap = 360 / N # 3개→120°, 4개→90°, 5개→72°
# 사이각 축소 (2/3): 양끝이 너무 벌어지는 것 방지
gap = base_gap * 2 / 3
# 각 노드 각도 계산 (상단 노드 고정, 나머지가 원 위에서 이동)
angles = []
for i in range(N):
if i == 0:
angles.append(start_angle) # 상단 고정
else:
# 상단 기준 좌/우 대칭 배치
# 홀수 인덱스: 좌측 (반시계)
# 짝수 인덱스: 우측 (시계)
if i % 2 == 1: # 좌측
step = (i + 1) // 2
angles.append(start_angle - gap * step)
else: # 우측
step = i // 2
angles.append(start_angle + gap * step)
# 각 노드의 2D 좌표
for i, angle in enumerate(angles):
x, y = project(angle)
print(f'Node {i}: angle={angle:.0f}°, pos=({x}, {y})')
```
### 10.4 N별 계산 예시
| N | 기본 간격 | 축소 간격 (2/3) | 노드 각도 |
|---|---------|--------------|---------|
| 3 | 120° | 80° | 270°, 190°, 350° |
| 4 | 90° | 60° | 270°, 210°, 330°, 150° |
| 5 | 72° | 48° | 270°, 222°, 318°, 174°, 366° |
| 6 | 60° | 40° | 270°, 230°, 310°, 190°, 350°, 150° |
### 10.5 화살표 >> 위치 계산
화살표는 **두 노드 사이 호의 1/3, 2/3 지점**에 배치. 방향은 **접선 방향**.
```python
def arrow_positions(angle1, angle2):
"""두 노드 사이 호에 화살표 2개 배치"""
mid1 = angle1 + (angle2 - angle1) * 0.35
mid2 = angle1 + (angle2 - angle1) * 0.65
pos1 = project(mid1)
pos2 = project(mid2)
# 접선 방향 (화살표 회전각)
def tangent_angle(alpha):
a = math.radians(alpha)
tx = -math.sin(a)
ty = math.cos(a) * math.cos(tilt)
return math.degrees(math.atan2(ty, tx))
rot1 = tangent_angle(mid1)
rot2 = tangent_angle(mid2)
return (pos1, rot1), (pos2, rot2)
```
### 10.6 설명 텍스트 배치 규칙
**Figma 원본 분석 결과**: 설명은 원 바깥 방향(Q꼬리)이 아니라, **노드 이름 옆에 수평으로** 배치.
```
규칙:
원 중심 기준 좌측 노드 (angle > 90° and < 270°):
→ 설명이 노드 이름의 좌측에 (text-anchor: end)
원 중심 기준 우측 또는 상단 노드 (나머지):
→ 설명이 노드 이름의 우측에 (text-anchor: start)
텍스트 구조:
[설명 제목] ← 이름과 같은 Y선, 옆에 수평
[노드 아이콘 원] • 불릿 1 ← 제목 아래 들여쓰기
[라벨] (원 아래) • 불릿 2
[서브라벨] (라벨 아래)
```
**실제 배치 (3노드 예시)**:
```
👥 사람(역량) 혁신적 사고방식 ← 우측
• 창의적 문제 해결
• 사용자 중심 접근
Digital 기술과... 🖥 기술(디지털) 자연(여건) 📋 지속적 투자 의지 ← 우측
• 건설 전문 지식 ↑ • 실행 추진력
• 최신 기술 좌측 • 변화를 통한 가치 창출
```
```python
def desc_position(node_x, node_y, node_angle, cx):
"""설명 텍스트 위치 계산"""
if node_x < cx: # 왼쪽 노드
desc_x = node_x - 36 # 노드 좌측
anchor = 'end'
else: # 오른쪽 또는 상단 노드
desc_x = node_x + 36 # 노드 우측
anchor = 'start'
desc_y = node_y + 37 # 라벨과 같은 높이 (아이콘 아래)
return desc_x, desc_y, anchor
```
### 10.7 SVG 구조
```xml
<svg viewBox="0 0 1000 380">
<!-- 1. 타원 궤도 -->
<ellipse cx="500" cy="200" rx="400" ry="69"/>
<!-- 2. 화살표 >> (N개 구간 × 2개씩) -->
<text transform="rotate(각도)">»</text>
<!-- 3. 노드 (원 + 아이콘 + 라벨) -->
<circle cx="x" cy="y" r="26"/>
<text>아이콘</text>
<text>라벨</text>
<!-- 4. 설명 텍스트 -->
<text>설명 제목</text>
<text>• 불릿</text>
</svg>
```
### 10.8 중요: 파이프라인에서 좌표 계산
이 블록은 **Jinja2 템플릿에 좌표를 하드코딩하면 안 됨**.
파이프라인(Python)에서 N, R, θ를 받아 좌표를 계산한 뒤 템플릿에 전달해야 함.
```python
# pipeline에서 호출
def calculate_orbit(n_nodes, radius=400, tilt_deg=80):
"""N개 노드의 SVG 좌표와 화살표 위치를 계산"""
cx, cy = 500, 200
tilt = math.radians(tilt_deg)
gap = (360 / n_nodes) * 2 / 3
nodes = []
arrows = []
# ... 위 공식 적용
return {
'ellipse': {'cx': cx, 'cy': cy, 'rx': radius, 'ry': round(radius * math.cos(tilt))},
'nodes': nodes, # [{x, y, angle}, ...]
'arrows': arrows, # [{x, y, rotation}, ...]
}
```
---
## 11. 실수 방지 (Anti-patterns)
### 11.1 절대 하면 안 되는 것
| Anti-pattern | 왜 안 되는지 | 올바른 방법 |
|-------------|------------|-----------|
| px 시행착오 조정 ("좀 더 올려볼게") | 3번 이상 실패, 시간 낭비 | Figma 좌표에서 수학적 계산 |
| 3D 효과를 CSS로 재현 | 평면적이라 품질 차이 심각 | Figma에서 PNG 추출 |
| 비교 리뷰를 좌/우 배치 | 크기 차이로 비교 불가 | 위/아래 같은 폭으로 배치 |
| Jinja2 템플릿을 브라우저에서 직접 열기 | 변수 미렌더, 이미지 경로 깨짐 | comparison.html 또는 FastAPI로 확인 |
| 독립 flex-column으로 2열 비교 | 행 정렬 안 됨 | CSS Grid 행 공유 |
| 느낌으로 폰트/색상 설정 | Figma와 다른 결과물 | Figma API에서 정확한 값 추출 |
### 11.2 반드시 해야 하는 것
| 원칙 | 이유 |
|------|------|
| CSS 주석에 계산 근거 기록 | 나중에 왜 이 값인지 추적 가능 |
| 비교 리뷰 후 진행 | 디자인 차이를 사전에 발견 |
| 이미지 자산은 `static/figma-assets/`에 저장 | FastAPI가 서빙, 경로 일관성 |
| `comparison.html`에 모든 프레임 포함 | 한 페이지에서 전체 리뷰 가능 |
| Figma 노드 ID 기록 | 나중에 업데이트된 디자인 재추출 가능 |
---
## 12. Figma 소스 정보
### 현재 등록된 Figma 파일
| 항목 | 값 |
|------|---|
| File Key | `9S6LsQyO6zlRxtiqZccOUM` |
**Page 1 (0:1)** — 기본 디자인
| 블록 | Node ID | 설명 |
|------|---------|------|
| hero-icon-cards | `1:5` | Frame 2 (Solution 제작 목표) |
| compare-2col-badge | `1:35` | Frame 3 (정책 달성) |
| compare-detail-gradient | `1:49` | Frame 4 (과정 vs 결과 혁신) |
| Badge 빨간 리본 | `1:33` | image 4019 |
| Badge 틸 리본 | `1:43` | image 2197 |
| Arrow As-Is→To-Be | `1:67` | image 2645 |
| Box 빨간 테두리 | `1:12` | Rectangle 42894 |
| Box 틸 테두리 | `1:37` | Rectangle 42598 |
**Page 2 (15:2)** — 프레젠테이션 슬라이드
| 블록 | Node ID | 설명 |
|------|---------|------|
| category-strip-table | `17:1264` | 001_개요 우측 하단 (필수조건 3열) |
**Page 3 (18:8204)** — 컴포넌트
| 블록 | Node ID | 설명 |
|------|---------|------|
| checklist-dark | `18:8351` | f5 (체크리스트 6행) |
| system-2col-center | `18:8405` | f8 (System 구성 H/W vs S/W) |
**Page 4 (29:439)** — 순환 다이어그램
| 블록 | Node ID | 설명 |
|------|---------|------|
| cycle-orbit | `29:439` | DX 시행 필수 요건 (3노드 순환) |
---
## 13. 체크리스트
새 Figma 프레임을 블록으로 변환할 때:
- [ ] Figma API로 노드 데이터 추출 (좌표, 크기, 색상, 폰트)
- [ ] PNG 렌더링 다운로드 (scale=2)
- [ ] 복잡한 비주얼 요소 식별 → 이미지로 추출 (CSS로 만들지 않음)
- [ ] 스케일 팩터 계산 (1200 / frame_width)
- [ ] 핵심 정렬 포인트 수학적 계산 (좌표 차이 × 스케일)
- [ ] CSS 값 도출 (계산 근거를 주석으로 기록)
- [ ] 비교 리뷰 페이지에 추가 (위/아래 같은 폭)
- [ ] 사용자 피드백 확인
- [ ] Jinja2 템플릿 변환 (고정값→변수, 반복→루프)
- [ ] catalog.yaml 등록
+97
View File
@@ -0,0 +1,97 @@
# Figma 도형 + 그라데이션 처리
## 핵심 원리
Figma에서 도형 작업 방식:
1. 박스(프레임/컨테이너) 안에 도형을 만든다 (border-radius + gradient)
2. **박스를 회전**시킨다 (도형 자체가 아니라)
3. 박스가 돌면 그 안의 도형도 같이 돌아간다 → border-radius와 gradient가 함께 회전
## CSS 구현 구조
**도형 자체에 transform 적용 금지. 반드시 래퍼(wrapper) 컨테이너에 적용한다.**
```html
<div class="wrapper"> <!-- 이게 회전한다 -->
<div class="shape"></div> <!-- 이건 건드리지 않는다 -->
</div>
```
```css
.wrapper {
position: absolute;
/* pre-rotation position/size */
transform: rotate(X deg); /* Figma gradient 각도의 부호 반대 */
}
.shape {
width: 100%; height: 100%;
border-radius: <Figma 값 그대로>;
background: linear-gradient(90deg, <Figma 색상 그대로>);
/* CSS 90deg = Figma 0deg = 왼→오, 이것이 "기본 상태" */
}
```
## 프로세스
```
1. Figma gradient 각도 확인 (예: 90°)
2. 기본 상태(Figma 0°) 정의
- shape: border-radius = Figma 값, gradient = CSS 90deg (left→right) + Figma 색상
3. 래퍼의 pre-rotation 위치/크기 계산
- 90° 회전이면 width/height 교환
- 래퍼 중심 = 최종 중심 (Figma position + size/2)
4. 래퍼에 transform: rotate() 적용
- Figma +90° → CSS rotate(-90deg) (부호 반대, CSS는 CW 기준)
```
## 예시: 42335
Figma 데이터:
- 위치: (574, 45), 크기: 205×424 (tall, 최종 상태)
- border-radius: 102 0 0 102 (왼쪽 둥근, 기본 상태 기준)
- gradient: 90deg (시계 반대 방향 90도 회전됨)
Pre-rotation 계산:
- 최종 중심: (676.5, 257)
- Pre-rotation 크기: 424×205 (swap)
- Pre-rotation top-left: (464.5, 154.5)
CSS:
```html
<div class="wrapper-42335">
<div class="shape-42335"></div>
</div>
```
```css
.wrapper-42335 {
position: absolute;
left: 464.5px; top: 154.5px;
width: 424px; height: 205px;
transform: rotate(-90deg);
}
.shape-42335 {
width: 100%; height: 100%;
border-radius: 102px 0 0 102px;
background: linear-gradient(90deg, rgba(217,162,104,1) 37%, rgba(220,103,14,0) 89%);
}
```
## Figma gradient 각도 → CSS transform 변환
Figma는 시계방향이 양수, CSS transform은 시계방향이 양수이지만:
- Figma gradient 각도는 **도형 내부 방향** 기준
- 박스를 회전시키는 관점에서는 부호가 반대
```
Figma gradient 0° → CSS transform: rotate(0deg) (회전 없음)
Figma gradient +90° → CSS transform: rotate(-90deg) (반시계)
Figma gradient -90° → CSS transform: rotate(+90deg) (시계)
Figma gradient ±180° → CSS transform: rotate(180deg)
```
## 주의사항
- Figma 데이터가 유일한 소스. PNG는 픽셀 분석으로만 교차 검증.
- 이미지를 눈으로 보고 방향 판단 금지 (멀티모달 해석 불안정).
- border-radius와 gradient를 각각 수동 계산하지 않는다. **래퍼를 회전**시킨다.
- 작동하는 값은 건드리지 않는다. 사용자가 지적한 것만 수정한다.
+362
View File
@@ -0,0 +1,362 @@
# 수학 공식 레퍼런스
Figma → HTML 변환에서 사용하는 모든 수학 공식. 이 문서의 공식만 사용하고, 직관/감으로 보정하지 않는다.
---
## §1. 스케일 팩터
### 정의
```
S = 1280 / W_원본_프레임
```
`1280`은 16:9 슬라이드 가로 폭. 모든 프레임은 가로 1280에 맞춰 축소된다.
### 적용 방법: CSS transform scale (권장)
```html
<div class="block">
<div class="inner"> <!-- 원본 W × H 좌표계 그대로 -->
... 모든 요소 (Figma 원본 px) ...
</div>
</div>
```
```css
.block {
width: 1280px;
height: {H × S}px;
overflow: hidden;
position: relative;
}
.inner {
position: absolute;
left: 0; top: 0;
width: {W}px; /* 원본 그대로 */
height: {H}px;
transform: scale({S});
transform-origin: top left;
}
```
**왜 transform이 좋은가:**
- 위치/크기/폰트/그림자/스트로크/blur radius 모두 한 번에 균일 축소
- 매 값 수동 곱셈하면 누적 오차 + 검증 어려움
- transform은 GPU 가속, 계산 정확
### 적용 대상
| 적용 | 미적용 |
|------|------|
| 위치 (x, y) | 색상 |
| 크기 (width, height) | 그라데이션 방향 (각도 그대로) |
| 폰트 크기 | 그라데이션 stop 퍼센트 (그대로) |
| 스트로크 너비 | 폰트 굵기 |
| 간격 (gap, padding) | line-height 비율 (1.5 등) |
| 그림자 (blur, offset) | border-radius 비율 (50% 등) |
| border-radius (px) | |
---
## §2. SVG `<linearGradient>` → CSS `linear-gradient()`
### 입력
SVG에서:
```xml
<linearGradient id="..." gradientUnits="userSpaceOnUse"
x1="..." y1="..." x2="..." y2="...">
<stop offset="0" stop-color="..."/>
<stop offset="1" stop-color="..."/>
</linearGradient>
```
### 변환 공식
```
1. dx = x2 - x1
dy = y2 - y1
L_svg = √(dx² + dy²)
2. SVG 벡터 각도 (y-down 좌표계, 0°=오른쪽, +CW):
svg_angle = atan2(dy, dx) (단위: 라디안)
3. CSS 각도 (12시 방향=0°, +CW):
css_angle = degrees(svg_angle) + 90
css_angle = css_angle mod 360
4. CSS 그라데이션 선 길이 (W×H 박스 안):
α = radians(css_angle)
L_css = |W × sin(α)| + |H × cos(α)|
5. 박스 중심의 t 파라미터 (SVG 벡터 위, 0=시작, 1=끝):
t_center = ((W/2 - x1)·dx + (H/2 - y1)·dy) / L_svg²
6. CSS 0% / 100%가 SVG t-space의 어디에 매핑되는지:
half = (L_css / 2) / L_svg
t0 = t_center - half ← CSS 0%
t1 = t_center + half ← CSS 100%
7. SVG 각 stop offset (0~1)을 CSS percent로:
pct = (offset - t0) / (t1 - t0) × 100
```
### 예시
SVG:
```xml
<linearGradient x1="110.833" y1="18.2292" x2="219.479" y2="175"
gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#FDC69E"/>
<stop offset="1" stop-color="#E0782C"/>
</linearGradient>
```
박스 W=H=350일 때:
```
dx = 108.65, dy = 156.77
L_svg = √(108.65² + 156.77²) = 190.74
svg_angle = atan2(156.77, 108.65) = 0.9646 rad = 55.27°
css_angle = 55.27 + 90 = 145.27°
α = 2.535 rad
L_css = 350 × |sin 145.27°| + 350 × |cos 145.27°|
= 350 × 0.5696 + 350 × 0.8220
= 487.06
t_center = ((175 - 110.833)·108.65 + (175 - 18.229)·156.77) / 190.74²
= (6971.7 + 24577.3) / 36382
= 0.8672
half = (487.06 / 2) / 190.74 = 1.2767
t0 = 0.8672 - 1.2767 = -0.4095
t1 = 0.8672 + 1.2767 = 2.1439
SVG offset 0 → pct = (0 - (-0.4095)) / 2.5534 × 100 = 16.04%
SVG offset 1 → pct = (1 - (-0.4095)) / 2.5534 × 100 = 55.20%
```
CSS:
```css
background: linear-gradient(145.27deg, #FDC69E 16.04%, #E0782C 55.20%);
```
### 코드: `scripts/gradient_math.py`
```python
from scripts.gradient_math import svg_to_css
svg_to_css(W=350, H=350,
x1=110.833, y1=18.2292, x2=219.479, y2=175,
stops=[(0, '#FDC69E'), (1, '#E0782C')])
# → "linear-gradient(145.27deg, #FDC69E 16.04%, #E0782C 55.20%)"
```
---
## §3. 회전 감지 (bbox 비율 검사)
Figma MCP는 `rotation` 속성을 출력하지 않으므로 bbox 비율로 추론:
```
단일 문자 텍스트:
width > height × 1.5 → 90° 회전 (가로로 누움)
일반 텍스트:
width < fontSize × 0.8 → 좁은 박스 세로 배치 (writing-mode 아님, <br>로 줄바꿈)
```
CSS 적용:
```css
.rotated {
transform: rotate(90deg); /* 또는 -90deg */
}
```
---
## §4. Descender 보정 (padding-bottom)
CSS `line-height: 1`이거나 `< font_content_area_ratio`이면 글리프 하강부(g, y, p, 쉼표)가 잘림.
### 폰트별 메트릭
| 폰트 | UPM | typoAscender | typoDescender | content_area_ratio |
|------|-----|------|------|------|
| Noto Sans KR | 1000 | 1160 | 288 | 1.448 |
| Pretendard | 1000 | 1100 | 300 | 1.400 |
### 공식
```
content_area_ratio = (typoAscender + |typoDescender|) / UPM
half_leading = (line_height_ratio - content_area_ratio) / 2
↑ 음수면 잘림 발생
clipped_px = |half_leading| × font_size
padding-bottom = ceil(clipped_px)
```
### 예시 (Noto Sans KR, font 27.1px, lh 1)
```
half_leading = (1.0 - 1.448) / 2 = -0.224
clipped = 0.224 × 27.1 = 6.07 px
→ padding-bottom: 7px
```
### 예시 (Noto Sans KR, font 30px, lh 35px → ratio 1.167)
```
half_leading = (1.167 - 1.448) / 2 = -0.1405
clipped = 0.1405 × 30 = 4.215 px
→ padding-bottom: 5px
```
---
## §5. SVG viewBox padding → CSS box-sizing 매핑
SVG가 drop-shadow blur 여백을 위해 viewBox를 확장해놓은 경우 (예: 280×280 fill을 310 viewBox에 넣음):
### 케이스 A — Stroke가 fill 외부 (안전과 품질 ring 같은 케이스)
```
SVG: viewBox 310, fill r=140 (d=280), stroke r=142.5 width=5 (extends r=140 to 145)
visible: 290×290 (fill 280 + 5px stroke 외부 확장)
viewBox padding: 310 - 290 = 20 (각 변 10이 drop-shadow blur 패딩, 추가 5는 stroke)
CSS:
div W=H=290
border: 5px solid white
box-sizing: border-box
→ border-box 290, padding-box 280 ← fill 영역
position: Figma fill 위치 - (5, 5) ← stroke 외부 확장 보정
```
### 케이스 B — Stroke가 fill 내부 (생산성/소통 ring 같은 케이스)
```
SVG: viewBox 300, fill r=140, stroke r=137.5 width=5 (extends r=135 to 140 — fill 외곽 5px overlap)
visible: 280×280 (stroke가 fill 외곽 5px를 덮음)
viewBox padding: 300 - 280 = 20 (전부 drop-shadow blur)
CSS:
div W=H=280
border: 5px solid white
box-sizing: border-box
background-origin: border-box ← gradient를 border-box 280에 매핑
background-clip: border-box
→ border 5가 외곽 fill을 덮어 그라데이션 가시 영역은 270
position: Figma 위치 그대로 (offset 없음)
```
### 그라데이션 좌표 remap
SVG `<linearGradient>` 좌표는 viewBox 공간 기준. CSS box로 매핑할 때:
```
viewBox padding이 P (예: 15 또는 10)이라면:
CSS_x = SVG_x - P
CSS_y = SVG_y - P
```
이렇게 보정한 좌표를 §2의 svg_to_css 공식에 W=H=fill_size로 넣는다.
---
## §6. Drop shadow: SVG `feGaussianBlur` ↔ CSS `box-shadow`
SVG:
```xml
<filter>
<feGaussianBlur stdDeviation="5"/>
<feColorMatrix .../>
</filter>
```
CSS 근사:
```css
box-shadow: 0 0 {2 × stdDeviation}px {color};
```
`stdDeviation=5` → CSS `box-shadow: 0 0 10px black`
**주의:** 정확한 픽셀 일치는 아님. 시각적으로 매우 유사하지만 SVG 가우시안과 CSS 블러 알고리즘이 다름. ±2px 차이는 허용.
---
## §7. Blend mode 호환
### Figma가 사용하는 blend mode → CSS 호환 매핑
| Figma | CSS 정확 | CSS 호환 (Chrome/Firefox) | 비고 |
|-------|---------|----------------------|------|
| Normal | normal | normal | 기본 |
| Multiply | multiply | multiply | OK |
| **Plus darker** | plus-darker | **multiply** | plus-darker는 Safari 전용 |
| Darken | darken | darken | OK |
| Screen | screen | screen | OK |
| Overlay | overlay | overlay | OK |
### Plus-darker vs Multiply 차이
```
plus-darker(src, dst) = max(0, src + dst - 1)
multiply(src, dst) = src × dst
```
- 흰 배경: 둘 다 동일 (효과 없음)
- 어두운 배경: multiply가 plus-darker보다 강하게 어두워짐
- 밝은 그라데이션 + 흰 배경 조합: 시각적 차이 거의 없음 (이 프로젝트 디자인 대부분 해당)
→ **Chrome/Firefox 호환 위해 multiply로 통일.** RULES.md R10 참조.
---
## §8. CSS `border-radius` 비율 변환
Figma `cornerRadius`는 px 단위. CSS도 px 단위 그대로 사용 + scale 적용.
특수 케이스:
- 완전 원: `border-radius: 50%`
- 캡슐: `border-radius: {height/2}px`
- 한쪽만 둥근 사각: `border-radius: {tl} {tr} {br} {bl}` (개별 4값)
스케일링 시: scale transform이 자동으로 px 값을 비율 유지하며 축소함. 별도 계산 불필요.
---
## §9. 글자 수 추정 (블록 안에 들어갈 텍스트 양)
블록 너비/높이에서 들어갈 수 있는 한글 글자 수를 미리 계산:
```
한 줄 글자 수 = 블록 너비(px) / (font_size × 한글_글자_너비_계수)
줄 수 = 블록 높이(px) / (font_size × line_height_ratio)
총 글자 수 = 한 줄 × 줄 수 × 안전계수(0.85)
```
### Pretendard / Noto Sans KR 한글 글자 너비 계수 = 0.97
| font-size | 한글 글자 너비 | line_height 1.6 줄 높이 |
|-----------|-------------|---------------------|
| 12px | 11.6px | 19.2px |
| 16px | 15.5px | 25.6px |
| 20px | 19.4px | 32.0px |
| 24px | 23.3px | 38.4px |
| 30px | 29.1px | 48.0px |
이는 design_agent 텍스트 편집 단계에서 사용. 변환 단계에서는 직접 사용하지 않음.
---
## 검증 체크리스트
변환 후 매번 확인:
- [ ] §1 스케일 — `transform: scale(S)` 한 번만 사용했는가, 매 값 수동 곱셈은 없는가
- [ ] §2 그라데이션 — gradient_math.py로 도출한 값을 그대로 사용했는가, 눈대중 각도/stop은 없는가
- [ ] §3 회전 — bbox 비율로 회전 감지했는가
- [ ] §4 descender — `line_height < content_area_ratio`인 텍스트에 padding-bottom 추가했는가
- [ ] §5 viewBox — stroke 정렬 확인 (외부/내부)에 따라 box-sizing 적용했는가
- [ ] §6 shadow — `box-shadow blur = 2 × stdDeviation`인가
- [ ] §7 blend — `plus-darker`를 `multiply`로 교체했는가
+478
View File
@@ -0,0 +1,478 @@
# Phase 2: Figma Block Design Specification
> 7개 블록 + 2개 서브 컴포넌트 상세 설계
> 기준: FIGMA-DESIGN-LANGUAGE.md 분석 결과
> 최종 업데이트: 2026-04-08
---
## Block 1: `hero-icon-cards`
### 1.1 시각적 구조
```
┌──────────────────────────────────────────────┐
│ [Hero Statement - 큰 텍스트, 중앙] │ ← zone: header or full-width
│ │
│ ┌─[Badge Title]─┐ │
│──────────┤ ├───────────────────│
│ ┌─────┐ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │icon │ │ │icon │ │icon │ │icon │ │icon │ │ ← N개 카드 (2~6)
│ │ │ │ │ │ │ │ │ │ │ │ │
│ │Title│ ╎ │Title│ │Title│ │Title│ │Title│ │ ← 세로 구분선
│ │(sub)│ │ │(sub)│ │(sub)│ │(sub)│ │(sub)│ │
│ └─────┘ │ └─────┘ └─────┘ └─────┘ └─────┘ │
│ └───────────────────────────────────│
└──────────────────────────────────────────────┘
```
### 1.2 슬롯 정의
| 슬롯 | 필수 | 타입 | 설명 |
|------|------|------|------|
| `statement` | O | string | Hero 메시지 (1-2줄) |
| `badge_title` | X | string | 배지 바 텍스트 |
| `cards[]` | O | array | 카드 배열 |
| `cards[].icon` | X | string | 아이콘 이미지 URL 또는 이모지 |
| `cards[].title` | O | string | 영문 또는 주제목 |
| `cards[].subtitle` | X | string | 한국어 부제 |
| `cards[].color` | X | string | 개별 카드 강조색 |
### 1.3 동적 재구성 규칙
#### 그리드 계산
```
입력: N = cards.length, W = container_width_px
N ≤ 5: 1행 N열
col_count = N
card_width = (W - padding*2 - gap*(N-1)) / N
N = 6: 1행 6열 (gap 축소)
col_count = 6
gap = 8px (기본 16px에서 축소)
N > 6: 2행
col_count = ceil(N / 2)
row_count = 2
```
#### 폰트 스케일링
```
card_width ≥ 200px → title: 20px, subtitle: 14px
card_width ≥ 150px → title: 16px, subtitle: 12px
card_width < 150px → title: 14px, subtitle: 11px
```
#### 높이 계산
```
hero_height = statement_lines * line_height + padding
badge_height = 44px (고정)
card_area_height = icon_height + title_lines * title_lh + subtitle_lh + padding
- 1행: card_area_height
- 2행: card_area_height * 2 + gap
total_min_height = hero_height + badge_height + card_area_height + gaps
```
### 1.4 catalog.yaml schema
```yaml
- id: hero-icon-cards
name: 히어로 문구 + 아이콘 카드
category: cards
template: blocks/cards/hero-icon-cards.html
height_cost: xlarge
min_height_px: 280
relation_types: [definition, flow]
min_items: 2
max_items: 6
visual: >
상단에 큰 Hero 메시지(24px bold, 중앙) + 배지 바 +
하단에 N열 아이콘 카드(둥근 흰색 컨테이너, 세로 구분선).
각 카드는 아이콘 이미지 + 영문 제목(20px/900) + 한국어 부제(14px/500).
when: >
핵심 목표나 가치를 N개 키워드로 선언할 때.
각 키워드에 아이콘이나 이미지가 있을 때.
"우리가 추구하는 5가지 가치" 같은 구조.
not_for: >
비교/대조 구조 → compare-2col-badge.
상세 설명이 길 때 → card-icon-desc.
순서/단계 → card-step-vertical 또는 process-horizontal.
purpose_fit: [핵심전달, 가치선언]
zone: full-width-only
slots:
required: [statement, cards[]]
optional: [badge_title, cards[].icon, cards[].subtitle, cards[].color]
schema:
statement:
max_lines: 2
font_size: 24
ref_chars:
body: 60
note: "24px bold, 중앙정렬, 흰색 스트로크"
badge_title:
max_lines: 1
font_size: 18
ref_chars:
body: 20
note: "18px bold white, 배지 바 위"
card_title:
max_lines: 2
font_size: 20
ref_chars:
body: 15
note: "20px black/900, 중앙정렬"
card_subtitle:
max_lines: 1
font_size: 14
ref_chars:
body: 10
note: "14px medium, 한국어 부제"
padding_overhead_px: 60
padding_h_px: 32
```
---
## Block 2: `compare-2col-badge`
### 2.1 시각적 구조
```
┌──────────────────────────────────────────────┐
│ ┌─[Badge Title]─┐ │
│────────────┤ ├─────────────────│
│ │
│ ┌── Left Column ──┐ ╎ ┌── Right Column ──┐ │
│ │ │ ╎ │ │ │
│ │ [Big Title] │ ╎ │ [Big Title] │ │
│ │ │ ╎ │ │ │
│ │ body text... │ ╎ │ body text... │ │
│ │ body text... │ ╎ │ body text... │ │
│ │ │ ╎ │ │ │
│ └──────────────────┘ ╎ └──────────────────┘ │
│ │
│ [Optional: Hero Statement] │
└──────────────────────────────────────────────┘
```
### 2.2 슬롯 정의
| 슬롯 | 필수 | 타입 | 설명 |
|------|------|------|------|
| `badge_title` | O | string | 배지 바 텍스트 |
| `left_title` | O | string | 좌측 열 대제목 |
| `left_body` | O | string | 좌측 열 본문 |
| `right_title` | O | string | 우측 열 대제목 |
| `right_body` | O | string | 우측 열 본문 |
| `statement` | X | string | 하단 Hero 메시지 |
| `left_color` | X | string | 좌측 강조색 (기본: --color-teal) |
| `right_color` | X | string | 우측 강조색 (기본: --color-teal) |
### 2.3 동적 재구성 규칙
#### 레이아웃 계산
```
container_width = 컨테이너 전체 폭
padding_h = 32px * 2
2열 모드 (기본):
col_width = (container_width - padding_h - divider_gap) / 2
divider_gap = 32px
1열 모드 (sidebar zone, 폭 < 500px):
좌/우가 세로 스택
col_width = container_width - padding_h
```
#### 높이 계산
```
badge_height = 44px
left_height = title_height + body_lines * line_height + padding
right_height = title_height + body_lines * line_height + padding
content_height = max(left_height, right_height)
statement_height = statement ? (statement_lines * 28 + 16) : 0
total = badge_height + content_height + statement_height + gaps
```
#### 텍스트 피팅
```
col_width에 따른 body 글자수 제한:
col_width ≥ 500px → ~40자/줄, font: 16px
col_width ≥ 350px → ~28자/줄, font: 14px
col_width < 350px → ~20자/줄, font: 13px
```
### 2.4 catalog.yaml schema
```yaml
- id: compare-2col-badge
name: 배지 헤더 2열 비교
category: cards
template: blocks/cards/compare-2col-badge.html
height_cost: large
min_height_px: 200
relation_types: [comparison, contrast]
visual: >
상단 배지 바(이미지/그라디언트 배경 + 흰색 텍스트) 아래
2열 비교 레이아웃. 좌/우 각각 대제목(24px/900) + 본문(16px/700).
중앙 세로 구분선. 둥근 흰색 컨테이너(r:20).
선택적 하단 Hero 메시지.
when: >
두 개념/방법/전략을 나란히 비교할 때.
배지 헤더로 상위 주제를 명시.
예: "Engn. Solution vs DfMA", "현재 vs 미래"
not_for: >
3개 이상 항목 비교 → compare-3col-badge.
장/단점 목록 → comparison-2col.
상세 내용이 길고 섹션이 많을 때 → compare-detail-gradient.
purpose_fit: [비교대조, 개념정의]
zone: full-width-only
slots:
required: [badge_title, left_title, left_body, right_title, right_body]
optional: [statement, left_color, right_color]
schema:
badge_title:
max_lines: 1
font_size: 18
ref_chars:
body: 15
note: "18px bold white, 배지 바"
left_title:
max_lines: 1
font_size: 24
ref_chars:
body: 15
note: "24px black/900, 흰색 스트로크"
left_body:
max_lines: 6
font_size: 16
ref_chars:
body: 200
note: "16px/700, 틸 색상"
right_title:
max_lines: 1
font_size: 24
ref_chars:
body: 15
note: "24px black/900, 흰색 스트로크"
right_body:
max_lines: 6
font_size: 16
ref_chars:
body: 200
note: "16px/700, 틸 색상"
statement:
max_lines: 2
font_size: 20
ref_chars:
body: 50
note: "20px bold, 중앙정렬"
padding_overhead_px: 56
padding_h_px: 32
```
---
## Block 3: `compare-detail-gradient`
### 3.1 시각적 구조
```
┌──────────────────────────────────────────────────────────┐
│ ┌───── Left Header Bar (gradient) ─────┐┌── Right ─────┐│
│ │ [Left Column Title] ││ [Right Title] ││
│ └──────────────────────────────────────┘└───────────────┘│
│ ┌─────── Left BG (warm) ──────┐┌──── Right BG (teal) ──┐│
│ │ ││ ││
│ │ [Section 1 Title] ││ [Section 1 Title] ││
│ │ • body text ││ • body text ││
│ │ • body text ││ • body text ││
│ │ ││ ││
│ │ [Section 2 Title] ││ [Section 2 Title] ││
│ │ • body text ││ • body text ││
│ │ • body text ││ • body text ││
│ │ ││ ││
│ │ [Section N Title] ││ [Section M Title] ││
│ │ • body text ││ • body text ││
│ └──────────────────────────────┘└───────────────────────┘│
└──────────────────────────────────────────────────────────┘
```
### 3.2 슬롯 정의
| 슬롯 | 필수 | 타입 | 설명 |
|------|------|------|------|
| `left_header` | O | string | 좌측 열 헤더 타이틀 |
| `right_header` | O | string | 우측 열 헤더 타이틀 |
| `left_sections[]` | O | array | 좌측 섹션 배열 |
| `left_sections[].title` | O | string | 섹션 소제목 |
| `left_sections[].body` | O | string | 섹션 본문 (줄바꿈 허용) |
| `right_sections[]` | O | array | 우측 섹션 배열 |
| `right_sections[].title` | O | string | 섹션 소제목 |
| `right_sections[].body` | O | string | 섹션 본문 |
| `left_color_theme` | X | string | 좌측 테마 (기본: warm) |
| `right_color_theme` | X | string | 우측 테마 (기본: teal) |
### 3.3 동적 재구성 규칙 (★ 가장 수학적으로 복잡)
#### 그리드 계산
```
container_width에서 2열 분할:
col_width = (container_width - gap) / 2
gap = 0px (그라디언트가 맞닿음)
```
#### 섹션 높이 계산 (핵심)
```
header_bar_height = 48px (고정)
각 섹션의 높이:
section_height(s) =
title_height(s.title, title_font_size, col_width) +
body_height(s.body, body_font_size, col_width) +
section_padding
title_height = ceil(char_count / chars_per_line) * title_line_height
body_height = line_count * body_line_height
chars_per_line = floor(col_width / (font_size * 0.55)) // 한글 평균 0.55em
좌측 전체:
left_total = header_bar + sum(section_height for s in left_sections) + gaps
우측 전체:
right_total = header_bar + sum(section_height for s in right_sections) + gaps
content_height = max(left_total, right_total)
```
#### 오버플로 방지 — Fit 검증
```
if content_height > container_available_height:
전략 1: 폰트 축소
body_font_size -= 1px (최소 12px)
재계산
전략 2: 섹션 본문 줄 수 제한
max_body_lines = floor(
(available_per_section - title_height) / body_line_height
)
available_per_section = (container_height - header*2 - gaps) / max(N_left, N_right)
전략 3: Kei 에스컬레이션 (기존 파이프라인)
content 요약 요청
```
#### 색상 테마 매핑
```
warm (좌측 기본):
header_gradient: rgba(165,161,150,0.10) → rgba(57,50,30,1.00)
section_title_color: var(--color-warm-brown)
bg: rgba(255,255,255,0.30) → rgba(57,50,30,0.30)
teal (우측 기본):
header_gradient: rgba(41,107,85,0.10) → rgba(3,33,24,1.00)
section_title_color: var(--color-dark-teal)
bg: rgba(41,107,85,0.30) → rgba(255,255,255,0.30)
```
### 3.4 catalog.yaml schema
```yaml
- id: compare-detail-gradient
name: 그라디언트 상세 2열 비교
category: cards
template: blocks/cards/compare-detail-gradient.html
height_cost: xlarge
min_height_px: 300
relation_types: [comparison, contrast, process]
min_items: 2 # 좌/우 최소 1섹션씩
max_items: 10 # 좌+우 합계
visual: >
좌우 그라디언트 배경(워 브라운 vs 다크틸)으로 나뉜 2열 비교.
각 열 상단에 그라디언트 헤더 바 + 큰 제목(28px/900).
하단에 N개 섹션(소제목 22px/900 + 본문 16px/700) 반복.
좌측은 따뜻한 톤(과정/As-Is), 우측은 차가운 톤(결과/To-Be).
when: >
두 카테고리를 상세하게 비교할 때.
각 카테고리에 여러 하위 항목이 있을 때.
과정 vs 결과, As-Is vs To-Be, 문제 vs 해결 구조.
not_for: >
간단한 2항목 비교(본문 짧을 때) → compare-2col-badge.
3열 비교 → compare-3col-badge.
비교가 아닌 단독 리스트 → dark-bullet-list.
purpose_fit: [비교대조, 구조시각화, 근거사례]
zone: full-width-only
slots:
required: [left_header, right_header, left_sections[], right_sections[]]
optional: [left_color_theme, right_color_theme]
schema:
left_header:
max_lines: 1
font_size: 28
ref_chars:
body: 20
note: "28px black/900, 그라디언트 바 위"
right_header:
max_lines: 1
font_size: 28
ref_chars:
body: 20
note: "28px black/900, 그라디언트 바 위"
section_title:
max_lines: 2
font_size: 22
ref_chars:
body: 30
note: "22px/900, 색상 테마별 (브라운 or 틸)"
section_body:
max_lines: 4
font_size: 16
ref_chars:
body: 120
note: "16px/700, black"
padding_overhead_px: 48
padding_h_px: 0
```
---
## 서브 컴포넌트
### S1. 장식 이미지 (3D 화살표 등)
- 블록이 아닌 **콘텐츠 이미지**로 처리
- `cards[].icon` 또는 별도 `decoration_image` 슬롯으로 전달
- 블록은 `<img>` 태그로 렌더링, 크기는 CSS로 컨테이너에 맞춤
### S2. CTA 버튼
- 독립 블록이 아닌 **다른 블록 내 선택적 요소**
- `cta_text` 슬롯으로 전달 (없으면 미표시)
- CSS: 그라디언트 바 + 둥근 버튼 (r:7)
---
## 구현 결과 (전체 7개 블록)
| # | 블록 | 카테고리 | 출처 | 상태 | 핵심 수학 |
|---|------|---------|------|------|----------|
| 1 | `hero-icon-cards` | cards | Page 1/Frame 2 | ✅ 완료 | 3D 리본 fold_offset 계산 (badge_y→box_y×scale) |
| 2 | `compare-2col-badge` | cards | Page 1/Frame 3 | ✅ 완료 | 3D 리본 fold_offset + 틸 테두리 |
| 3 | `compare-detail-gradient` | cards | Page 1/Frame 4 | ✅ 완료 | CSS Grid 행 공유 + As-Is/To-Be + 연속 그라디언트 |
| 4 | `category-strip-table` | cards | Page 2/001_개요 | ✅ 완료 | scale=1200/2123, N열 동적 Grid |
| 5 | `checklist-dark` | emphasis | Page 3/f5 | ✅ 완료 | scale=1200/1770, 행 간격 계산 |
| 6 | `system-2col-center` | cards | Page 3/f8 | ✅ 완료 | scale=1200/2446, 3열 Grid |
| 7 | `cycle-orbit` | visuals | Page 4/Frame 1 | ✅ 완료 | **3D 원 Z축 기울임(80°) → 2D 투영**, 사이각 축소, 접선 회전 |
### 핵심 교훈
1. **수학적 계산 필수**: Figma 좌표 → 스케일 → CSS값. 시행착오 금지.
2. **3D 리본은 이미지 추출**: CSS로 재현 불가, Figma에서 PNG 추출.
3. **CSS Grid 행 공유**: 좌/우 섹션 Y선 정렬 문제 해결.
4. **연속 그라디언트**: 셀별 배경 → Grid 전체 배경으로 끊김 방지.
5. **3D 원 투영**: `project(α) = (cx+R×cos(α), cy+R×sin(α)×cos(θ))` — N개 노드 자동 배치.
6. **텍스트 배치**: 좌측 노드→이름 좌측에, 우측/상단 노드→이름 우측에.
7. **비교 리뷰 필수**: Figma PNG vs HTML을 같은 폭으로 위/아래 비교.
+98
View File
@@ -0,0 +1,98 @@
# Figma → HTML 변환 파이프라인
> 핵심 운영 절차는 [PROCESS.md](PROCESS.md), 수학 공식은 [MATH.md](MATH.md), CSS 보정 규칙은 [RULES.md](RULES.md), 변환 완료 목록은 [blocks_index.md](blocks_index.md).
## 현재 방향 (2026-04 확정)
**프로세스 우선, 인벤토리 후순위.** 35개 프레임을 사전 분류하지 않고, 1세션 1프레임씩 변환하면서 패턴이 발견되면 그때 템플릿화한다.
### 폐기된 접근
| 단계 | 폐기 사유 |
|------|---------|
| ~~Stage 1: Figma 인벤토리 일괄 추출~~ | work-creating-work. 35개는 사람이 5분이면 훑음 |
| ~~Stage 2: 노드 수 기반 지문~~ | leaf 카운트는 약한 시그널. 패턴 분류에 부정확 |
| ~~Stage 3: 자동 군집~~ | 약한 지문으로 자동 군집 시 잘못 묶임. 사람 눈이 빠름 |
**대체:** 매 변환 직후 [blocks_index.md](blocks_index.md)에 1줄 메모. 패턴은 bottom-up으로 발견된다.
## 활성 단계
```
[루프, 1세션 1프레임]
A. 1:1 변환 ← PROCESS.md 10단계 실행
↓
B. 변형 축 메모 ← flat.md에 1~5줄 작성
↓
C. blocks_index.md 1줄 추가
↓
D. 패턴 2번째 등장? → 템플릿화 (Jinja2 + catalog.yaml 등록)
↓
design_agent/templates/blocks/{category}/
↓
[다음 프레임은 새 세션에서]
```
## 현황 (2026-04-10)
| 항목 | 상태 |
|------|------|
| 핵심 문서 (CLAUDE/PROCESS/MATH/RULES/PROCESS-CONTROL) | ✅ 정리 완료 |
| 재사용 스크립트 (scripts/gradient_math.py) | ✅ 자체 회귀 테스트 통과 |
| 변환 완료 블록 (정적 HTML) | 2 / N |
| 템플릿화 (Jinja2) | 0 |
| catalog.yaml 등록 (새 패턴) | 0 |
| design_agent 본체 통합 | 0 |
### 변환 완료 블록
| # | slug | frame | pattern | 상태 |
|---|------|-------|---------|------|
| 1 | prerequisites-3col | 45:15 | 3-column-comparison | static (이전 작업) |
| 2 | bim-goals-3circles | 66:310 | cycle-3way-intersect | static (Pure CSS, 검증 ✓) |
자세한 내역: [blocks_index.md](blocks_index.md)
## 대상 Figma 파일
- 파일키: `9S6LsQyO6zlRxtiqZccOUM` ("Untitled")
- 페이지: Page 2
- 추정 프레임 수: ~35개 (확정 안 됨, 사용자가 매번 선택)
## 다음 액션
1. 사용자가 Figma desktop에서 다음 변환할 프레임 **선택**
2. 에이전트가 PROCESS.md의 10단계 그대로 실행
3. 변환 후 blocks_index.md 업데이트
4. 다음 프레임은 **새 세션에서**
## 학습된 규칙 (이 프로젝트에서 발견)
> [RULES.md](RULES.md) R1~R12에 정리됨
1. Figma MCP는 rotation 미제공 → bbox 비율로 감지 (R2)
2. CSS line-height:1 → descender 잘림 → padding-bottom 보정 (R1)
3. Figma 세로 텍스트 = 좁은 박스 + 가로 텍스트 → HTML `<br>` 방식 (R3)
4. 흰 텍스트 stroke → HTML에서 비주얼 안 좋음 → 제거
5. 미리보기 배경은 항상 흰색 (R7)
6. 다중 fills → 최상단만 사용 (R5)
7. 동일 좌표 중복 노드 → 1개만 렌더링 (R6)
8. **순수 CSS 우선, SVG는 곡선/필터에만** (R9, 1171281211 변환에서 확정)
9. **plus-darker → multiply 교체** (R10, Safari 외 호환)
10. **Stroke inside/outside 구분** → box-sizing 결정 (R11)
11. **viewBox padding 그라데이션 좌표 remap** 필수 (R12)
12. **Vector 노드 metadata bbox는 회전된 좌표** → React wrapper 좌표 신뢰
## 블록 라이브러리 통합 경로 (Stage E~G, 미래)
```
figma_to_html_agent/templates/ ← Jinja2 템플릿 임시 저장소
↓
design_agent/templates/blocks/{category}/{pattern_id}.html.j2
design_agent/templates/catalog.yaml ← when/slots/min_size_px 등록
↓
design_agent Phase Q 블록 선택 단계와 자동 연결
↓
사용자 콘텐츠 → 존 크기 → 패턴 매칭 → 블록 선택 → 슬롯 채우기 → 렌더
```
+66
View File
@@ -0,0 +1,66 @@
# Figma → HTML 프로세스 제어
## 변경 전 반드시 확인
### 1. 소스는 Figma 데이터다
- gradient 방향: Figma 데이터의 각도에서 CSS 변환 (CSS = 90 - Figma)
- border-radius: Figma 데이터 그대로 (스케일만)
- PNG를 보고 방향을 판단하지 않는다
- PNG는 픽셀 데이터 분석으로만 교차 검증에 사용
### 2. 이미지 해석 금지
- 멀티모달 이미지 해석으로 gradient 방향 판단 불가 (미묘한 alpha에서 틀림)
- 방향 확인이 필요하면 픽셀 데이터를 숫자로 분석
- "보니까 ~인 것 같다" 금지. 데이터로 확인
### 3. 작동하는 것은 건드리지 않는다
- 사용자가 A만 문제라고 하면 A만 수정
- B, C가 "같은 이유로 틀릴 것 같다"고 추측해서 함께 바꾸지 않는다
- 변경 전: 현재 값이 뭔지 기록
- 변경 후: 변경한 값이 뭔지 기록
- 되돌려야 할 때 정확히 어디로 돌아가는지 알아야 한다
### 4. 한 번에 하나만 바꾼다
- gradient 각도와 border-radius를 동시에 바꾸지 않는다
- 하나 바꾸고 확인, 맞으면 다음 하나
### 5. 사용자가 말한 것만 한다
- 사용자의 피드백을 자의적으로 해석하지 않는다
- "주황색 gradient가 안 맞다" → 주황색 gradient만 수정
- 초록, 다른 요소는 건드리지 않는다
### 6. 찍어맞추기 금지
- 0deg 안 되면 180deg, 그것도 안 되면 90deg... 이런 식 금지
- 값을 바꾸기 전에 WHY를 먼저 설명할 수 있어야 한다
- 설명 못하면 바꾸지 않는다
### 7. "쉬운 전면 재작성" 절대 금지
- 80점 결과물에서 2가지 문제를 고칠 때, 구조를 flex/grid 등으로 **전면 재작성하지 않는다**
- 기존에 맞춘 수십 가지(pill 크기, 위치, 비율, border 걸침)가 전부 깨진다
- **기존 구조 유지 + 문제만 정확히 수정**이 원칙
- 보완이 안 되면 그 방식을 오답노트로 두고 **다른 방식으로 접근**
- 점점 나빠지면 **즉시 멈추고 마지막 OK 상태로 복원**
- 구조 변경이 불가피하면 **사전에 영향 범위 분석 + 사용자 확인 후** 진행
## Figma 도형 gradient 처리 프로세스
```
1. Figma gradient 각도 확인
2. gradient를 0으로 돌린 기본 상태 파악
- border-radius: Figma 값 그대로
- gradient: CSS 90deg (Figma 0 = 왼→오 = CSS 90deg)
3. CSS로 기본 상태 구현
4. Figma gradient 각도 적용: CSS = 90 - Figma각도
5. 위치(left, top)와 크기(width, height) 배치
```
## Figma gradient 각도 체계
```
Figma 0° = 왼쪽 진 → 오른쪽 옅 = CSS 90°
Figma -90° = 위 진 → 아래 옅 = CSS 180°
Figma -180° = 오른쪽 진 → 왼쪽 옅 = CSS 270°
Figma 90° = 아래 진 → 위 옅 = CSS 0°
```
변환: **CSS = 90 - Figma**
+410
View File
@@ -0,0 +1,410 @@
# 변환 절차 (10 STEP)
Figma 프레임 1개를 HTML+template으로 변환할 때 매번 동일하게 따르는 운영 핸드북.
> **원칙: 같은 세션에서 여러 프레임 연속 작업 OK.** 컨텍스트가 무거워지면 `/compact` 로 정리하고 계속 진행. 핵심 결정/규칙/산출물은 모두 파일에 박혀있어 compact 후에도 보존됨. 이렇게 해야 누적 학습(R13 등 sub-pattern)이 즉시 적용됨. CLAUDE.md 원칙 7 참조.
> **원칙: 1 프레임 변환 = 1:1 reference + 템플릿 동시 작성.** 1번째 등장이라도 templates_staging/{pattern}.html.j2 + meta.yaml + example.yaml 까지 작성한다. 정적 HTML만 두는 것은 work-creating-work. 사용자가 final 검수 후 design_agent/templates/ 로 직접 프로모션.
---
## STEP 0 — 준비
### 0-A. 에이전트: blocks_index.md 한 번 읽기 (필수)
새 세션은 메모리가 없다. 패턴 발견 트리거(2번째 등장)가 작동하려면 **세션 시작 직후** [blocks_index.md](blocks_index.md)를 한 번 통으로 읽어야 한다.
```
Read figma_to_html_agent/blocks_index.md
```
확인할 것:
- "변환 완료 (현행 방법론)" 섹션의 패턴 목록
- "패턴 카탈로그" 섹션의 등록 패턴 (등장 횟수)
- "templates_staging 대기열" 의 진행 중 패턴
### 0-B. 사용자: 프레임 선택
1. Figma desktop에서 변환할 프레임을 **선택** (클릭) 한다
2. 에이전트에게 "이 프레임 변환해줘"라고 알린다 (프레임 ID/이름 명시 권장)
### 0-C. 에이전트: 패턴 비교
STEP 1~3로 metadata + screenshot 받은 직후, 0-A에서 본 인덱스와 비교:
- 비슷한 구조 발견 → "이거 X 패턴과 비슷합니다. 두 번째 등장이면 templates_staging/ 로 Jinja2 추출 진행할까요?" 사용자에게 확인
- 비슷한 게 없음 → 일반 STEP 4 이하 진행
**확인사항:**
- Figma desktop 앱이 활성 탭에 올바른 파일이 떠 있는가
- `.mcp.json`에 figma-desktop SSE 서버가 등록돼있는가 (`http://127.0.0.1:3845/sse`)
---
## STEP 1~3 — 데이터 수집 (병렬)
세 도구를 **단일 메시지에 multiple tool_use 블록**으로 동시 호출한다 (도구 호출 단위 병렬). 순차 호출하면 같은 노드 ID를 두 번 추출하느라 토큰만 낭비됨.
```
[single message, multiple tool_use blocks]
1. mcp__figma-desktop__get_metadata nodeId="" (현재 선택 노드)
2. mcp__figma-desktop__get_design_context nodeId="" (현재 선택 노드)
3. mcp__figma-desktop__get_screenshot nodeId="" (현재 선택 노드)
```
**주의:** nodeId를 비우면 현재 선택 노드를 사용하므로 metadata 응답을 기다릴 필요 없음. 셋 다 동시에 갈 수 있다.
| 도구 | 얻는 것 | 사용처 |
|------|--------|-------|
| get_metadata | 모든 leaf 노드의 `id, type, name, x, y, width, height` (XML) | bottom-up 플래튼 |
| get_design_context | gradient/filter/font/color (React+Tailwind 코드) | CSS 변환 |
| get_screenshot | Figma가 렌더한 PNG | STEP 8 사람 눈 검증 |
**주의:**
- get_metadata 응답이 100KB+ 면 frame이 너무 커서 자르지 않은 상태. 사용자에게 더 작은 단위 선택 요청
- get_design_context는 응답이 매우 크므로 한 프레임당 1회만 호출
---
## STEP 4 — 자산 정리 (block-tests/assets/shared/ 캐시)
design_context에서 `localhost:3845/assets/{hash}.png|svg` 패턴의 자산 URL 추출.
각 자산에 대해:
1. URL 끝의 hash를 파일명으로 사용
2. `block-tests/assets/shared/{hash}.{ext}` 가 **이미 있으면 다운로드 스킵**
3. 없으면 curl로 다운로드
```bash
cd block-tests/assets/shared
for url in $URLS; do
hash=$(basename "$url")
[ -f "$hash" ] || curl -sSo "$hash" "$url"
done
```
HTML에서 참조 시:
```html
<img src="assets/shared/{hash}.png">
```
(`block-tests/{slug}.html` 기준으로 상대 경로 `assets/shared/`)
**효과:**
- 동일 자산이 여러 프레임에서 등장해도 한 번만 다운로드 (해시 파일명이라 자동 dedup)
- 후속 프레임 변환 시간 단축
- 토큰 절약 (이미 있는지 확인만)
**프레임 매핑 메모:** `block-tests/{slug}_assets.txt`에 사용한 hash 목록 + 의미 라벨 기록 → 추후 재추출 시 빠른 매핑
```
# bim-goals-3circles_assets.txt
84965807....png bg_texture
f05ebf15....png arc_top
2f0f1750....png arc_side
```
**legacy:** 이전에 다운로드한 자산이 `block-tests/assets/frame_{id}/` 에 있다면 그대로 두되, 새 변환부터는 `shared/` 만 사용한다.
---
## STEP 5 — flat.md 작성 (분석 + 이상 탐지)
`block-tests/{slug}_flat.md` 파일 생성. 다음 섹션을 반드시 포함:
### 섹션 1. 메타
```markdown
# Frame {ID} — {이름}
> 원본: {W} × {H} px (node {ID})
> Scale: × {S} → {1280} × {H×S} px
> 슬라이드 16:9 안 배치
```
### 섹션 2. 계층 경로 (bottom-up)
모든 leaf 노드를 들여쓰기 트리로 표현. 그룹별 누적 offset 표시.
```
Frame {root} ({W}×{H})
├─ Group "X" (offset → 누적)
│ ├─ TEXT "..." (abs_x, abs_y) {w}×{h}
│ └─ ...
```
### 섹션 3. 이상 탐지 결과
| 검사 | 결과 |
|------|------|
| 회전 단일문자 (bbox 가로 > 세로 × 1.5) | 발견 노드 ID 또는 "없음" |
| 좁은 박스 세로 텍스트 (width < fontSize × 0.8) | ... |
| 중복 노드 (동일 좌표 + 동일 내용) | ... |
| Vector 좌표 metadata vs design_context 불일치 | ... (있으면 어느 쪽 신뢰) |
### 섹션 4. 변형 가능 축 메모 + 슬롯 옵션
이 블록을 템플릿화한다면 무엇이 파라미터가 될지 1~5줄로. **각 슬롯이 required인지 optional인지 표시**:
```markdown
## 변형 가능 축
- columns[N=2~4] (required)
- badge (required)
- bullet_items[1~12] (required)
- bg_image (required)
- bottom_photo (optional) ← 사진 없는 mdx도 이 블록 매칭 가능
- color_palette[N] (required, N과 일치)
```
이 메모가 STEP 10의 `blocks_index.md` 요약 + 향후 templates_staging meta.yaml 의 초안.
### 섹션 5. Sub-pattern 식별 (재사용 가능한 atomic 단위)
이 블록 안에 **다른 블록과 공유 가능한 sub-pattern**이 있는가? RULES.md R13~ 참조.
```markdown
## Sub-patterns
- `bullet-list-with-marker` (R13) — 각 텍스트 앞에 장식 마커
- 위치: 각 컬럼 본문 영역
- 마커: checkbox PNG
- 적용 구조: .bullet-list / .bullet-row / .bullet-icon / .bullet-text
```
Sub-pattern을 **즉시 RULES.md에 등록할 필요는 없다**. 동일 sub-pattern이 2번째 등장하면 그때 R번호 부여해서 정식 등록.
---
## STEP 6 — 그라데이션 수학 변환
각 SVG `<linearGradient>` 데이터를 [scripts/gradient_math.py](scripts/gradient_math.py)로 CSS로 변환.
```bash
python scripts/gradient_math.py \
--w 350 --h 350 \
--x1 110.833 --y1 18.2292 --x2 219.479 --y2 175 \
--stops "0:#FDC69E,1:#E0782C"
```
출력:
```
linear-gradient(145.28deg, #FDC69E 16.04%, #E0782C 55.20%)
```
수학 원리는 [MATH.md §2 참조](MATH.md).
**여러 그라데이션을 한 번에 변환할 땐 Python 인라인 스크립트 사용:**
```python
import sys, os
# scripts/ 디렉토리를 sys.path에 명시 추가 (작업 디렉토리 무관)
sys.path.insert(0, os.path.join('figma_to_html_agent', 'scripts'))
from gradient_math import svg_to_css
svg_to_css(W=350, H=350, x1=110.833, y1=18.2292, x2=219.479, y2=175,
stops=[(0, '#FDC69E'), (1, '#E0782C')])
```
**작업 디렉토리가 `figma_to_html_agent/` 인 경우:**
```python
import sys; sys.path.insert(0, 'scripts')
from gradient_math import svg_to_css
```
**또는 정식 패키지로 사용** (`scripts/__init__.py` 가 있으므로):
```python
# 작업 디렉토리가 figma_to_html_agent/ 일 때
from scripts.gradient_math import svg_to_css
```
⚠️ **금지: 함수 코드를 인라인 Python에 복사 붙여넣기**. 한 번 만든 `gradient_math.py`를 항상 import해서 쓴다. 복사하면 버그 수정 시 여러 곳을 동시에 고쳐야 하고 수식이 미세하게 어긋날 위험.
---
## STEP 7 — HTML 작성
### 7-A. 기본 구조
```html
<div class="slide"> <!-- 1280×720 흰색 -->
<div class="block"> <!-- 1280 × (H×S) -->
<div class="inner"> <!-- 원본 W×H, transform: scale(S) -->
... 모든 요소 (Figma 원본 좌표 사용) ...
</div>
</div>
</div>
```
```css
.inner {
position: absolute;
left: 0; top: 0;
width: {W}px; height: {H}px;
transform: scale({S});
transform-origin: top left;
}
```
**왜 transform: scale을 쓰는가:** 모든 위치/크기/폰트/그림자/스트로크가 한 번의 transform으로 균일하게 축소됨. 매 값을 수동으로 ×S 곱하는 것보다 안전하고 검증 가능. ([MATH.md §1](MATH.md))
### 7-B. 요소 변환 우선순위
| 요소 종류 | 구현 방법 | 이유 |
|---------|---------|-----|
| 원/사각형 + gradient + blend | **HTML div** + `border-radius` + `linear-gradient` + `mix-blend-mode: multiply` | 동적 재구성 위해 |
| Stroke (경계선) | `border: Npx solid color` + `box-sizing: border-box` | gradient와 함께 사용 가능 |
| Drop shadow blur | `box-shadow: 0 0 {2×stdDev}px {color}` | SVG feGaussianBlur 근사 |
| 곡선 (아크, 비원형) | **SVG `<path>`** 또는 미리 export된 PNG | CSS 불가능 |
| 텍스트 | HTML `<div>` 절대 배치 | 선택 가능, 접근성 |
| 실사 이미지 | `<img>` PNG | 재현 불가 |
| 회전된 도형 | 래퍼 div + `transform: rotate()` ([INSIGHT-GRADIENT.md](INSIGHT-GRADIENT.md)) | gradient 동시 회전 |
### 7-C. 보정 규칙
[RULES.md](RULES.md) R1~R16 모두 적용:
- R1: descender padding-bottom
- R2~R3: 회전/세로 텍스트
- R4: 그라데이션 텍스트
- R5: 다중 fills
- R6: 중복 노드
- R7: 흰 배경
- R8: 스케일 팩터
- R9: 순수 CSS 우선
- R10: blend mode 호환
- R11: stroke 정렬 (inside/outside)
- R12: viewBox padding
---
## STEP 8 — Selenium 렌더링 + 사람 눈 검증
```python
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from PIL import Image
import os, time
# _renders/ 폴더 없으면 생성
os.makedirs('block-tests/_renders', exist_ok=True)
opts = Options()
opts.add_argument('--headless=new')
opts.add_argument('--hide-scrollbars')
opts.add_argument('--force-device-scale-factor=1')
opts.add_argument('--window-size=1600,900')
d = webdriver.Chrome(options=opts)
p = os.path.abspath('block-tests/{slug}.html').replace('\\','/')
d.get('file:///' + p)
time.sleep(1.5)
d.save_screenshot('block-tests/_renders/{slug}_full.png')
r = d.execute_script(
'const r=document.querySelector(".slide").getBoundingClientRect();'
'return [r.x,r.y,r.width,r.height];'
)
Image.open('block-tests/_renders/{slug}_full.png').crop(
(int(r[0]), int(r[1]), int(r[0]+r[2]), int(r[1]+r[3]))
).save('block-tests/_renders/{slug}.png')
d.quit()
```
**검증 방식:**
- 자동 픽셀 diff는 하지 않음 (font 렌더 차이로 노이즈만 많음)
- Figma `get_screenshot` 응답과 Selenium 결과를 **사람 눈**으로 비교
- 차이 발견 시 STEP 5~7로 돌아가서 원인 파악 (값 수정 금지)
---
## STEP 9 — 결과물 저장
```
block-tests/
├── {slug}.html ← 변환물
├── {slug}_flat.md ← 플래튼/이상/변형 축 메모
└── _renders/
└── {slug}.png ← 검증 스크린샷
```
`{slug}` 명명 규칙: 의미 기반 kebab-case (예: `bim-goals-3circles`, `cards-3col-icon`).
프레임 ID는 metadata로 추적 가능하므로 파일명에 넣지 않음.
---
## STEP 10 — blocks_index.md 1줄 업데이트
`blocks_index.md` 끝에 한 줄 추가:
```markdown
| {slug} | {프레임 ID} | {1줄 변형 축 요약} | {날짜} |
```
이 인덱스가 패턴 발견의 단서가 된다. 다음 변환 시작 전에 이 인덱스를 한 번 훑어서 "이미 비슷한 거 했나?" 확인.
---
## 패턴 → 템플릿화 (1번째부터 즉시)
**규칙: 1번째 등장부터 templates_staging 작성. 정적 HTML만 두는 것 금지.**
| 등장 횟수 | 처리 |
|---------|------|
| **1번째** | `block-tests/{slug}.html` (1:1 reference) + `templates_staging/{pattern_id}.html.j2` (Jinja2 + meta.yaml + example.yaml) **함께 작성** |
| 2번째 | 기존 staging 템플릿이 새 데이터로 잘 렌더되는지 확인. 안 되면 템플릿 수정. example 추가. |
| 3번째 이후 | 동일 |
**왜 1번째부터 템플릿화하나?**
- 변환의 목적은 **블록 라이브러리 구축**, 단순 HTML 복제가 아님
- 1:1 단계에서 발견한 인사이트(R13 등)를 즉시 템플릿에 반영해야 잊지 않음
- 사용자가 검수할 때 "이게 블록으로 어떻게 작동할지" 즉시 확인 가능
- 2번째 등장을 기다리면 사용자 수동 복제 작업이 누적됨 (work-creating-work)
**Stage 2 산출물:**
```
templates_staging/
├── {pattern_id}.html.j2 ← Jinja2 템플릿 본체
└── {pattern_id}.meta.yaml ← when / slots / min_size_px / 변형 축 초안
```
여기까지가 **에이전트 책임의 끝.**
---
## 🚧 프로모션 게이트 (사용자 전용)
> 이 게이트 이후 작업은 **에이전트가 절대 수행하지 않는다.** 모든 design_agent/templates/ 변경은 사용자 본인이 직접 한다.
### 사용자가 수행할 작업
1. **검수**: `templates_staging/{pattern_id}.html.j2` 를 다양한 파라미터로 렌더 테스트
2. **품질 게이트 통과 확인**:
- [ ] 1:1 변환물과 시각적으로 동일한가
- [ ] 슬롯 파라미터를 바꿔도 깨지지 않는가 (원 4개, 라벨 0개 등 극단 케이스)
- [ ] meta.yaml의 when/slots가 design_agent의 다른 블록과 충돌 없는가
3. **이동**: `templates_staging/{pattern_id}.html.j2` → `design_agent/templates/blocks/{category}/`
4. **등록**: `design_agent/templates/catalog.yaml` 에 when/slots/min_size_px 추가
5. **상태 업데이트**: `blocks_index.md` 의 해당 행 상태 → `promoted`
### 에이전트의 역할
- staging 작성까지만
- 사용자 요청 없이 `design_agent/templates/` 를 절대 읽거나 쓰지 않음
- "templates/ 에 옮겨드릴까요?" 같은 제안 금지 (월권)
- 사용자가 명시적으로 "이 staging 결과 검토해줘"라고 요청하면 → staging 폴더 내에서만 검토
---
## 안티 패턴 (하지 말 것)
| ❌ 하지 말 것 | 이유 |
|------------|-----|
| 사전에 인벤토리/지문/군집 단계 | work-creating-work, 패턴은 변환하면서 발견됨 |
| 1번째 등장은 정적 HTML로만 두기 (templates_staging 미작성) | work-creating-work, 인사이트 잊혀짐. 1번째부터 템플릿 작성 |
| 컨텍스트 차면 강제 새 세션 | compact 사용. 핵심 결정은 모두 파일에 박혀있어 손실 없음 |
| Figma 데이터 안 보고 멀티모달 이미지로 추측 | 미묘한 alpha/blend에서 틀림 |
| "여기 1px 어색하니 다른 곳도 같이 바꾸자" | 사용자 피드백만 정확히 반영 |
| 같은 자산을 매번 새로 다운로드 | `block-tests/assets/shared/` 캐시 활용 |
| 그라데이션 각도/색을 눈대중으로 | gradient_math.py로 수학 도출 |
| gradient_math.py 함수 코드 인라인 복사 | import만 한다. 복사하면 수식 어긋남 |
| 세션 시작에 blocks_index.md 안 읽음 | 패턴 발견 트리거 영영 작동 안 함 |
| `design_agent/templates/` 직접 수정 | 프로모션은 사용자 전용. 에이전트는 staging까지만 |
| "templates/ 옮겨드릴까요?" 제안 | 월권. 사용자가 알아서 함 |
| `prerequisites-3col.html` 을 신규 변환 레퍼런스로 사용 | 구 방법론 (R8/R9 미적용). legacy 표시됨 |
+165
View File
@@ -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.
File renamed without changes.
+466
View File
@@ -0,0 +1,466 @@
# CSS 보정 규칙
Figma → HTML 변환 시 Figma와 CSS 렌더링 차이를 수학적으로 보정하는 규칙 모음.
**모든 규칙은 수학적 근거가 있어야 한다. 감으로 보정하지 않는다.**
---
## R1. Descender 보정 (padding-bottom)
**문제:** CSS `line-height: 1`이면 글리프 하강부(g, y, p, 쉼표)가 잘림.
Figma는 line-height에 관계없이 글리프를 항상 표시하지만, CSS는 line box 밖을 자른다.
**원인:** 폰트의 content area > line box일 때 half-leading이 음수가 되어 잘림 발생.
**계산:**
```
content_area_ratio = (typoAscender + |typoDescender|) / UPM
half_leading = (line_height - content_area_ratio) / 2 ← 음수이면 잘림
clipped_px = |half_leading| × font_size
padding-bottom = ceil(clipped_px)
```
**폰트별 값:**
| 폰트 | UPM | Ascender | Descender | content_area_ratio |
|------|-----|----------|-----------|-------------------|
| Noto Sans KR | 1000 | 1160 | 288 | 1.448 |
| Pretendard | 1000 | 1100 | 300 | 1.400 |
**예시 (Noto Sans KR, font-size 27.1px, line-height 1):**
```
half_leading = (1 - 1.448) / 2 = -0.224
clipped = 0.224 × 27.1 = 6.07px
→ padding-bottom: 7px
```
**적용:** `line-height < content_area_ratio`인 모든 텍스트 요소에 padding-bottom 추가.
---
## R2. 회전 감지 (bbox 비율)
**문제:** Figma MCP는 `rotation`/`transform` 속성을 출력하지 않음.
**감지 방법:** 바운딩 박스의 가로세로 비율이 해당 글자의 정상 비율과 반대이면 회전.
```
단일 문자 "(" 정상: ~18×50 (세로가 김)
Figma bbox: 60×19 (가로가 김)
→ 가로:세로 = 3.2:1 → 90° 회전 확정
```
**규칙:**
- 단일 문자 텍스트에서 `width > height × 1.5` → 90° 회전
- 일반 텍스트에서 `width < fontSize × 0.8` → 세로 배치용 좁은 박스 (writing-mode 아님, <br> 줄바꿈)
**CSS 구현:**
```css
.rotated-bracket { transform: rotate(90deg); } /* 여는 괄호 */
.rotated-bracket-close { transform: rotate(-90deg); } /* 닫는 괄호 */
```
---
## R3. 세로 텍스트 (좁은 박스)
**문제:** Figma에서 좁은 박스(width < fontSize) 안에 텍스트를 넣으면 글자가 한 줄에 하나씩 배치됨.
**감지:** `bbox.width < fontSize × 0.8` + 2글자 이상
**CSS 구현:** `writing-mode` 사용하지 않음. HTML에서 `<br>`로 글자마다 줄바꿈.
```html
<span class="vlabel">기<br>술</span>
```
이유: `writing-mode: vertical-rl`은 Figma 원본과 다른 간격/정렬을 만듦.
---
## R4. 그라데이션 텍스트
**Figma:** 텍스트 fills에 GRADIENT_LINEAR이 있으면 그라데이션 텍스트.
**CSS:**
```css
.gradient-text {
background: linear-gradient(...);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
```
**주의:** 흰 텍스트 스트로크(`-webkit-text-stroke: white`) 사용 금지.
HTML에서 보기 불편하므로 제거한다.
---
## R5. 다중 fills 처리
**Figma:** 하나의 노드에 여러 fill이 쌓일 수 있음 (리스트 순서 = 위에서 아래).
**규칙:** 첫 번째 fill이 불투명(opacity 1)이면 나머지는 가려짐 → 첫 번째만 사용.
---
## R6. 중복 노드
**감지:** 동일 좌표 + 동일 내용 + 동일 크기 → Figma 복사 흔적.
**처리:** 1개만 렌더링, 나머지 무시. flat 목록에 [중복] 표기.
---
## R7. 미리보기 배경
**슬라이드 배경:** 항상 `#ffffff` (흰색)
**블록 배경:** 항상 `#ffffff` (미리보기용). 원본 배경색은 주석으로 기록.
이유: 다크 배경에서 요소가 안 보이는 문제 방지. 위치/크기 확인이 우선.
---
## R8. 스케일 팩터
**계산:** `Scale = 1280 / 원본_width`
**적용 대상:**
- 위치 (x, y)
- 크기 (width, height)
- 폰트 크기 (fontSize)
- 스트로크 너비 (strokeWeight)
- 간격 (gap, padding)
- 그림자 (blur, offset)
**적용하지 않는 것:**
- 색상 (그대로 유지)
- 그라데이션 방향/퍼센트 (그대로 유지)
- 폰트 굵기 (그대로 유지)
- line-height 비율 (그대로 유지)
- border-radius 비율 (스케일 적용)
**구현 권장:** 매 값 수동 곱셈 대신 `transform: scale(S)` 한 번으로 균일 축소. MATH.md §1 참조.
---
## R9. 순수 CSS 우선, SVG는 곡선/필터에만
블록 라이브러리의 동적 재구성을 위해 가능한 한 **HTML div + CSS**로 구현한다.
| 요소 | 구현 |
|------|------|
| 원/사각형 + linear-gradient | `<div>` + `border-radius` + `background: linear-gradient(...)` |
| Stroke (경계선) | `border` + `box-sizing: border-box` |
| Drop shadow blur | `box-shadow: 0 0 {2×stdDev}px {color}` |
| **곡선 (아크, 비원형 path)** | **SVG `<path>` 또는 PNG** ← CSS 불가능 |
| **복잡한 SVG filter chain** | **SVG `<filter>`** ← CSS 근사 불가 시 |
| 텍스트 | HTML `<div>` 절대 배치 |
**이유:** SVG `<img src="...svg">`는 정적 파일. 색상/개수/위치 변경 시 매번 재export 필요. CSS는 변수/Jinja로 즉시 파라미터화 가능.
---
## R10. Blend mode 호환 (plus-darker → multiply)
**문제:** Figma의 `plus darker` blend mode는 Apple CoreGraphics 전용. CSS 스펙엔 `plus-darker`가 있지만 **Safari/WebKit만 지원**, Chrome/Firefox에서는 무시되어 효과 사라짐.
**규칙:**
1. SVG/CSS에 `mix-blend-mode: plus-darker` 발견 시 → **`multiply`로 교체**
2. SVG 파일 내부의 `style="mix-blend-mode:plus-darker"`도 함께 교체
3. 시각 차이 검증: 흰 배경 위 밝은 그라데이션은 거의 동일. 어두운 영역은 multiply가 더 강함
```
plus-darker(src, dst) = max(0, src + dst - 1) [Safari only]
multiply(src, dst) = src × dst [모든 브라우저]
```
자세한 비교: MATH.md §7
---
## R11. Stroke 정렬: viewBox padding 처리
SVG는 stroke가 fill의 안/밖으로 확장될 수 있어 viewBox에 padding이 들어감. CSS 변환 시 두 케이스로 나뉨:
### 케이스 A — Stroke가 fill **외부**
예: `r=140 fill` + `r=142.5 stroke-width=5` → stroke가 r=140~145 (외부)
```css
.ring {
width: 290px; height: 290px; /* fill 280 + 외부 stroke 5×2 */
border: 5px solid white;
box-sizing: border-box; /* border 안쪽 padding-box = 280 = fill */
background: linear-gradient(...); /* default origin: padding-box 280 */
border-radius: 50%;
}
/* 위치: Figma fill 위치에서 (-5, -5) 오프셋 */
```
### 케이스 B — Stroke가 fill **내부** (overlap)
예: `r=140 fill` + `r=137.5 stroke-width=5` → stroke가 r=135~140 (fill 외곽 overlap)
```css
.ring {
width: 280px; height: 280px; /* fill 280 그대로 */
border: 5px solid white;
box-sizing: border-box; /* padding-box 270 */
background: linear-gradient(...);
background-origin: border-box; /* gradient는 280 영역에 매핑 */
background-clip: border-box;
border-radius: 50%;
}
/* 위치: Figma fill 위치 그대로 */
```
판별: SVG 안의 stroke `r` 값이 fill `r`보다 **크면** 외부 (케이스 A), **작거나 같으면** 내부 (케이스 B).
---
## R12. viewBox padding gradient remap
viewBox padding이 있는 SVG의 그라데이션 좌표는 viewBox 공간 기준이므로, CSS 박스로 매핑할 때 **각 좌표에서 padding 만큼 빼야** 한다.
```python
# SVG viewBox 310, 실제 fill 280, padding 15
css_x1 = svg_x1 - 15
css_y1 = svg_y1 - 15
css_x2 = svg_x2 - 15
css_y2 = svg_y2 - 15
# 그 다음 svg_to_css(W=280, H=280, ...)
```
또는 `scripts/gradient_math.py`의 `svg_to_css_remap()` 사용:
```python
svg_to_css_remap(css_W=280, css_H=280, viewbox_padding=15,
x1=..., y1=..., x2=..., y2=..., stops=[...])
```
---
## R14. 한글 줄바꿈은 word-break: keep-all (전역 default)
**문제:** Chrome 기본 동작은 한글을 글자 단위로 wrap (예: "수행공정의 쉬운이해로 관리 편의성 증" / "진"). Figma는 단어 단위 wrap이라 시각이 다름.
**규칙:** 모든 변환물의 base CSS에 `word-break: keep-all` 적용.
```css
body {
font-family: 'Noto Sans KR', sans-serif;
...
word-break: keep-all; /* 한글 단어 단위 wrap (Figma matching) */
}
```
또는 텍스트 컨테이너 단위로:
```css
.bullet-text, .left-text, .right-text, .body-text {
word-break: keep-all;
}
```
**언제 빼나:**
- `white-space: nowrap` 단일 라인 텍스트 (영향 없음, 안 빼도 무방)
- 코드/숫자 등 단어 경계가 없는 콘텐츠
**예외:** 영문/기호 혼합 텍스트는 `word-break: keep-all` 만으로는 부족할 수 있음. 그 경우 `overflow-wrap: anywhere` 또는 `<br>` 명시 split.
---
## R15. 박스 vertical center align (Figma flex justify-center 모방)
**문제:** Figma React 코드에서 자주 보이는 패턴:
```jsx
<div className="-translate-y-1/2 absolute flex flex-col h-[71px] justify-center top-[243.5px]">
```
이는 **컨테이너 박스의 vertical center에 텍스트를 정렬**한다는 의미. 단순히 `top` 값만 받아서 박는 건 잘못 — 텍스트가 박스 top에 붙어 다른 요소(예: cat pill의 vertical center)와 어긋남.
**올바른 변환:**
```css
.text-box {
position: absolute;
top: <visual_top>; /* Figma top - height/2 */
height: <figma_height>;
width: <figma_width>;
display: flex;
flex-direction: column;
justify-content: center; /* vertical center */
}
```
**또는 인접 박스(예: 옆에 있는 cat pill)와 동일한 top + height를 박고 flex justify-center 적용**하면 자동으로 가운데 align. 1:1 변환에서 가장 안전.
**검증:** 인접 박스 center y 와 텍스트 박스 center y 가 같은지 측정. 차이 > 5px이면 잘못된 것.
---
## R13. Custom-Marker Bullet List 패턴 (sub-pattern)
**감지 조건 (3가지 모두 충족):**
1. 여러 텍스트 항목이 세로로 나열됨
2. 각 항목 앞에 **장식 마커**가 있음 (체크박스 아이콘, 점, 화살표, 숫자, 원, PNG 등)
3. 마커는 인터랙티브하지 않고 순수 시각 요소 (실제 `<input type="checkbox">` 가 아님)
**Figma 원본에서는** 마커와 텍스트가 별도 요소로 평면 배치돼있을 수 있다. 그래도 **시맨틱적으로는 하나의 list item**으로 봐야 한다.
### 구조 (CSS Flex Pair Pattern)
```html
<div class="bullet-list" style="--icon-gap: ...;">
<div class="bullet-row">
<span class="bullet-icon"><img src="marker.png"></span>
<span class="bullet-text">텍스트 항목</span>
</div>
<div class="bullet-row compact">
<span class="bullet-icon"><img src="marker.png"></span>
<span class="bullet-text">긴 텍스트가<br>두 줄로</span>
</div>
</div>
```
### CSS
```css
.bullet-list {
display: flex;
flex-direction: column;
/* 동일 top/bottom 정렬을 위해 컨테이너에 fixed height + space-between */
justify-content: space-between;
}
.bullet-row {
display: flex;
align-items: flex-start;
--lh: 85px; /* 기본 라인 높이 */
}
.bullet-row.compact {
--lh: 50px; /* 2-line 항목용 타이트 lh */
}
.bullet-icon {
flex: none;
width: var(--icon-w);
height: var(--icon-h);
/* 핵심: 아이콘 vertical center를 첫 줄 vertical center에 align */
margin-top: calc(var(--lh) / 2 - var(--icon-h) / 2);
/* 컬럼별 figma gap (text_left − icon_left − icon_w) */
margin-right: var(--icon-gap);
}
.bullet-text {
flex: 1;
line-height: var(--lh);
white-space: normal;
word-break: keep-all; /* 한글: 단어 단위 줄바꿈 */
}
```
### 핵심 수학
```
icon margin-top = lh / 2 − icon_h / 2 (첫 줄 vertical center)
icon margin-right = text_left − icon_left − icon_w (Figma 데이터)
```
### 절대 하지 말 것
- 마커와 텍스트를 별도 요소로 절대 배치 (`<div class="checkbox" style="left:..; top:..">` × N)
- row에 fixed `height` 설정 (wrap 시 overlap)
- `white-space: nowrap` (텍스트가 컨테이너 밖으로 overflow)
- 모든 row에 동일한 top/bottom margin 강제 (텍스트 길이가 결정해야 함)
### 정렬 원칙
3개 이상의 평행한 컬럼이 있을 때:
- **모든 컬럼은 동일한 top + 동일한 height** 로 시작
- 컬럼별 자연 콘텐츠 합 중 **가장 큰 값**을 height로 사용
- `justify-content: space-between` 으로 내부 균등 분포
- 결과: 컬럼별 spacing은 다르지만 vertical extent는 동일
### 적용 사례
| 프레임 | 사용 | 비고 |
|--------|------|------|
| 1171281191 (cards-3col-persona) | 3 컬럼 × 6~7 마커-text 페어 | 첫 적용 |
| (앞으로 비슷한 패턴 발견 시 추가) | | |
### 1:1 변환 단계의 임시 보정 (템플릿화 시 제거)
다음은 1:1 시각 fidelity를 위한 **임시 보정**이며, 템플릿화 시 모두 제거해야 한다 (자연 wrap이 처리):
- `letter-spacing: -1.5px` 등 — Chrome Noto Sans KR 너비가 Figma보다 약간 넓어 wrap이 일어나는 것을 방지하기 위한 보정
- `<br>` 명시적 줄바꿈 — Figma의 의도된 split 위치 보존용. 템플릿화 시 자연 wrap이 알아서 처리
- `class="compact"` 수동 지정 — 어떤 항목이 2-line인지 1:1 단계에선 수동, 템플릿화 시 텍스트 길이 자동 판정
이 보정들은 HTML 코멘트로 `<!-- TEMP: 1:1 fidelity, 템플릿화 시 제거 -->` 표시한다.
---
## R16. 이미지 프레임 배치 — overflow:hidden으로 부분 표시
**상황:** 하나의 원본 이미지에 양쪽 끝 모두 디자인 요소(곡선, 말림, 장식 등)가 있고, Figma에서 프레임(컨테이너)보다 이미지를 크게 배치하여 **한쪽만 보이게** 하는 경우.
**Figma가 하는 것:**
- 프레임: 457.96px (표시 영역)
- 이미지: 664px (원본, 프레임보다 큼)
- 이미지를 프레임 안에서 `left`, `width`로 위치/크기 지정
- 프레임에 `overflow: hidden` → 프레임 밖으로 나간 부분 안 보임
- 결과: 이미지의 **원하는 쪽만** 프레임 안에 보임
**Figma가 주는 값의 의미:**
```
left: -45.3%; width: 145.3%
→ 이미지를 좌측으로 45.3% 밀어서 배치
→ 좌측 끝이 프레임 밖으로 나감 → 좌측 디자인 요소 안 보임
→ 우측 디자인 요소만 프레임 안에 보임
left: 0; width: 151.25%
→ 이미지를 좌측 정렬, 우측이 프레임 밖으로 넘침
→ 우측 디자인 요소 안 보임
→ 좌측 디자인 요소만 프레임 안에 보임
```
**이것은 crop이 아니다.** 이미지를 자르는 것이 아니라, 프레임 안에서 이미지의 **위치**를 조절하는 것. 이미지 원본은 그대로 유지.
**CSS 구현:**
```css
.pill-frame {
position: relative; /* 또는 absolute */
width: 457.96px; /* 프레임 크기 */
height: 95.62px;
overflow: hidden; /* 핵심: 프레임 밖 숨김 */
}
.pill-frame img {
position: absolute;
top: 0;
left: -45.3%; /* Figma 값 그대로 */
width: 145.3%; /* Figma 값 그대로 */
height: 100%;
}
```
**절대 하지 말 것:**
- `width: 100%; object-fit: fill` — 이미지가 찌그러져 양쪽 디자인 요소가 다 보임
- `scaleX(-1)` 임의 추가 — Figma에 없는 변환
- `object-fit: cover/contain` — 이미지 비율/위치가 달라짐
- "crop"이라 부르기 — 이미지를 자르는 게 아니라 위치를 조절하는 것
**rotate(180deg) + 이미지 배치 주의:**
부모에 `rotate(180deg)`가 적용된 경우 (예: 하단 pill), 이미지가 상하좌우 모두 뒤집힘. 이때 **이미지 배치(left/width)를 상단과 반대로** 적용해야 최종 결과가 올바른 방향이 됨.
```
상단 left-pill: left: -45.3%; width: 145.3% → 우측 보임
하단 left-pill: rotate(180) + left: 0; width: 151.25% → 결과적으로 우측 보임 (뒤집혀서)
상단 right-pill: left: 0; width: 151.25% → 좌측 보임
하단 right-pill: rotate(180) + left: -45.3%; width: 145.3% → 결과적으로 좌측 보임 (뒤집혀서)
```
**검증 방법:** 각 pill을 개별 screenshot으로 뽑아서 Figma 원본 pill screenshot과 **곡선/직선 위치**를 1:1 대조. 양쪽 다 곡선이 보이면 이미지 배치가 잘못된 것.
**적용 사례:**
| 프레임 | 사용 | 비고 |
|--------|------|------|
| 1171281194 (issues-paired-rows) | 두루마리 pill 8개 | 첫 적용. 상/하 배치 반전 패턴 발견. |
Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 KiB

Loaded 100 of 1512 files, more files were not shown because too many files have changed in this diff. Show more