untracked files on main: dceb101 feat(#63): IMP-34 R1 donor capacity measured bound (u1+u2)

This commit is contained in:
2026-05-21 22:07:41 +09:00
commit 8f085a28d3
3220 changed files with 985495 additions and 0 deletions
@@ -0,0 +1,729 @@
# Phase Z-2 frame contracts catalog (executable v0).
#
# 목적 :
# Python mapper 에 박혀있던 slot 구조 / cardinality / visual role 을 catalog 로 끌어냄.
# generic mapper (src/phase_z2_mapper.py:map_with_contract) 가 본 catalog 를 읽고
# MdxSection → slot_payload 변환. frame 별 hand-coded mapper 를 제거 방향.
#
# 원칙 :
# - frame 추가 = 본 yaml 에 entry 추가 (Python 수정은 새 builder/parser 필요시에만)
# - role_order = 순서 기반 visual role (label 키워드 기반 X — 다른 MDX 도 깨지지 않게)
# - cardinality strict 위반 → FitError → fallback path (AI restructuring 후보)
# - payload.builder = PAYLOAD_BUILDERS named registry 의 entry
# - builder 가 ITEM_PARSERS / COLUMN_BODY_PARSERS 같은 sub-primitive 호출
#
# v0 등록 frame :
# F13 three_parallel_requirements → builder=items_with_role
# F29 process_product_two_way → builder=process_product_pair
# F16 bim_issues_quadrant_four → builder=quadrant_flat_slots
# 이 셋이 현재 runtime 에서 실제 쓰는 주요 frame 의 모든 mapper.
three_parallel_requirements:
template_id: three_parallel_requirements
frame_id: 1171281190
family: three_parallel
source_shape: top_bullets
cardinality:
strict: 3
overflow_policy: abort_or_review
# 순서 기반 visual role (color_class 대체 — label 키워드 의존 제거)
role_order:
- tech
- people
- nature
# zone-level layout 힌트 — pipeline 의 compute_zone_layout 이 본 값을 lower bound 로 사용.
# 230 = 본 frame 의 token-based font 기준 최소 가독 높이 (Figma → CSS adapt 검증값).
visual_hints:
min_height_px: 230
# NEW (SPEC v1 §3) : 본 frame 이 받을 수 있는 content_object type (Layer A → Layer B 매칭 입력).
# 현재 mapper 는 본 필드를 *읽지 않음* — dormant. Layer A planner (B2) 가 도입 시 소비 예정.
accepted_content_types:
- text_block
# NEW (SPEC v1 §3) : Frame Slot (Layer B) 선언 — frame 내부 자리.
# YAML field name 'sub_zones' = 코드/catalog reality 유지. 의미 = Frame Slot.
# partial_target_path = path *식별자* 만 (값). 실제 marker attribute (data-frame-slot 등) 은 별 axis (B5).
sub_zones:
- id: pillar_1
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f13b__cols > .f13b__col:nth-child(1)"
- id: pillar_2
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f13b__cols > .f13b__col:nth-child(2)"
- id: pillar_3
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f13b__cols > .f13b__col:nth-child(3)"
payload:
title:
source: section.title
builder: items_with_role
builder_options:
item_parser: pillar_item # ITEM_PARSERS entry
array_root: pillars # payload[array_root] = list of items
role_field: color_class # role_order[i] → item[role_field]
process_product_two_way:
template_id: process_product_two_way
frame_id: 1171281210
family: two_column_h3
source_shape: h3_subsections
cardinality:
strict: 2 # F29 frame = 2 visual columns. ≠2 → fallback.
overflow_policy: abort_or_review
# transform-block + 3 sections 의 breathing 위해 345 (이전 hardcoded 동일).
visual_hints:
min_height_px: 345
# NEW (SPEC v1 §3) : 본 frame 이 받을 수 있는 content_object type.
# process column 은 transform_table (AS-IS/TO-BE) 도 수용. product column 은 text_block 만.
# 현재 mapper 는 본 필드를 *읽지 않음* — dormant.
accepted_content_types:
- text_block
- transform_table
# NEW (SPEC v1 §3) : Frame Slot (Layer B) 선언.
# 2 column × 3 section = 6 cell, but Frame Slot 단위 = column.
# partial_target_path = path 식별자 만 (값). marker attribute 결정은 별 axis.
sub_zones:
- id: process_column
role: main_text
accepts: [text_block, transform_table]
cardinality: { strict: 3 } # 3 sections per column
partial_target_path: ".f29b__grid .f29b__cell--left"
- id: product_column
role: main_text
accepts: [text_block] # product 쪽은 transform 안 받음 (현재 frame 의 시각 구분)
cardinality: { strict: 3 }
partial_target_path: ".f29b__grid .f29b__cell--right"
payload:
title:
source: section.title
builder: process_product_pair
builder_options:
pad_sections_to: 3 # 각 column.sections 길이 = 3 (legacy 와 동일)
columns:
- title_to: banner_left
body_to: process
body_parser: column_with_transform # 첫 top-bullet AS-IS/TO-BE 표 인식
- title_to: banner_right
body_to: product
body_parser: column_plain # 모든 section = 일반 text_lines
bim_issues_quadrant_four:
template_id: bim_issues_quadrant_four
frame_id: 1171281193
family: bim_issues_quadrant
source_shape: top_bullets
# F16 정책 = pad_to=4 + truncate>4 (legacy 와 동일). cardinality strict 화는 본 transition 범위 외.
# 향후 normal path 안정 후 strict 적용 + 위반 시 fallback path (FitError) 검토.
# NEW (SPEC v1 §3) : 본 frame 이 받을 수 있는 content_object type.
# 현재 mapper 는 본 필드를 *읽지 않음* — dormant.
accepted_content_types:
- text_block
# NEW (SPEC v1 §3) : Frame Slot (Layer B) 선언 — 4 quadrant.
# sub_zone 별 cardinality strict: 1 = 각 Frame Slot 의 *capacity* (Layer B schema).
# 기존 payload.builder_options.pad_to=4 / truncate_at=4 = mapper runtime 거동 (input mismatch 시).
# 두 layer 는 *공존* — 본 axis 에서 builder_options 는 미변경.
sub_zones:
- id: quadrant_1
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f16b__quadrant--tl"
- id: quadrant_2
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f16b__quadrant--tr"
- id: quadrant_3
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f16b__quadrant--bl"
- id: quadrant_4
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f16b__quadrant--br"
payload:
title:
source: section.title
builder: quadrant_flat_slots
builder_options:
item_parser: quadrant_item # ITEM_PARSERS entry
pad_to: 4 # quadrant_1..4 모두 채움 (부족 시 empty pad)
truncate_at: 4 # 5번째 이후 무시 + _truncated_count 기록
label_key_pattern: "quadrant_{n}_label"
body_key_pattern: "quadrant_{n}_body"
empty_label: ""
empty_body: []
# implicit slot_order = [quadrant_1=TL, quadrant_2=TR, quadrant_3=BL, quadrant_4=BR]
# 위치(TL/TR/BL/BR) 매핑은 partial template (families/bim_issues_quadrant_four.html) 결정.
# ─── IMP-04 catalog 확장 (Gitea #4 scope-lock 7 frame) ───
# 본 entry 들은 기존 3 entry (F13/F29/F16) *뒤에* append — YAML order 가
# `list(load_frame_contracts().values())` 의 B4 trace 선택 순서 surface.
# Reason 명시 (Codex Catch 5) : 각 frame 의 V4 use_as_is/light_edit 신호.
three_persona_benefits:
# Reason : V4 use_as_is=1 (frame_number=14, frame_id=1171281191).
# Pattern : cards-3col-persona (발주자/시공자/설계자 3 주체 각 benefit).
template_id: three_persona_benefits
frame_id: 1171281191
family: cards
source_shape: top_bullets
cardinality:
strict: 3 # 3 persona = strict.
overflow_policy: abort_or_review
# 순서 기반 color theme (Figma 원본 색).
# client = 발주자 (#285b4a 그린 계열), constructor = 시공자 (#445a2f 올리브),
# designer = 설계자 (#743002 적갈) — partial 의 .f14b__col--{role} 매핑.
role_order:
- client
- constructor
- designer
# min_height_px : derive + confirm (Codex round 13 §2.2 + round 24 asset 분류
# + round 26 self-consistency catch + round 28 M1 결정).
# Figma frame 1927 px @ scale 0.49213 → 948 px adapted (full frame).
# Phase Z slide-body ≤ 585 px → adaptive content fit.
# Content density 2nd refinement (raster promoted) :
# badge raster 70 + bullet body 210 + photo strip 36 + padding 30 = 346 sum
# + 4 safety buffer = **350** (F29 345 와 동등 class).
# confirm via smoke harness `--render-to` artifact (browser visual inspection).
visual_hints:
min_height_px: 350
accepted_content_types:
- text_block
# Frame Slot 선언 (SPEC v1 §3 layer B). 3 persona = 3 sub-zone.
sub_zones:
- id: persona_1
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f14b__cols > .f14b__col:nth-child(1)"
- id: persona_2
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f14b__cols > .f14b__col:nth-child(2)"
- id: persona_3
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f14b__cols > .f14b__col:nth-child(3)"
payload:
title:
source: section.title
# builder 재사용 — F13 의 `items_with_role` + F16 의 `quadrant_item` parser.
# quadrant_item = {label, body:[{text,indent}]} → persona card body 정합.
builder: items_with_role
builder_options:
item_parser: quadrant_item # ITEM_PARSERS entry (F16 재사용)
array_root: personas # payload.personas = list of items
role_field: color_class # role_order[i] → item.color_class
construction_goals_three_circle_intersection:
# Reason : V4 use_as_is=1 (02-1) + light_edit=1 (01-1) + restructure=1 — UAI tier strongest.
# Pattern : diagram / cycle-3way-intersection (3 메인 원 + 중앙 intersection).
# Track A frame 2 / IMP-04 Track A V4 priority strict (Codex round 30/33/35 합의).
template_id: construction_goals_three_circle_intersection
frame_id: 1171281189
family: diagram
source_shape: top_bullets
cardinality:
strict: 3 # 3 메인 원 — strict.
overflow_policy: abort_or_review
# 순서 기반 visual role (3 메인 원 각자 다른 gradient — index.html L141-189).
# circle_1 = 안전(orange/red), circle_2 = 생산성(brown/grey), circle_3 = 소통(green/dark).
role_order:
- safety
- productivity
- trust
# min_height_px derivation :
# Figma frame total height = 1195 px @ scale 0.58312 → 697 px adapted (full frame).
# Phase Z slide-body ≤ 585 px → compact adapted layout 필요.
# Content density (3 메인 원 visual + title) — F14 (350, raster-heavy) 보다 가벼움.
# Derived = 320 (3 원 60-80 px × 3 + title 30 + intersection 30 + padding 30).
# Confirm via smoke `--render-to` artifact (R3 acceptance gate).
visual_hints:
min_height_px: 320
accepted_content_types:
- text_block
# Frame Slot 선언 (SPEC v1 §3 layer B). 3 메인 원 + intersection center.
sub_zones:
- id: circle_1
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f12b__circles > .f12b__circle:nth-child(1)"
- id: circle_2
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f12b__circles > .f12b__circle:nth-child(2)"
- id: circle_3
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f12b__circles > .f12b__circle:nth-child(3)"
- id: intersection
role: emphasis
accepts: [text_block]
cardinality: { min: 0, max: 1 } # OPTIONAL — analysis.md "slots 5개, required 4개" 정합 (Codex F1-a 정정)
partial_target_path: ".f12b__intersection"
payload:
title:
source: section.title
# New builder `cycle_intersect_3` (phase_z2_mapper.py) — 3 circle labels + intersection.
# parser 재사용 = `quadrant_item` (label 만 사용, body 무시).
builder: cycle_intersect_3
builder_options:
item_parser: quadrant_item # F16 의 parser 재사용 (label only)
pad_to: 3
truncate_at: 3
label_key_pattern: "circle_{n}_label"
empty_label: ""
intersection_default: "" # MDX 안 intersection 명시 안 되면 빈 문자
construction_bim_three_usage:
# Reason : V4 UAI=1 (01-1) + RS=1 — UAI tier strongest after F12.
# Pattern : cards / cards-3-category — 3 horizontal stacked rows.
# Track A frame 3 (Codex round 39 accepted, V4 priority strict).
template_id: construction_bim_three_usage
frame_id: 1171281182
family: cards
source_shape: top_bullets
cardinality:
strict: 3
overflow_policy: abort_or_review
# 순서 기반 visual role — 3 카테고리 (model / object / position) 각자
# Figma 의 동일 brown label box + white content card 패턴. role 은
# MDX label 내용 따라 결정되므로 generic ordering name 만.
role_order:
- category_1
- category_2
- category_3
# min_height_px derivation (round 13 §2.2 — derive + confirm) :
# Figma frame 1066 px @ scale 0.67542 → 720 px adapted (full frame).
# Phase Z compact (title + 3 rows + padding) :
# title 30 + 3 row × 70 (label + body) + gap 6×2 + padding 30 = 282 px.
# + safety buffer (body bullets 2-3 lines/row 보호) 38 = **320**.
# Confirm via smoke `--render-to` artifact + MDX 03 regression.
visual_hints:
min_height_px: 320
accepted_content_types:
- text_block
# Frame Slot 선언 (SPEC v1 §3 layer B). 3 category × {label + body}.
sub_zones:
- id: category_1
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f11b__rows > .f11b__row:nth-child(1)"
- id: category_2
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f11b__rows > .f11b__row:nth-child(2)"
- id: category_3
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f11b__rows > .f11b__row:nth-child(3)"
payload:
title:
source: section.title
# Builder 재사용 = `quadrant_flat_slots` (F16) — pad_to / label_key / body_key
# 만 configure. F16 / 본 frame 모두 *flat keyed slot + label + body* grammar.
# Codex round 39 secondary builder reuse 허용 (V4 primary 보존).
builder: quadrant_flat_slots
builder_options:
item_parser: quadrant_item # F16 의 parser 재사용
pad_to: 3 # 3 categories
truncate_at: 3
label_key_pattern: "category_{n}_label"
body_key_pattern: "category_{n}_body"
empty_label: ""
empty_body: []
bim_dx_comparison_table:
# Reason : V4 UAI=1 (01-2 "용어간 상호관계") — UAI tier strongest after F12/F11.
# Pattern : table / compare-rows — 2-column compare table, N category rows.
# Track A frame 4 (Codex round 41 accepted, V4 priority strict).
# NEW builder `compare_table_2col` + NEW parser `compare_row_2col_item`.
template_id: bim_dx_comparison_table
frame_id: 1171281195
family: table
source_shape: top_bullets
# NOTE (Codex round 43 §F1-c) : top-level `cardinality.strict: 2` = *column 수*
# (col_a / col_b). data row 수 는 별 — `sub_zones.rows.cardinality` 의 `{min:1, max:12}`.
cardinality:
strict: 2 # 2 columns (col_a / col_b) — NOT row count
overflow_policy: abort_or_review
role_order:
- col_a
- col_b
# min_height_px derivation :
# Figma frame compare table — N rows × ~40 px row height.
# Phase Z compact (title 30 + header 30 + 6 data rows × 35 + padding 30) = 300
# + safety buffer (longer cell content 보호) 50 = **350**. F14 와 동등 class.
# Confirm via smoke + R3 artifact.
visual_hints:
min_height_px: 350
accepted_content_types:
- text_block
# Frame Slot 선언 : header (col_a/col_b) + rows[].
sub_zones:
- id: col_a_header
role: header
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f18b__header > .f18b__header-cell:nth-child(2)"
- id: col_b_header
role: header
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f18b__header > .f18b__header-cell:nth-child(3)"
- id: rows
role: data
accepts: [text_block]
cardinality: { min: 1, max: 12 } # N category rows (typical 4-8)
partial_target_path: ".f18b__rows"
payload:
title:
source: section.title
# NEW builder `compare_table_2col` (phase_z2_mapper.py) +
# NEW parser `compare_row_2col_item` (ITEM_PARSERS).
builder: compare_table_2col
builder_options:
item_parser: compare_row_2col_item # NEW parser — top_bullet → {label, col_a, col_b}
col_a_label_default: "BIM" # F1-a (Codex round 43) — explicit default
col_b_label_default: "DX" # F1-a — explicit default
strip_col_prefix_aliases: # F1-b (Codex round 43) — narrow alias 만 strip
- "BIM"
- "DX"
max_rows: 12 # typical 4-8, overflow 보호
dx_sw_necessity_three_perspectives:
# Reason : V4 LE=2 (03-1 + 01-1) + RS=1 — V4 LE tier strongest 잔여.
# Pattern : cards / cards-3-header — 3 columns horizontal (DX 필수성 3 관점).
# Track A frame 5 (Codex round 45 accepted, V4 priority strict).
# builder 재사용 = `quadrant_flat_slots` (F16/F11 pattern). 새 builder/parser 0.
template_id: dx_sw_necessity_three_perspectives
frame_id: 1171281198
family: cards
source_shape: top_bullets
cardinality:
strict: 3 # 3 perspective columns
overflow_policy: abort_or_review
# 순서 기반 role naming (3 perspective 각자 color 도 다를 수 있음 — 단순 ordering).
role_order:
- perspective_1
- perspective_2
- perspective_3
# min_height_px derivation :
# 3 column × (header 30 + body bullets ~3 × 25) + title 30 + padding 30 = ~ 195.
# safety buffer (label 길고 body 5+ bullets 보호) = 125 → **320**.
# F12/F11/F18 와 동등 class. confirm via smoke + R3.
visual_hints:
min_height_px: 320
accepted_content_types:
- text_block
# Frame Slot 선언 : 3 perspective columns (각 column = label + body).
sub_zones:
- id: perspective_1
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f20b__cols > .f20b__col:nth-child(1)"
- id: perspective_2
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f20b__cols > .f20b__col:nth-child(2)"
- id: perspective_3
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f20b__cols > .f20b__col:nth-child(3)"
payload:
title:
source: section.title
# Builder 재사용 = `quadrant_flat_slots` (F16/F11 pattern).
# 새 builder / 새 parser 0. F18 같은 새 builder 도입 아님 (Codex round 45 secondary reuse 허용).
builder: quadrant_flat_slots
builder_options:
item_parser: quadrant_item # F16/F11 의 parser 재사용
pad_to: 3 # 3 perspectives
truncate_at: 3
label_key_pattern: "perspective_{n}_label"
body_key_pattern: "perspective_{n}_body"
empty_label: ""
empty_body: []
info_management_what_how_when:
# Reason : V4 신호 0 (4 MDX sample 안). 32-frame all-in scope 채우는
# **catalog-completeness activation** (NOT V4 endorsement, Codex round 47 guardrail).
# V4 는 matching authority. 향후 다른 MDX section 에서 V4 가 이 frame 을 추천 시
# backend 가 받을 수 있도록 등록.
# Pattern : cards/3-section-framework (What/How/When 3 관점 framework).
# Track A frame 6. Builder 재사용 = `quadrant_flat_slots` (F11/F20 pattern).
template_id: info_management_what_how_when
frame_id: 1171281179
family: cards
source_shape: top_bullets
cardinality:
strict: 3 # 3 sections (What / How / When)
overflow_policy: abort_or_review
role_order:
- section_1 # What (무슨 정보)
- section_2 # How (어떻게 연계)
- section_3 # When (언제 사용)
# min_height_px : F20 와 동등 (3-column body + header). 320 = 3×(header 30 +
# body bullets 75) + title 30 + padding 30 + buffer 125 / 195 base.
visual_hints:
min_height_px: 320
accepted_content_types:
- text_block
sub_zones:
- id: section_1
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f8b__cols > .f8b__col:nth-child(1)"
- id: section_2
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f8b__cols > .f8b__col:nth-child(2)"
- id: section_3
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f8b__cols > .f8b__col:nth-child(3)"
payload:
title:
source: section.title
builder: quadrant_flat_slots # 재사용 (F16/F11/F20 pattern)
builder_options:
item_parser: quadrant_item # 재사용
pad_to: 3
truncate_at: 3
label_key_pattern: "section_{n}_label"
body_key_pattern: "section_{n}_body"
empty_label: ""
empty_body: []
sw_reality_three_emphasis:
# Reason : V4 신호 0 (4 MDX sample). 32-frame all-in scope
# **catalog-completeness activation** (NOT V4 endorsement, Codex round 49 §1+§2).
# Pattern : cards / title-plus-3-emphasis — 3 problem-emphasis cards parallel.
# Track A frame 7 (single-frame, Codex round 49 §9 — no batch yet).
# Builder 재사용 = `quadrant_flat_slots` (F11/F20/F8 pattern).
template_id: sw_reality_three_emphasis
frame_id: 1171281209
family: cards
source_shape: top_bullets
cardinality:
strict: 3
overflow_policy: abort_or_review
role_order:
- emphasis_1
- emphasis_2
- emphasis_3
visual_hints:
min_height_px: 320
accepted_content_types:
- text_block
sub_zones:
- id: emphasis_1
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f28b__cols > .f28b__col:nth-child(1)"
- id: emphasis_2
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f28b__cols > .f28b__col:nth-child(2)"
- id: emphasis_3
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f28b__cols > .f28b__col:nth-child(3)"
payload:
title:
source: section.title
builder: quadrant_flat_slots # 재사용
builder_options:
item_parser: quadrant_item # 재사용
pad_to: 3
truncate_at: 3
label_key_pattern: "emphasis_{n}_label"
body_key_pattern: "emphasis_{n}_body"
empty_label: ""
empty_body: []
bim_current_problems_paired:
# Reason : V4 RS=1 (4 MDX sample 안 restructure 1). source 가 8 atomic issues 임을 confirm.
# **schema-correction** — round 55~73 의 review-loop 의 calibration frame.
# 자체 round 71 §4 F17 done-definition lock per Codex round 70/72 + Claude round 63 §5.
# Pattern : cards/paired-rows-4x2 — 4 paired rows × 2 issue cells (top/bottom pill alternation).
# Track A frame 8. 신규 builder = `paired_rows_4x2_slots` (quadrant_flat_slots → 2-axis).
template_id: bim_current_problems_paired
frame_id: 1171281194
family: cards
source_shape: top_bullets # mapper split_source allow-list 정합 (Codex round 60)
layout_variant: paired_rows_4x2_alternating_pills # runtime projection model
cardinality:
strict: 8 # 8 atomic issues (4 rows × 2 cells per source texts.md)
overflow_policy: abort_or_review
# row × side deterministic mapping per Codex round 70 §1.
role_order:
- row_1_left
- row_1_right
- row_2_left
- row_2_right
- row_3_left
- row_3_right
- row_4_left
- row_4_right
# min_height_px : provisional 380 (8 cells + title + gaps), smoke 후 1 회 조정 가능.
# pill_positions : source index.html line 161~225 의 row-별 pill 상/하 alternation (top/bottom/top/bottom).
# row_gap_after : source line 199-200 의 `<div class="row-gap">` (행 2-3 사이 시각 단락) per Codex round 58 §3.
visual_hints:
min_height_px: 380
pill_positions: [top, bottom, top, bottom]
row_gap_after: [2]
accepted_content_types:
- text_block
sub_zones:
- id: row_1_left
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f17b__row:nth-child(1) > .f17b__cell--left"
- id: row_1_right
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f17b__row:nth-child(1) > .f17b__cell--right"
- id: row_2_left
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f17b__row:nth-child(2) > .f17b__cell--left"
- id: row_2_right
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f17b__row:nth-child(2) > .f17b__cell--right"
- id: row_3_left
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f17b__row:nth-child(3) > .f17b__cell--left"
- id: row_3_right
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f17b__row:nth-child(3) > .f17b__cell--right"
- id: row_4_left
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f17b__row:nth-child(4) > .f17b__cell--left"
- id: row_4_right
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
partial_target_path: ".f17b__row:nth-child(4) > .f17b__cell--right"
payload:
title:
source: section.title
# 신규 builder — quadrant_flat_slots 와 차이 : 1-axis (TL/TR/BL/BR) → 2-axis (row × side).
# strict 8 + no pad/truncate (silent loss 차단, Codex round 60 §3).
# parser = quadrant_item 재사용 (label + body heading-less, Codex round 60).
builder: paired_rows_4x2_slots
builder_options:
item_parser: quadrant_item
label_key_pattern: "row_{r}_{side}_label"
body_key_pattern: "row_{r}_{side}_body"
rows: 4
sides: [left, right]
@@ -0,0 +1,37 @@
# Phase Z Families — WIP Marker
**Status:** intentionally untracked, uncontracted, out-of-scope for runtime matcher.
**Closes audit follow-up:** INTEGRATION-AUDIT-01-REPORT.md §10.2 F-2 (option c).
**Gate for promote/remove:** Gitea issue #42 (`IMP-04b Catalog extension to 32 frames`).
## Baseline lock (2026-05-19)
| Surface | Count | Notes |
|---|---|---|
| `git ls-files templates/phase_z2/families/*.html` | 11 | tracked family templates |
| `templates/phase_z2/catalog/frame_contracts.yaml` top-level keys | 11 | 1:1 with tracked basenames |
| `Get-ChildItem templates/phase_z2/families/*.html` | 13 | tracked 11 + WIP 2 (below) |
Active contracted family count = **11**. Tracked basenames ↔ `frame_contracts.yaml` top-level keys are set-equal. Drift between disk (13) and contracted (11) is fully explained by the 2 WIP files below.
## WIP family templates (uncontracted)
| File | Figma frame | Status |
|---|---|---|
| `app_sw_package_vs_solution.html` | frame 23 (`1171281203`) | WIP — not in `frame_contracts.yaml`, not in runtime matcher set. |
| `pre_construction_model_info_stacked.html` | frame 9 (`1171281180`) | WIP — not in `frame_contracts.yaml`, not in runtime matcher set. |
These files are partials authored during Phase Z-2 MVP-1.5b exploration. They are **not** part of the contracted Phase Z runtime catalog and must not be enumerated by frame selection, matcher, or any Stage 3 pipeline surface.
## Rules
- Adding a file to `templates/phase_z2/families/*.html` without a matching `frame_contracts.yaml` entry is **only** permitted if it is named here as WIP.
- `tests/test_family_contract_baseline.py` enforces this invariant: tracked families ↔ `frame_contracts.yaml` keys must be set-equal, modulo the WIP allowlist parsed from this file.
- Promoting a WIP file (add `frame_contracts.yaml` entry + register with matcher) or removing it must happen under issue #42 or a follow-up issue, not silently.
## References
- `docs/architecture/INTEGRATION-AUDIT-01-REPORT.md` §10.2 F-2 (audit finding closed by issue #52, option c)
- `docs/architecture/IMP-18-SVG-GAP-REPORT.md` L28, L51 (count basis corrected to `11 contracted + 2 WIP`)
- Gitea issue #52 (this reconciliation)
- Gitea issue #42 (pre-flight gate for promote/remove)
@@ -0,0 +1,159 @@
<!-- Phase Z-2 MVP F17 schema-correction partial — paired_rows_4x2_alternating_pills.
§17 룰 — Figma 시각 언어 promote, geometry 만 zone-compatible adapt.
본 partial = round 55~73 review-loop 의 calibration frame implementation. -->
{#
─────────────────────────────────────────────────────────────────────────────
Frame 17 = "현황 및 문제점" (cards/paired-rows-4x2-alternating-pills).
**source = 8 atomic issues** (4 paired rows × 2 issue cells per row).
이전 strict-4 → source 의 절반 누락. round 55~73 review-loop 의 schema-correction.
Visual provenance — figma_to_html_agent/blocks/1171281194/{index.html, texts.md, assets}.
- title gradient : rgb(204,82,0) → rgb(136,55,0) (source line 50)
- row border : 3px solid #60A451 (source line 69)
- row bg : rgba(250,237,203,0.15) (source line 68)
- body text : #0c271e (source line 130)
- 분할선 (cell 사이) : 2px dashed #60A451 (source line 135)
- pill alternation : row 1/3 = pill 상단, row 2/4 = pill 하단 (source line 161~225)
- row 2-3 visual gap : source `<div class="row-gap">` (height 65px) line 199-200
PROMOTED CSS : source-faithful unified green/cream/brown theme + pill shape +
top/bottom/top/bottom pill alternation + row 2-3 visual gap.
NOT PROMOTED (scope cap per Codex round 64/72 — CSS fallback 정당) :
- pill asset crop variant (`.crop-left` left -45.3% width 145.3% / `.crop-right` width 151.25%) —
source 의 PNG asset 정확 crop reproduction 은 single-pass scope 외 (Codex round 62 §6
+ round 68 scope cap). CSS approximation = pill shape + alternation + theme 모두 보존.
- bottom pill flip image transform (source line 117) — 단순 column-reverse 로 처리.
ADAPTED : Figma absolute → 4-row flex layout + token-fixed typography.
slots (17) : title +
row_{1..4}_{left,right}_{label,body} (8 × 2 = 16).
no row headers (source 부재, infer 금지 per Codex round 58 §1 + round 70 §3.6).
#}
<style>
.f17b {
width: 100%; height: 100%;
display: flex; flex-direction: column;
gap: 8px;
font-family: 'Noto Sans KR', 'Pretendard', sans-serif;
word-break: keep-all;
}
/* title — source line 50 gradient + drop-shadow (text-shadow approximation) */
.f17b__title {
font-size: var(--font-zone-title);
font-weight: 700;
line-height: var(--lh-zone-title);
background-image: linear-gradient(rgb(204,82,0), rgb(136,55,0));
-webkit-background-clip: text; background-clip: text;
color: transparent;
flex-shrink: 0;
filter: drop-shadow(0 0 3px rgba(50,44,30,0.4));
}
/* rows 컨테이너 (4 paired rows) */
.f17b__rows {
display: flex; flex-direction: column;
gap: 4px;
flex: 1 1 auto;
min-height: 0;
}
/* row 2 뒤 visual gap (visual_hints.row_gap_after = [2]) — source line 199-200 */
.f17b__row:nth-child(2) { margin-bottom: 10px; }
/* row = 좌우 2 cells flex */
.f17b__row {
display: flex; flex-direction: row;
gap: 6px;
flex: 1 1 0;
min-height: 0;
border: 2px solid #60A451;
border-radius: 8px;
background: rgba(250,237,203,0.15);
overflow: hidden;
position: relative;
}
/* cell — pill alternation per visual_hints.pill_positions [top,bottom,top,bottom] */
.f17b__cell {
flex: 1 1 0;
display: flex; flex-direction: column;
padding: 8px 10px;
min-width: 0;
gap: 4px;
}
/* 분할선 — source line 135 dashed 2px #60A451 (cell 사이) */
.f17b__cell--right { border-left: 2px dashed #60A451; }
/* row 1, 3 = pill 상단 (default column) */
.f17b__row:nth-child(1) .f17b__cell,
.f17b__row:nth-child(3) .f17b__cell { flex-direction: column; }
/* row 2, 4 = pill 하단 (column-reverse — pill 이미지 flip 은 단순 reverse 로 approximate per source line 117) */
.f17b__row:nth-child(2) .f17b__cell,
.f17b__row:nth-child(4) .f17b__cell { flex-direction: column-reverse; }
/* pill — CSS approximation (source assets/*.png crop variant fallback per round 64/72 scope cap).
pill shape (rounded) + green/cream/brown theme 보존. */
.f17b__pill {
display: inline-flex; align-items: center; justify-content: center;
background-image: linear-gradient(180deg, #60A451 0%, #3d6d34 100%);
color: #fff;
font-weight: 700;
font-size: var(--font-sub-title);
line-height: 1.15;
padding: 4px 14px;
border-radius: 999px;
text-align: center;
flex-shrink: 0;
letter-spacing: -0.02em;
align-self: flex-start;
box-shadow: 0 1px 3px rgba(0,0,0,0.15);
}
/* body — source line 130 color #0c271e */
.f17b__body {
flex: 1 1 auto;
overflow: hidden;
color: #0c271e;
min-height: 0;
display: flex; flex-direction: column;
gap: 2px;
}
.f17b__body .text-line {
color: inherit;
font-size: var(--font-body);
line-height: var(--lh-body);
}
</style>
<div class="f17b" data-frame-id="1171281194" data-template-id="bim_current_problems_paired">
<div class="f17b__title">{{ slot_payload.title }}</div>
<div class="f17b__rows">
{% for r in [1, 2, 3, 4] %}
<div class="f17b__row">
<div class="f17b__cell f17b__cell--left">
<div class="f17b__pill">{{ slot_payload['row_' ~ r ~ '_left_label'] | safe }}</div>
<div class="f17b__body">
{% set body_left = slot_payload['row_' ~ r ~ '_left_body'] %}
{% if body_left %}
{% for line in body_left %}<div class="text-line{% if line.indent and line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
{% endif %}
</div>
</div>
<div class="f17b__cell f17b__cell--right">
<div class="f17b__pill">{{ slot_payload['row_' ~ r ~ '_right_label'] | safe }}</div>
<div class="f17b__body">
{% set body_right = slot_payload['row_' ~ r ~ '_right_body'] %}
{% if body_right %}
{% for line in body_right %}<div class="text-line{% if line.indent and line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
@@ -0,0 +1,146 @@
<!-- Phase Z-2 MVP-1.5b frame-derived adapted block.
§17 룰 — Figma 시각 언어 promote, geometry 만 zone-compatible adapt. -->
{#
─────────────────────────────────────────────────────────────────────────────
Visual Provenance — figma_to_html_agent/blocks/1171281195/ (frame 18)
─────────────────────────────────────────────────────────────────────────────
Frame 18 = "BIM 과 DX 의 이해" (table / compare-rows). 2개 column header (BIM vs DX) +
중앙 category column + N category rows (범위 / S/W / 프로세스 / 성과물 / 활용 /
확장성 / 수행개념 / 주체 등). compare 관점의 다면 비교 표 디자인.
본 partial = Track A frame 4 (Codex round 41 accepted, V4 UAI tier). NEW builder
`compare_table_2col` + NEW parser `compare_row_2col_item` 도입.
3-layer architecture (matrix §0) :
- V4 = matching authority — V4 ranked this frame use_as_is for 01-2.
- figma_to_html = source/evidence — analysis/texts/index.html/flat/assets full.
- Phase Z = runtime orchestration — 본 commit 추가 runtime 자원.
PROMOTED — CSS :
- title gradient (#000 → #883700, F13/F14/F12/F11 zone-title family)
- header row : col_a/col_b background tint + bold typography
- row 별 alternating background (zebra) — readability ↑
- cell border : subtle divider (#A5BBB4 family — F11 와 통일)
NOT PROMOTED (P1 case-by-case, compact zone fit) :
- Figma 의 column header 의 *raster icon* (있는 경우) — compact 에서 효과 ↓
- 다양한 색 강조 / 한자 deco (Figma source 보존, 미 promote)
ADAPTED :
- Figma 표 absolute positioning + zoom → Phase Z flex/grid (3-column table)
- typography → token-fixed (zone-title / sub-title / caption / body)
- row 별 height auto (content-fit)
─────────────────────────────────────────────────────────────────────────────
min_height_px derivation :
title 30 + header 30 + 6 data rows × 35 (compact) + padding 30 = 300
+ safety buffer (longer cell content) 50 = **350** (F14 와 동등 class).
Confirm via smoke + R3 artifact + V4 use_as_is target sample (MDX 01-2 미 trigger
in MDX 03 regression, 단 future batch verification 가능).
─────────────────────────────────────────────────────────────────────────────
slots :
- title : section.title
- col_a_label : 좌 column header (예: "BIM")
- col_b_label : 우 column header (예: "DX")
- rows : list[{label, col_a, col_b}] — N category rows
─────────────────────────────────────────────────────────────────────────────
4-class failure taxonomy :
- class 1 adapter readiness : NEW builder `compare_table_2col` + NEW parser
`compare_row_2col_item` 등록 필수 (PAYLOAD_BUILDERS / ITEM_PARSERS). 본 commit.
- class 2 content-fit : row 수 max 12 (builder limit). cell content 한 줄 한계
— 긴 문장 시 truncate 또는 wrap (CSS overflow-hidden).
#}
<style>
.f18b {
width: 100%; height: 100%;
display: flex; flex-direction: column;
gap: 6px;
font-family: 'Noto Sans KR', 'Pretendard', sans-serif;
word-break: keep-all;
}
.f18b__title {
font-size: var(--font-zone-title);
font-weight: 700;
line-height: var(--lh-zone-title);
background-image: linear-gradient(180deg, #000 0%, #883700 100%);
-webkit-background-clip: text; background-clip: text;
color: transparent;
flex-shrink: 0;
filter: drop-shadow(0 0 3px rgba(50,44,30,0.4));
}
/* compare table — 3-column (category / col_a / col_b) */
.f18b__table {
display: grid;
grid-template-columns: 1fr 2fr 2fr;
gap: 0;
flex: 1 1 auto;
min-height: 0;
border: 1px solid #A5BBB4;
border-radius: 6px;
overflow: hidden;
background: #fff;
}
/* header row */
.f18b__header {
display: contents;
}
.f18b__header-cell {
background: linear-gradient(180deg, rgba(50,31,9,0.85) 0%, rgba(50,31,9,0.95) 100%);
color: #fff;
font-weight: 700;
font-size: var(--font-caption);
line-height: 1.2;
padding: 6px 8px;
text-align: center;
border-bottom: 2px solid #A5BBB4;
}
.f18b__header-cell--category {
background: linear-gradient(180deg, rgba(120,90,40,0.85) 0%, rgba(120,90,40,0.95) 100%);
font-style: italic;
}
/* data rows */
.f18b__rows {
display: contents;
}
.f18b__cell {
padding: 5px 8px;
font-size: var(--font-body);
line-height: var(--lh-body);
border-bottom: 1px solid rgba(165,187,180,0.4);
color: #1a1a1a;
overflow: hidden;
}
.f18b__cell--category {
background: rgba(255,250,240,0.7);
font-weight: 700;
color: #5a4b2e; /* PROMOTED — brown family */
text-align: center;
}
/* zebra striping (visual readability) — odd rows alternate */
.f18b__row:nth-child(odd) .f18b__cell:not(.f18b__cell--category) {
background: rgba(248,250,252,0.6);
}
</style>
<div class="f18b" data-frame-id="1171281195" data-template-id="bim_dx_comparison_table">
<div class="f18b__title">{{ slot_payload.title }}</div>
<div class="f18b__table">
<div class="f18b__header">
<div class="f18b__header-cell f18b__header-cell--category">구분</div>
<div class="f18b__header-cell">{{ slot_payload.col_a_label | safe }}</div>
<div class="f18b__header-cell">{{ slot_payload.col_b_label | safe }}</div>
</div>
<div class="f18b__rows">
{% for row in slot_payload.rows %}
<div class="f18b__row" style="display:contents;">
<div class="f18b__cell f18b__cell--category">{{ row.label | safe }}</div>
<div class="f18b__cell f18b__cell--col-a">{{ row.col_a | safe }}</div>
<div class="f18b__cell f18b__cell--col-b">{{ row.col_b | safe }}</div>
</div>
{% endfor %}
</div>
</div>
</div>
@@ -0,0 +1,223 @@
<!-- Phase Z-2 frame-derived adapted block.
§17 룰 — Figma 시각 언어 promote, geometry 만 zone-compatible adapt. -->
{#
─────────────────────────────────────────────────────────────────────────────
Visual Provenance — figma_to_html_agent/blocks/1171281193/index.html
─────────────────────────────────────────────────────────────────────────────
PROMOTED (Figma 1:1 변환물 → 그대로 사용):
- card-bg gradient (TL/TR top→bottom) : line 49 → linear-gradient(180deg, #f2f2f2 0%, #ffffff 100%)
- card-bg gradient (BL/BR bottom→top) : line 53-54 → linear-gradient(0deg, #f2f2f2 0%, #ffffff 100%)
- header-ribbon (TL/BL — 갈색) : line 75, 79 → linear-gradient(270deg, rgba(165,161,150,0.5), #39321E 73%)
- header-ribbon (TR/BR — 청록) : line 83, 87 → linear-gradient(90deg, rgba(41,107,85,0.5), #032118)
- red headline color : line 127 → #ff0000
- body-text color : line 136 → #000
- center-quote (Rome wasn't Built in a day!) : line 233 (decorative, MDX 4-2.2 의 핵심 메시지로 대체 가능)
- bullet marker : line 241 → "•"
- card 모서리 radius : line 51-54 → 50px (top corners TL/TR, bottom corners BL/BR)
NOT PROMOTED (Figma 데코지만 MDX 04-2.2 에 없는 콘텐츠):
- center-quote 의 영문 ("Rome wasn't Built in a day!") — slot_payload.center_quote 로 동적 (없으면 omit)
ADAPTED (Figma px / native size 무시, zone-compatible):
- 2226×1766 inner + zoom 0.575 → CSS grid 2×2 (no zoom, no fixed inner)
- Figma 60px ribbon font / 55px headline / 42px body → token-fixed (zone-title 13 / sub-title 12 / body 11)
- 1080px card width / 119px ribbon height → grid 1fr 1fr / 22px ribbon
- 644×584 center 원 → 90px 원 (zone-fit)
- absolute positioning (4 quadrants) → grid-template-areas
─────────────────────────────────────────────────────────────────────────────
slots: title, center_quote (옵션), quadrant_{1,2,3,4}_{label, body}
- quadrant 1 = TL (정책 집행)
- quadrant 2 = TR (수행 개념)
- quadrant 3 = BL (근본 취지의 이해부족)
- quadrant 4 = BR (지속적 투자 의지 부재)
- body 형식 = text-line family 호환 HTML (text_lines list 또는 pre-rendered string)
#}
<style>
.f16b {
width: 100%; height: 100%;
display: flex; flex-direction: column;
gap: 4px;
font-family: 'Noto Sans KR', 'Pretendard', sans-serif;
word-break: keep-all;
position: relative;
}
.f16b__title {
font-size: var(--font-zone-title);
font-weight: 700;
line-height: var(--lh-zone-title);
color: #1e293b;
flex-shrink: 0;
padding-left: 4px;
}
/* 4 quadrants — 2×2 grid (CSS grid-template-areas) */
.f16b__quadrants {
flex: 1;
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
gap: 6px;
position: relative;
min-height: 0;
}
.f16b__quadrant {
display: flex; flex-direction: column;
overflow: hidden;
min-height: 0;
padding: 0 8px 6px;
/* PROMOTED — card-bg gradient */
}
.f16b__quadrant--tl {
background: linear-gradient(180deg, #f2f2f2 0%, #ffffff 100%);
border-radius: 14px 14px 0 0; /* PROMOTED 50px → adapted 14px */
}
.f16b__quadrant--tr {
background: linear-gradient(180deg, #f2f2f2 0%, #ffffff 100%);
border-radius: 14px 14px 0 0;
}
.f16b__quadrant--bl {
background: linear-gradient(0deg, #f2f2f2 0%, #ffffff 100%);
border-radius: 0 0 14px 14px;
}
.f16b__quadrant--br {
background: linear-gradient(0deg, #f2f2f2 0%, #ffffff 100%);
border-radius: 0 0 14px 14px;
}
/* Header ribbon — PROMOTED gradient + token font */
.f16b__ribbon {
height: 22px;
margin: 0 -4px 4px;
border-radius: 999px;
display: flex; align-items: center; justify-content: center;
font-size: var(--font-sub-title);
font-weight: 900;
color: #fff;
text-shadow: 0 0 3px #322c1e;
flex-shrink: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0 8px;
}
/* PROMOTED ribbon gradient — TL/BL 갈색, TR/BR 청록 (figma line 75,79,83,87) */
.f16b__quadrant--tl .f16b__ribbon,
.f16b__quadrant--bl .f16b__ribbon {
background: linear-gradient(270deg, rgba(165,161,150,0.5) -26.87%, #39321E 73.13%);
}
.f16b__quadrant--tr .f16b__ribbon,
.f16b__quadrant--br .f16b__ribbon {
background: linear-gradient(90deg, rgba(41,107,85,0.5) 0%, #032118 100%);
}
/* Body — red headline + text-line bullets */
.f16b__body {
flex: 1;
overflow: hidden;
min-height: 0;
font-size: var(--font-body);
line-height: var(--lh-body);
color: #000;
}
.f16b__headline {
color: #ff0000; /* PROMOTED line 127 */
font-weight: 700;
font-size: var(--font-sub-title);
line-height: 1.3;
margin-bottom: 3px;
}
/* text-line family 은 slide_base.html 의 공통 contract 사용 */
/* Center quote (옵션 — MDX 에 있을 때만) */
.f16b__center {
position: absolute;
left: 50%; top: 50%;
transform: translate(-50%, -50%);
width: 80px; height: 80px;
border-radius: 50%;
background: linear-gradient(135deg, rgba(60,50,30,0.85), rgba(20,15,5,0.92));
display: flex; align-items: center; justify-content: center;
z-index: 2;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
pointer-events: none;
}
.f16b__center-quote {
color: #fff;
font-size: 9px;
font-weight: 700;
text-align: center;
line-height: 1.2;
padding: 0 6px;
}
</style>
<div class="f16b" data-frame-id="1171281193" data-template-id="bim_issues_quadrant_four">
{% if slot_payload.title %}
<div class="f16b__title">{{ slot_payload.title }}</div>
{% endif %}
<div class="f16b__quadrants">
{# Quadrant 1 (TL) — 정책 집행 (갈색 ribbon) #}
<div class="f16b__quadrant f16b__quadrant--tl">
{% if slot_payload.quadrant_1_label %}
<div class="f16b__ribbon">{{ slot_payload.quadrant_1_label | safe }}</div>
{% endif %}
{% if slot_payload.quadrant_1_headline or slot_payload.quadrant_1_body %}
<div class="f16b__body">
{% if slot_payload.quadrant_1_headline %}<div class="f16b__headline">{{ slot_payload.quadrant_1_headline | safe }}</div>{% endif %}
{% for line in slot_payload.quadrant_1_body %}<div class="text-line text-line--bullet{% if line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
</div>
{% endif %}
</div>
{# Quadrant 2 (TR) — 수행 개념 (청록 ribbon) #}
<div class="f16b__quadrant f16b__quadrant--tr">
{% if slot_payload.quadrant_2_label %}
<div class="f16b__ribbon">{{ slot_payload.quadrant_2_label | safe }}</div>
{% endif %}
{% if slot_payload.quadrant_2_headline or slot_payload.quadrant_2_body %}
<div class="f16b__body">
{% if slot_payload.quadrant_2_headline %}<div class="f16b__headline">{{ slot_payload.quadrant_2_headline | safe }}</div>{% endif %}
{% for line in slot_payload.quadrant_2_body %}<div class="text-line text-line--bullet{% if line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
</div>
{% endif %}
</div>
{# Quadrant 3 (BL) — 근본 취지의 이해부족 (갈색 ribbon) #}
<div class="f16b__quadrant f16b__quadrant--bl">
{% if slot_payload.quadrant_3_label %}
<div class="f16b__ribbon">{{ slot_payload.quadrant_3_label | safe }}</div>
{% endif %}
{% if slot_payload.quadrant_3_headline or slot_payload.quadrant_3_body %}
<div class="f16b__body">
{% if slot_payload.quadrant_3_headline %}<div class="f16b__headline">{{ slot_payload.quadrant_3_headline | safe }}</div>{% endif %}
{% for line in slot_payload.quadrant_3_body %}<div class="text-line text-line--bullet{% if line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
</div>
{% endif %}
</div>
{# Quadrant 4 (BR) — 지속적 투자 의지 부재 (청록 ribbon) #}
<div class="f16b__quadrant f16b__quadrant--br">
{% if slot_payload.quadrant_4_label %}
<div class="f16b__ribbon">{{ slot_payload.quadrant_4_label | safe }}</div>
{% endif %}
{% if slot_payload.quadrant_4_headline or slot_payload.quadrant_4_body %}
<div class="f16b__body">
{% if slot_payload.quadrant_4_headline %}<div class="f16b__headline">{{ slot_payload.quadrant_4_headline | safe }}</div>{% endif %}
{% for line in slot_payload.quadrant_4_body %}<div class="text-line text-line--bullet{% if line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
</div>
{% endif %}
</div>
{# Center quote — 옵션 #}
{% if slot_payload.center_quote %}
<div class="f16b__center">
<div class="f16b__center-quote">{{ slot_payload.center_quote | safe }}</div>
</div>
{% endif %}
</div>
</div>
@@ -0,0 +1,169 @@
<!-- Phase Z-2 MVP-1.5b frame-derived adapted block.
§17 룰 — Figma 시각 언어 promote, geometry 만 zone-compatible adapt. -->
{#
─────────────────────────────────────────────────────────────────────────────
Visual Provenance — figma_to_html_agent/blocks/1171281182/ (frame 11)
─────────────────────────────────────────────────────────────────────────────
Frame 11 = "시공단계 BIM 모델·정보 활용 구분" (cards-3-category, 1248×1066
px, scale 0.67542). Figma 원본 = 3 horizontal stacked rows (각 row = brown
label box at left + white content card at right with green border) + bottom
BIM 모델/공사 Process 다이어그램 (deco).
본 partial = Track A frame 3 (Codex round 39 accepted, V4 strongest UAI tier).
`index.html` base + Phase Z 규약 adapt + **compact MDX-mapped focus**.
3-layer architecture (matrix §0) :
- V4 = matching authority — V4 ranked this frame as use_as_is for "01-1 용어 정의"
(MDX 01) and restructure=1 다른 section. UAI tier post-F12.
- figma_to_html = source/evidence — 401-line index.html + 4 PNG assets (title-icon,
card_decor × 3). full A+T+I+F+S resource.
- Phase Z = runtime orchestration — 본 commit 추가 runtime 자원 (catalog + partial + smoke).
PROMOTED — CSS (Figma 색/디자인 의도 → CSS 보존) :
- 브라운 label box : rgba(50,31,9,0.8) bg + 우측 둥글기 (border-radius 0 20 20 0)
+ drop-shadow — index.html L82-95
- 라벨 typography : yellow (#FFE100) Korean + white English/subscript text — index.html L92-96
- 화이트 content card : white bg + green border (#A5BBB4) — index.html L99-103
- title gradient : F13/F14/F12 zone-title family (#000 ~ #883700)
NOT PROMOTED — Phase Z compact zone fit (자체 결정, P1 case-by-case, matrix §4.1 Fix 7) :
- title-icon (50×50 PNG) — small icon, zone-size 에서 visible 효과 ↓
- card_decor × 3 (260×120 decorative image at right side of each card) — Figma
의 visual 풍부함이지만 MDX 내용과 무관 deco
- 하단 BIM 모델/공사 Process 연계 다이어그램 — Figma 의 *대구도 시각* 으로
compact zone (320px 정도) 에서 표현 불가. content 의 *bottom synthesis text* 가
이 역할 대체 가능 — 단 현재 cardinality strict 3 (body) 라 별 slot 추가 X
→ figma_to_html source evidence 는 *보존* (assets/ 폴더 그대로). 향후 *fidelity
review* 시 promote 결정 가능 (preservation guardrail, Codex round 37 §3 정합).
ADAPTED :
- Figma 50/45/38/30px → token-fixed (zone-title 13 / sub-title 12 / caption / body 11)
- 1248×1066 absolute positioning + zoom 0.67542 → Phase Z flex column (3 row stack)
- 335×124 label box → compact 90~110×40~50 box (zone fit)
- 870×124 card → flex-1 (남은 width 차지)
─────────────────────────────────────────────────────────────────────────────
min_height_px derivation (round 13 §2.2) :
title 30 + 3 row × 70 (label + body 2-3 bullets) + gap 6×2 + padding 30 = 282
+ safety buffer 38 = **320**. F29/F12 와 동등 class.
─────────────────────────────────────────────────────────────────────────────
slots :
- title : section.title
- category_1_label / 2_label / 3_label : 3 category headings (quadrant_flat_slots)
- category_1_body / 2_body / 3_body : 3 category bullet lists (text_lines)
─────────────────────────────────────────────────────────────────────────────
4-class failure taxonomy (matrix §4.1 Fix 7) :
- class 1 adapter readiness : `quadrant_flat_slots` builder 재사용 (pad_to=3 +
key_pattern). 기존 F16 builder 미터치, secondary reuse 허용 (Codex round 39).
- class 2 content-fit : 3 row stack + body bullets. row 별 ~70px 한계 →
body 가 2-3 bullets 짧게. 4+ bullets 시 overflow watch.
#}
<style>
.f11b {
width: 100%; height: 100%;
display: flex; flex-direction: column;
gap: 6px;
font-family: 'Noto Sans KR', 'Pretendard', sans-serif;
word-break: keep-all;
}
.f11b__title {
font-size: var(--font-zone-title);
font-weight: 700;
line-height: var(--lh-zone-title);
background-image: linear-gradient(180deg, #000 0%, #883700 100%);
-webkit-background-clip: text; background-clip: text;
color: transparent;
flex-shrink: 0;
filter: drop-shadow(0 0 3px rgba(50,44,30,0.4));
}
/* 3 row stack — Figma 의 brown-label + white-card horizontal pair */
.f11b__rows {
display: flex; flex-direction: column;
gap: 6px;
flex: 1 1 auto;
min-height: 0;
}
.f11b__row {
display: flex; align-items: stretch;
gap: 0;
flex: 1 1 0;
min-height: 0;
}
/* label box — Figma L82-95 (rgba(50,31,9,0.8) brown + 둥글기 + drop-shadow) */
.f11b__label {
flex: 0 0 110px;
background: rgba(50, 31, 9, 0.8);
border-radius: 0 12px 12px 0;
filter: drop-shadow(0 2px 1px rgba(0,0,0,0.25));
color: #fff;
font-weight: 700;
font-size: var(--font-sub-title);
line-height: 1.15;
letter-spacing: -0.04em;
display: flex; align-items: center; justify-content: center;
text-align: center;
padding: 4px 6px;
word-break: keep-all;
}
/* white card — Figma L99-103 (white bg + green border #A5BBB4) */
.f11b__card {
flex: 1 1 auto;
background: #fff;
border: 2px solid #A5BBB4;
border-radius: 12px;
padding: 6px 10px;
color: #1a1a1a;
min-height: 0;
overflow: hidden;
display: flex; flex-direction: column;
gap: 2px;
}
.f11b__card .text-line {
color: inherit;
font-size: var(--font-body);
line-height: var(--lh-body);
position: relative;
padding-left: 12px;
}
.f11b__card .text-line--bullet::before {
content: "•";
position: absolute;
left: 2px; top: 0;
color: #5a4b2e; /* PROMOTED — brown family */
font-weight: 700;
}
</style>
<div class="f11b" data-frame-id="1171281182" data-template-id="construction_bim_three_usage">
<div class="f11b__title">{{ slot_payload.title }}</div>
<div class="f11b__rows">
{# 3 rows — quadrant_flat_slots produces category_N_label / category_N_body for N=1..3 #}
<div class="f11b__row">
<div class="f11b__label">{{ slot_payload.category_1_label | safe }}</div>
<div class="f11b__card">
{% if slot_payload.category_1_body %}
{% for line in slot_payload.category_1_body %}<div class="text-line text-line--bullet{% if line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
{% endif %}
</div>
</div>
<div class="f11b__row">
<div class="f11b__label">{{ slot_payload.category_2_label | safe }}</div>
<div class="f11b__card">
{% if slot_payload.category_2_body %}
{% for line in slot_payload.category_2_body %}<div class="text-line text-line--bullet{% if line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
{% endif %}
</div>
</div>
<div class="f11b__row">
<div class="f11b__label">{{ slot_payload.category_3_label | safe }}</div>
<div class="f11b__card">
{% if slot_payload.category_3_body %}
{% for line in slot_payload.category_3_body %}<div class="text-line text-line--bullet{% if line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
{% endif %}
</div>
</div>
</div>
</div>
@@ -0,0 +1,173 @@
<!-- Phase Z-2 MVP-1.5b frame-derived adapted block.
§17 룰 — Figma 시각 언어 promote, geometry 만 zone-compatible adapt. -->
{#
─────────────────────────────────────────────────────────────────────────────
Visual Provenance — figma_to_html_agent/blocks/1171281189/ (frame 12)
─────────────────────────────────────────────────────────────────────────────
Frame 12 = "건설산업의 목표 (BIM 의 목적)" (cycle-3way-intersection, 2195×1195
px, scale 0.58312). Figma 원본 = 3 메인 원 (안전·품질 / 생산성·향상 / 소통·
신뢰) + 6 액센트 한자 원 (安/質/速/利/通/信) + 6 사이드 라벨 (안전성 제고 등)
+ 3 장식 rect + 3 arc + bg texture.
본 partial = Track A frame 2 (Codex round 30/33/35 합의, V4 strongest UAI tier).
`index.html` (510 lines) base + Phase Z 규약 adapt + **compact MDX-mapped focus**
(MDX 콘텐츠와 직접 매핑되는 5 slots 만 유지. Figma 의 frame-side decoration
는 NOT PROMOTED — zone 사이즈에서 cluttered 회피).
PROMOTED — CSS (Figma 색/디자인 의도 → CSS 으로 충분) :
- circle 1 (safety) gradient : index.html L142-145 (outer) + L149-154 (inner)
outer = linear-gradient(145.28deg, #FDC69E, #E0782C) multiply blend
inner = linear-gradient(145.28deg, #BC652B, #A24200) + white border + shadow
- circle 2 (productivity) : index.html L158-172
outer = linear-gradient(218.84deg, #D5AA89, #737373) multiply
inner = linear-gradient(153.95deg, #897445, #3E3523) + white border
- circle 3 (trust) : index.html L175-189
outer = linear-gradient(145.90deg, #FFFFFF, #253E1F) multiply
inner = linear-gradient(153.95deg, #296B55, #123328) + white border
- title gradient : #000 → #883700 (index.html L42-46) — F13/F14 zone-title 와 동일 family
- main label typography : 700 weight, white, text-shadow (index.html L192-204)
NOT PROMOTED — Phase Z compact zone fit (자체 결정, P1 case-by-case) :
- 6 액센트 한자 원 (安/質/速/利/通/信) — Figma 외 deco, MDX 무관, zone 사이즈에서 cluttered
- 6 사이드 라벨 (안전성 제고 / 품질 향상 / 신속·정확성 증진 / 비용저감 / 소통·이해 / 신뢰·투명성)
— Figma source 의 정해진 문구. MDX content 와 무관 deco text
- 3 장식 rect (gradient 곡선 wrap) + 3 arc 이미지 — Figma 의 시각 풍부함이지만 zone-size 에서 visible 효과 약함
- bg-texture multiply image (index.html L375) — zone 사이즈에서 effect 미미
ADAPTED :
- Figma 70/50/40px → token-fixed (zone-title 13 / sub-title 12 / caption / body 11)
- 3 메인 원 absolute positioning + zoom 0.58312 → Phase Z flex/grid (3-circle row)
- 350×350 원 사이즈 → compact 70-90 px circle (zone fit)
- Figma 의 3 원 *겹침 layout* (안전-품질 위 / 생산성 좌 / 소통 우) → flex row (분리, *cycle 의 의미*는 intersection text 가 표현)
─────────────────────────────────────────────────────────────────────────────
min_height_px derivation (Codex round 13 §2.2 — derive + confirm) :
Figma frame 1195 px @ scale 0.58312 → 697 px adapted (full frame, 6 deco 포함).
Phase Z compact (3 메인 원 + title + intersection) :
title 30 + 3 circle row 80 + label area 60 + intersection 30 + padding 40 = 240 px
+ safety buffer (가벼운 텍스트 overflow 보호) 80 = **320** (F14 350 보다 가벼움).
Confirm via smoke harness `--render-to` artifact.
─────────────────────────────────────────────────────────────────────────────
slots :
- title : section.title (Jinja 변수)
- circle_1_label / 2_label / 3_label : 3 메인 원 label (cycle_intersect_3 builder 산출)
- intersection : 중앙 교차 텍스트 (optional)
─────────────────────────────────────────────────────────────────────────────
4-class failure taxonomy (matrix §4.1 Fix 7) :
- class 1 adapter readiness 보호 : new builder cycle_intersect_3 가 PAYLOAD_BUILDERS
에 등록되어야 contract 가 valid. 안 등록 시 → builder None → 본 frame runtime fail
- class 2 content-fit : MDX 의 3 bullets 가 너무 길면 (label > ~20 chars) overflow.
label 짧게 (compact) 권장
─────────────────────────────────────────────────────────────────────────────
#}
<style>
.f12b {
width: 100%; height: 100%;
display: flex; flex-direction: column;
gap: 6px;
font-family: 'Noto Sans KR', 'Pretendard', sans-serif;
word-break: keep-all;
}
.f12b__title {
font-size: var(--font-zone-title);
font-weight: 700;
line-height: var(--lh-zone-title);
background-image: linear-gradient(180deg, #000 0%, #883700 100%);
-webkit-background-clip: text; background-clip: text;
color: transparent;
flex-shrink: 0;
filter: drop-shadow(0 0 3px rgba(50,44,30,0.4));
}
/* 3 circle row — compact flex layout (Figma 의 겹침 cycle layout 단순화) */
.f12b__circles {
display: flex; align-items: center; justify-content: center;
gap: 16px;
flex: 1 1 auto;
min-height: 0;
padding: 8px 0;
}
.f12b__circle {
flex: 0 0 auto;
width: 110px; height: 110px;
border-radius: 50%;
position: relative;
display: flex; align-items: center; justify-content: center;
box-shadow: 0 0 10px rgba(0,0,0,0.3);
}
/* outer ring (Figma multiply blend 의도) — CSS gradient approximation */
.f12b__circle::before {
content: "";
position: absolute; inset: -8px;
border-radius: 50%;
z-index: -1;
mix-blend-mode: multiply;
}
/* per-circle gradient (PROMOTED from index.html L141-189) */
.f12b__circle--safety {
background: linear-gradient(145.28deg, #BC652B 16.04%, #A24200 55.20%);
border: 3px solid #fff;
}
.f12b__circle--safety::before {
background: linear-gradient(145.28deg, #FDC69E 16.04%, #E0782C 55.20%);
}
.f12b__circle--productivity {
background: linear-gradient(153.95deg, #897445 15.27%, #3E3523 61.74%);
border: 3px solid #fff;
}
.f12b__circle--productivity::before {
background: linear-gradient(218.84deg, #D5AA89 14.08%, #737373 92.67%);
}
.f12b__circle--trust {
background: linear-gradient(153.95deg, #296B55 15.27%, #123328 61.74%);
border: 3px solid #fff;
}
.f12b__circle--trust::before {
background: linear-gradient(145.90deg, #FFFFFF 8.47%, #253E1F 87.56%);
}
/* circle 안 label (white text + shadow, index.html L192-204) */
.f12b__circle-label {
color: #fff;
font-size: var(--font-caption);
font-weight: 700;
line-height: 1.1;
text-align: center;
padding: 4px;
text-shadow: 0 0 4px rgba(0,0,0,0.6);
word-break: keep-all;
}
/* intersection — 중앙 교차 텍스트 (optional, MDX 의 emphasis content) */
.f12b__intersection {
flex-shrink: 0;
text-align: center;
padding: 6px 12px;
font-size: var(--font-sub-title);
font-weight: 700;
color: #883700; /* PROMOTED — title gradient 의 end color */
background: linear-gradient(180deg, rgba(255,250,240,0.6) 0%, rgba(255,250,240,0) 100%);
border-top: 1px dashed #883700;
line-height: 1.3;
min-height: 0;
}
.f12b__intersection:empty {
display: none; /* slot 빈 경우 자동 hide */
}
</style>
<div class="f12b" data-frame-id="1171281189" data-template-id="construction_goals_three_circle_intersection">
<div class="f12b__title">{{ slot_payload.title }}</div>
<div class="f12b__circles">
<div class="f12b__circle f12b__circle--safety">
<div class="f12b__circle-label">{{ slot_payload.circle_1_label | safe }}</div>
</div>
<div class="f12b__circle f12b__circle--productivity">
<div class="f12b__circle-label">{{ slot_payload.circle_2_label | safe }}</div>
</div>
<div class="f12b__circle f12b__circle--trust">
<div class="f12b__circle-label">{{ slot_payload.circle_3_label | safe }}</div>
</div>
</div>
<div class="f12b__intersection">{{ slot_payload.intersection | safe }}</div>
</div>
@@ -0,0 +1,158 @@
<!-- Phase Z-2 MVP-1.5b frame-derived adapted block.
§17 룰 — Figma 시각 언어 promote, geometry 만 zone-compatible adapt. -->
{#
─────────────────────────────────────────────────────────────────────────────
Visual Provenance — figma_to_html_agent/blocks/1171281198/ (frame 20)
─────────────────────────────────────────────────────────────────────────────
Frame 20 = "디지털 전환(DX)은 S/W가 필수다" (cards-3-header). Figma 원본 =
상단 dark green banner ("디지털 전환(DX)은 S/W가 필수다" green/white mix) +
좌측 DX circular area + 우측 3 perspective cards (BIM 전면설계 / 디지털
전환 S/W / 고부가가치 산업전환).
본 partial = Track A frame 5 (Codex round 45 accepted, V4 LE tier strongest).
builder 재사용 = `quadrant_flat_slots` (F11 cards-3-category pattern). 새
builder/parser 0.
3-layer architecture (matrix §0) :
- V4 = matching authority — V4 ranked this frame light_edit for 03-1 + 01-1.
- figma_to_html (1171281198) = source/evidence — 386-line index.html + assets/.
- Phase Z = runtime — 본 commit adds catalog + partial + smoke fixture.
PROMOTED — CSS :
- 3 column header bg : dark green (`#296B55` family, Figma green theme)
- header text white bold + green accent
- title gradient (#000 → #883700, F13/F14/F12/F11/F18 zone-title family)
- card border + bullet markers (green family)
NOT PROMOTED (P1 case-by-case, compact zone fit) :
- 상단 dark green banner (Figma 의 큰 visual 영역, MDX 의 *title 만* 핵심)
- 좌측 DX circular area (multi-image + center text + decor — Figma 의 main
rhetorical anchor 단 compact zone 에서 표현 불가). title 의 gradient 로 의도 보존
- 한자/장식/배경 텍스처 — Figma source evidence 보존, 미 promote
ADAPTED :
- Figma 90/65/40px → token-fixed (zone-title 13 / sub-title 12 / caption / body 11)
- 1280×426 absolute positioning + zoom → Phase Z 3-column grid
- 3 perspective cards (header + body) → flex column
─────────────────────────────────────────────────────────────────────────────
min_height_px derivation :
3 col × (header 30 + body 3-bullets × 25) + title 30 + padding 30 = ~195
+ safety buffer (longer label / 5+ bullets) 125 = **320**. F12/F11/F18 class.
─────────────────────────────────────────────────────────────────────────────
slots :
- title : section.title
- perspective_1/2/3_label : 3 column headers
- perspective_1/2/3_body : list[{text, indent}] : 3 column bullets
─────────────────────────────────────────────────────────────────────────────
4-class failure taxonomy :
- class 1 readiness : builder reuse `quadrant_flat_slots` + `quadrant_item`.
contract + partial + smoke fixture.
- class 2 content-fit : header label 짧게 (single line). body 3-5 bullets per
column (zone fit). 6+ bullets 시 watch.
#}
<style>
.f20b {
width: 100%; height: 100%;
display: flex; flex-direction: column;
gap: 6px;
font-family: 'Noto Sans KR', 'Pretendard', sans-serif;
word-break: keep-all;
}
.f20b__title {
font-size: var(--font-zone-title);
font-weight: 700;
line-height: var(--lh-zone-title);
background-image: linear-gradient(180deg, #000 0%, #883700 100%);
-webkit-background-clip: text; background-clip: text;
color: transparent;
flex-shrink: 0;
filter: drop-shadow(0 0 3px rgba(50,44,30,0.4));
}
/* 3-column grid — Figma cards-3-header pattern */
.f20b__cols {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 10px;
flex: 1 1 auto;
min-height: 0;
}
.f20b__col {
display: flex; flex-direction: column;
border: 2px solid #296B55; /* PROMOTED — green family from Figma */
border-radius: 8px;
overflow: hidden;
background: #fff;
min-height: 0;
}
/* header bar (top of each card, dark green per Figma) */
.f20b__header {
background: linear-gradient(180deg, #296B55 0%, #123328 100%); /* PROMOTED — Figma green theme */
color: #fff;
font-weight: 700;
font-size: var(--font-sub-title);
line-height: 1.15;
padding: 6px 10px;
text-align: center;
flex-shrink: 0;
letter-spacing: -0.04em;
}
/* body — bullet list (CSS check marker, F14 pattern) */
.f20b__body {
flex: 1 1 auto;
overflow: hidden;
padding: 6px 10px 8px;
color: #1a1a1a;
min-height: 0;
display: flex; flex-direction: column;
gap: 3px;
}
.f20b__body .text-line {
color: inherit;
font-size: var(--font-body);
line-height: var(--lh-body);
position: relative;
padding-left: 14px;
}
.f20b__body .text-line--bullet::before {
content: "\2713"; /* ✓ check mark — green theme */
position: absolute;
left: 0; top: 0;
color: #296B55; /* PROMOTED — green family */
font-weight: 700;
}
</style>
<div class="f20b" data-frame-id="1171281198" data-template-id="dx_sw_necessity_three_perspectives">
<div class="f20b__title">{{ slot_payload.title }}</div>
<div class="f20b__cols">
{# 3 columns — quadrant_flat_slots produces perspective_N_label / perspective_N_body for N=1..3 #}
<div class="f20b__col">
<div class="f20b__header">{{ slot_payload.perspective_1_label | safe }}</div>
<div class="f20b__body">
{% if slot_payload.perspective_1_body %}
{% for line in slot_payload.perspective_1_body %}<div class="text-line text-line--bullet{% if line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
{% endif %}
</div>
</div>
<div class="f20b__col">
<div class="f20b__header">{{ slot_payload.perspective_2_label | safe }}</div>
<div class="f20b__body">
{% if slot_payload.perspective_2_body %}
{% for line in slot_payload.perspective_2_body %}<div class="text-line text-line--bullet{% if line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
{% endif %}
</div>
</div>
<div class="f20b__col">
<div class="f20b__header">{{ slot_payload.perspective_3_label | safe }}</div>
<div class="f20b__body">
{% if slot_payload.perspective_3_body %}
{% for line in slot_payload.perspective_3_body %}<div class="text-line text-line--bullet{% if line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
{% endif %}
</div>
</div>
</div>
</div>
@@ -0,0 +1,152 @@
<!-- Phase Z-2 MVP-1.5b frame-derived adapted block.
§17 룰 — Figma 시각 언어 promote, geometry 만 zone-compatible adapt. -->
{#
─────────────────────────────────────────────────────────────────────────────
Visual Provenance — figma_to_html_agent/blocks/1171281179/ (frame 8)
─────────────────────────────────────────────────────────────────────────────
Frame 8 = "효율적인 정보의 관리와 활용 (What/How/When)" (cards/3-section-framework).
3 sections framework — What (무슨 정보) / How (어떻게 연계) / When (언제 사용).
정보 관리·활용 3관점 병렬 제시 디자인.
본 partial = Track A frame 6 (Codex round 47 V4-zero catalog-completeness
activation guardrail). **V4 신호 0** (4 MDX sample 기준). 32-frame all-in
scope 채우는 등록 — V4 가 다른 MDX 에서 본 frame 을 추천 시 backend 받음.
*V4 endorsement 아님* — V4 = matching authority 보존, Phase Z 는 runtime
availability 추가만.
3-layer architecture (matrix §0) :
- V4 = matching authority — 본 frame 은 4 MDX 에서 미선택 (UAI/LE/RS = 0).
단 V4 evidence 미 cover 의 MDX 가 추천 시 backend 가 받을 수 있게 등록.
- figma_to_html (1171281179) = source/evidence — analysis + texts + index +
flat + assets 모두 보유. 본 partial 의 visual base.
- Phase Z = runtime — catalog + partial + smoke fixture 추가.
PROMOTED — CSS :
- 3 section header color (W/H/W = What/How/When framework)
- title gradient (zone-title family — F13/F14/F12/F11/F18/F20/F8 등 동일)
- card border + bullet markers
NOT PROMOTED (P1 case-by-case + preservation guardrail) :
- Figma 의 상단 banner / icon / 배경 텍스처 / 장식 image 등 — figma_to_html
source evidence 보존, compact zone fit 위해 미 promote
ADAPTED :
- Figma 50px+ → token-fixed
- absolute positioning → Phase Z 3-column grid
─────────────────────────────────────────────────────────────────────────────
min_height_px = 320 (F11/F20 와 동등 — 3-column header + body)
slots : title, section_1/2/3_label, section_1/2/3_body
4-class status :
- class 1 ready (builder reuse + partial + smoke)
- class 2 watch (body 5+ bullets per column)
#}
<style>
.f8b {
width: 100%; height: 100%;
display: flex; flex-direction: column;
gap: 6px;
font-family: 'Noto Sans KR', 'Pretendard', sans-serif;
word-break: keep-all;
}
.f8b__title {
font-size: var(--font-zone-title);
font-weight: 700;
line-height: var(--lh-zone-title);
background-image: linear-gradient(180deg, #000 0%, #883700 100%);
-webkit-background-clip: text; background-clip: text;
color: transparent;
flex-shrink: 0;
filter: drop-shadow(0 0 3px rgba(50,44,30,0.4));
}
.f8b__cols {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 10px;
flex: 1 1 auto;
min-height: 0;
}
.f8b__col {
display: flex; flex-direction: column;
border: 2px solid;
border-radius: 8px;
overflow: hidden;
background: #fff;
min-height: 0;
}
/* per-section accent — W/H/W theme (다른 frame 들과 차별) */
.f8b__col:nth-child(1) { border-color: #2563eb; } /* What — blue */
.f8b__col:nth-child(2) { border-color: #ea580c; } /* How — orange */
.f8b__col:nth-child(3) { border-color: #16a34a; } /* When — green */
.f8b__header {
color: #fff;
font-weight: 700;
font-size: var(--font-sub-title);
line-height: 1.15;
padding: 6px 10px;
text-align: center;
flex-shrink: 0;
letter-spacing: -0.02em;
}
.f8b__col:nth-child(1) .f8b__header { background: linear-gradient(180deg, #2563eb 0%, #1e40af 100%); }
.f8b__col:nth-child(2) .f8b__header { background: linear-gradient(180deg, #ea580c 0%, #c2410c 100%); }
.f8b__col:nth-child(3) .f8b__header { background: linear-gradient(180deg, #16a34a 0%, #15803d 100%); }
.f8b__body {
flex: 1 1 auto;
overflow: hidden;
padding: 6px 10px 8px;
color: #1a1a1a;
min-height: 0;
display: flex; flex-direction: column;
gap: 3px;
}
.f8b__body .text-line {
color: inherit;
font-size: var(--font-body);
line-height: var(--lh-body);
position: relative;
padding-left: 14px;
}
.f8b__body .text-line--bullet::before {
content: "•";
position: absolute;
left: 2px; top: 0;
font-weight: 700;
}
.f8b__col:nth-child(1) .f8b__body .text-line--bullet::before { color: #2563eb; }
.f8b__col:nth-child(2) .f8b__body .text-line--bullet::before { color: #ea580c; }
.f8b__col:nth-child(3) .f8b__body .text-line--bullet::before { color: #16a34a; }
</style>
<div class="f8b" data-frame-id="1171281179" data-template-id="info_management_what_how_when">
<div class="f8b__title">{{ slot_payload.title }}</div>
<div class="f8b__cols">
<div class="f8b__col">
<div class="f8b__header">{{ slot_payload.section_1_label | safe }}</div>
<div class="f8b__body">
{% if slot_payload.section_1_body %}
{% for line in slot_payload.section_1_body %}<div class="text-line text-line--bullet{% if line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
{% endif %}
</div>
</div>
<div class="f8b__col">
<div class="f8b__header">{{ slot_payload.section_2_label | safe }}</div>
<div class="f8b__body">
{% if slot_payload.section_2_body %}
{% for line in slot_payload.section_2_body %}<div class="text-line text-line--bullet{% if line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
{% endif %}
</div>
</div>
<div class="f8b__col">
<div class="f8b__header">{{ slot_payload.section_3_label | safe }}</div>
<div class="f8b__body">
{% if slot_payload.section_3_body %}
{% for line in slot_payload.section_3_body %}<div class="text-line text-line--bullet{% if line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
{% endif %}
</div>
</div>
</div>
</div>
@@ -0,0 +1,294 @@
<!-- Phase Z-2 MVP-1.5b frame-derived adapted block.
§17 룰 — Figma 시각 언어 promote, geometry 만 zone-compatible adapt. -->
{#
─────────────────────────────────────────────────────────────────────────────
Visual Provenance — figma_to_html_agent/blocks/1171281210/index.html
─────────────────────────────────────────────────────────────────────────────
PROMOTED (Figma 1:1 변환물 → 그대로 사용):
- banner-left text gradient : lines 101-104 → linear-gradient(90deg, rgb(62,53,35), rgb(62,53,35)) on linear-gradient(180deg, rgb(41,107,85), rgb(18,51,40))
- banner-right text gradient : lines 110-113 → linear-gradient(90deg, rgb(34,95,74), rgb(34,95,74)) on linear-gradient(180deg, rgb(41,107,85), rgb(18,51,40))
- banner-left bg gradient : lines 84-91 → linear-gradient(to right, rgba(136,131,118,.343), #39321E 73.1%, #39321E) + border-radius pill
- banner-right bg gradient : lines 92-98 → linear-gradient(to right, #032118, rgba(41,107,85,.1)) + border-radius pill
- body bg gradients (좌/우) : lines 53-68 → multi-bg / 좌(57,50,30) α0.3 → white α0.3, 우(41,107,85) α0.3 → white α0.3
- section-title-left color : line 125 → #5C3714 (갈)
- section-title-right color : line 126 → #084C56 (짙은 청록)
- bullets text : line 136 → 700 / #000 / list-disc
- arrow PNG asset : line 220 → assets/arrow.png (AS-IS → TO-BE)
- footnote () inline : lines 144, 281-283 → 25.8px small + indent
(2026-05-08 수정) 원본 SVG 3 개 (header_left_bg / header_right_bg / body_bg) → CSS gradient 로 재현.
asset 파일 부재 (figma 원본 R8 룰 = "순수 CSS 우선" 따라 SVG 자체 미생성). 시각 fidelity 동일.
ADAPTED (Figma px / native 무시, zone flex 재구성):
- 3848×1388 inner + zoom 0.33270 → flex layout (no zoom, no fixed inner)
- 70px banner / 50px section / 40px body→ var(--font-sub-title) / 8-10px / var(--font-caption)
- absolute left/top px → flex grid (no absolute)
- banner heights 142px + body 933px → 비율 기반 flex (banner ~28%, body ~72%)
- assets 1921×142 / 3845×933 sizes → object-fit cover, 비율 유지
─────────────────────────────────────────────────────────────────────────────
slots: title (omitted), banner_left, banner_right,
process.sections[3] (s[0]: AS-IS/TO-BE, s[1-2]: bullets),
product.sections[3] (bullets, optional footnote/sub_title)
#}
<style>
.f29b {
width: 100%; height: 100%;
display: flex; flex-direction: column;
gap: 4px;
position: relative;
font-family: 'Noto Sans KR', 'Pretendard', sans-serif;
word-break: keep-all;
overflow: hidden;
}
.f29b__title {
font-size: var(--font-zone-title); /* 13px — frame 13 의 f13b__title 과 동일 위계 */
font-weight: 700;
line-height: var(--lh-zone-title);
background-image: linear-gradient(180deg, #000 0%, #883700 100%);
-webkit-background-clip: text; background-clip: text;
color: transparent;
flex-shrink: 0;
padding-left: 4px;
}
/* banners — top */
.f29b__banners {
flex: 0 0 auto;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4px;
position: relative;
height: 36px;
}
.f29b__banner {
position: relative;
overflow: hidden;
display: flex; align-items: center; justify-content: center;
}
/* PROMOTED background gradients — figma origin lines 84-98 (header-bg--left/right).
원본 SVG 3 개 → CSS gradient 로 재현 (R8/R9 준수). border-radius 9999px = 자동 pill. */
.f29b__banner--left {
background: linear-gradient(to right,
rgba(136, 131, 118, 0.343) 0%,
#39321E 73.1%,
#39321E 100%);
border-radius: 0 9999px 9999px 0;
}
.f29b__banner--right {
background: linear-gradient(to right,
#032118 0%,
rgba(41, 107, 85, 0.1) 100%);
border-radius: 9999px 0 0 9999px;
}
.f29b__banner-text {
position: relative;
z-index: 1;
font-size: var(--font-sub-title);
font-weight: 900;
line-height: 1;
text-shadow: 0 0 2px #322C1E;
-webkit-background-clip: text; background-clip: text;
color: transparent;
padding: 0 8px;
}
/* PROMOTED gradient — lines 101-104 / 110-113 */
.f29b__banner--left .f29b__banner-text {
background-image:
linear-gradient(90deg, rgb(62,53,35) 0%, rgb(62,53,35) 100%),
linear-gradient(180deg, rgb(41,107,85) 0%, rgb(18,51,40) 100%);
}
.f29b__banner--right .f29b__banner-text {
background-image:
linear-gradient(90deg, rgb(34,95,74) 0%, rgb(34,95,74) 100%),
linear-gradient(180deg, rgb(41,107,85) 0%, rgb(18,51,40) 100%);
}
/* body — 6 cell grid (3 rows × 2 cols) — 좌우 row divider Y 정렬 */
/* PROMOTED background — figma origin lines 53-68 (body-bg-left/right).
좌(0~50%): #39321E α0.3 (top) → white α0.3 (bottom)
우(50~100%): #296B55 α0.3 (top) → white α0.3 (bottom)
원본 SVG → CSS multi-bg 로 재현. */
.f29b__body {
flex: 1;
position: relative;
min-height: 0;
padding-top: 4px;
background:
linear-gradient(to bottom, rgba(57,50,30,0.3) 0%, rgba(255,255,255,0.3) 100%) 0% 0% / 50% 100% no-repeat,
linear-gradient(to bottom, rgba(41,107,85,0.3) 0%, rgba(255,255,255,0.3) 100%) 100% 0% / 50% 100% no-repeat;
}
/* 6 cell grid: 3 rows (section 1/2/3), 2 cols (process/product). 각 row 의 height = max(좌, 우) */
.f29b__grid {
position: relative;
z-index: 1;
display: grid;
grid-template-columns: 1fr 1fr;
grid-auto-rows: auto;
gap: 6px 10px;
padding: 4px 8px;
height: 100%;
align-content: start;
}
.f29b__cell {
overflow: hidden;
min-width: 0;
min-height: 0;
}
/* row 2/3 (3 번째 cell 이후) 위에 dashed divider — 좌우 같은 Y */
.f29b__cell:nth-child(n+3) {
border-top: 1px dashed rgba(0,0,0,0.15);
padding-top: 6px;
margin-top: 4px;
}
/* section-title — 부소목차 (사용자 5-layer 직설 2026-05-07): Analogue 기반... 같은 깊이 0 굵은 bullet */
.f29b__section-title {
font-size: var(--font-sub-title); /* 12px — 부소목차 위계 */
font-weight: 900;
line-height: 1.3;
margin-bottom: 5px;
padding-left: 6px;
border-left: 3px solid currentColor;
}
.f29b__cell--left .f29b__section-title { color: #5C3714; } /* PROMOTED line 125 */
.f29b__cell--right .f29b__section-title { color: #084C56; } /* PROMOTED line 126 */
.f29b__section-title__sub {
font-size: var(--font-caption); /* 10px */
margin-left: 3px;
}
/* f29b 의 본문 = .text-line 공통 contract 사용. bullet color override. */
.f29b__cell .text-line--bullet { color: #000; font-weight: 700; }
/* AS-IS/TO-BE 좌우 + arrow (PROMOTED 구조) */
.f29b__transform {
display: grid;
grid-template-columns: 1fr auto 1fr;
gap: 4px;
align-items: start;
}
.f29b__transform__list {
/* .text-line 공통 contract 그대로. wrapper 만 grid 안의 cell 역할 */
}
.f29b__transform__arrow {
align-self: center;
width: 28px;
flex-shrink: 0;
}
.f29b__transform__arrow img {
width: 100%;
height: auto;
display: block;
}
.f29b__footnote {
font-size: var(--font-caption); /* 10px */
line-height: var(--lh-caption);
color: #333;
padding-left: 14px;
margin-top: 1px;
white-space: pre-wrap;
}
.f29b__aster-small { font-size: 9px; }
</style>
<div class="f29b" data-frame-id="1171281210" data-template-id="process_product_two_way">
{# H2 mid-heading — section.title 렌더 (frame 13 의 f13b__title 과 동일 위계) #}
<div class="f29b__title">{{ slot_payload.title }}</div>
{# Banners (PROMOTED 구조 + gradient lines 101-113).
배경 = .f29b__banner--left/right 의 CSS gradient (figma SVG → R8 CSS gradient 재현). #}
<div class="f29b__banners">
<div class="f29b__banner f29b__banner--left">
<div class="f29b__banner-text">{{ slot_payload.banner_left }}</div>
</div>
<div class="f29b__banner f29b__banner--right">
<div class="f29b__banner-text">{{ slot_payload.banner_right }}</div>
</div>
</div>
{# Body — 6 cell grid (row-major: row 1 좌/우, row 2 좌/우, row 3 좌/우).
row 별 height = max(좌, 우) → divider Y 정렬.
배경 = .f29b__body 의 CSS multi-gradient (figma SVG → R8 CSS gradient 재현). #}
<div class="f29b__body">
<div class="f29b__grid">
{# ───── Row 1: section 1 ───── #}
{% set s1 = slot_payload.process.sections[0] %}
<div class="f29b__cell f29b__cell--left" data-frame-slot-id="process_column">
{% if s1.title %}
<div class="f29b__section-title">{{ s1.title | safe }}</div>
{% if s1.transforms %}
<div class="transform-block">
<div class="transform-block__header">
<span class="transform-block__label--from">AS-IS</span>
<span></span>
<span class="transform-block__label--to">TO-BE</span>
</div>
<div class="transform-rows">
{% for t in s1.transforms %}
<div class="transform-row">
<div class="transform-row__from">{{ t.from | safe }}</div>
<div class="transform-row__arrow"></div>
<div class="transform-row__to">{{ t.to | safe }}</div>
</div>
{% endfor %}
</div>
</div>
{% elif s1.text_lines %}
{% for line in s1.text_lines %}<div class="text-line text-line--bullet{% if line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
{% endif %}
{% endif %}
</div>
{% set p1 = slot_payload.product.sections[0] %}
<div class="f29b__cell f29b__cell--right" data-frame-slot-id="product_column">
{% if p1.title %}
<div class="f29b__section-title">{{ p1.title | safe }}</div>
{% for line in p1.text_lines %}<div class="text-line text-line--bullet{% if line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
{% endif %}
</div>
{# ───── Row 2: section 2 ───── #}
{% set s2 = slot_payload.process.sections[1] %}
<div class="f29b__cell f29b__cell--left" data-frame-slot-id="process_column">
{% if s2.title %}
<div class="f29b__section-title">{{ s2.title | safe }}</div>
{% for line in s2.text_lines %}<div class="text-line text-line--bullet{% if line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
{% endif %}
</div>
{% set p2 = slot_payload.product.sections[1] %}
<div class="f29b__cell f29b__cell--right" data-frame-slot-id="product_column">
{% if p2.title %}
<div class="f29b__section-title">{{ p2.title | safe }}</div>
{% for line in p2.text_lines %}<div class="text-line text-line--bullet{% if line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
{% if p2.footnote %}
<div class="f29b__footnote"><span class="f29b__aster-small"></span> {{ p2.footnote | safe }}</div>
{% endif %}
{% endif %}
</div>
{# ───── Row 3: section 3 ───── #}
{% set s3 = slot_payload.process.sections[2] %}
<div class="f29b__cell f29b__cell--left" data-frame-slot-id="process_column">
{% if s3.title %}
<div class="f29b__section-title">{{ s3.title | safe }}</div>
{% for line in s3.text_lines %}<div class="text-line text-line--bullet{% if line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
{% endif %}
</div>
{% set p3 = slot_payload.product.sections[2] %}
<div class="f29b__cell f29b__cell--right" data-frame-slot-id="product_column">
{% if p3.title %}
<div class="f29b__section-title">{{ p3.title | safe }}{% if p3.sub_title %}<span class="f29b__section-title__sub">{{ p3.sub_title | safe }}</span>{% endif %}</div>
{% for line in p3.text_lines %}<div class="text-line text-line--bullet{% if line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
{% endif %}
</div>
</div>
</div>
</div>
@@ -0,0 +1,135 @@
<!-- Phase Z-2 MVP-1.5b frame-derived adapted block.
§17 룰 — Figma 시각 언어 promote, geometry 만 zone-compatible adapt. -->
{#
─────────────────────────────────────────────────────────────────────────────
Visual Provenance — figma_to_html_agent/blocks/1171281209/ (frame 28)
─────────────────────────────────────────────────────────────────────────────
Frame 28 = "현존 상용 S/W의 현실" (cards/title-plus-3-emphasis). 3 emphasis
cards parallel — 3 problem/약점/한계 진단 (토목 전문성 부족 / 비효율성 /
실무 적용 불가).
본 partial = Track A frame 7 (V4-zero catalog-completeness activation per
Codex round 49 guardrail). V4 = matching authority 보존. Phase Z 는 runtime
availability 만 추가.
PROMOTED CSS :
- per-emphasis 경고 색 (red/orange/amber — problem theme)
- title gradient (zone-title family)
- bullet markers per-emphasis color
NOT PROMOTED : Figma source decoration / banner / texture — 보존 (figma_to_html).
ADAPTED : Figma absolute → flex 3-column grid + token-fixed typography.
slots : title, emphasis_1/2/3_label, emphasis_1/2/3_body.
#}
<style>
.f28b {
width: 100%; height: 100%;
display: flex; flex-direction: column;
gap: 6px;
font-family: 'Noto Sans KR', 'Pretendard', sans-serif;
word-break: keep-all;
}
.f28b__title {
font-size: var(--font-zone-title);
font-weight: 700;
line-height: var(--lh-zone-title);
background-image: linear-gradient(180deg, #000 0%, #883700 100%);
-webkit-background-clip: text; background-clip: text;
color: transparent;
flex-shrink: 0;
filter: drop-shadow(0 0 3px rgba(50,44,30,0.4));
}
.f28b__cols {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 10px;
flex: 1 1 auto;
min-height: 0;
}
.f28b__col {
display: flex; flex-direction: column;
border: 2px solid;
border-radius: 8px;
overflow: hidden;
background: #fff;
min-height: 0;
}
/* per-emphasis problem theme — orange/red family for 약점/한계 진단 */
.f28b__col:nth-child(1) { border-color: #dc2626; } /* red */
.f28b__col:nth-child(2) { border-color: #ea580c; } /* orange */
.f28b__col:nth-child(3) { border-color: #d97706; } /* amber */
.f28b__header {
color: #fff;
font-weight: 700;
font-size: var(--font-sub-title);
line-height: 1.15;
padding: 6px 10px;
text-align: center;
flex-shrink: 0;
letter-spacing: -0.02em;
}
.f28b__col:nth-child(1) .f28b__header { background: linear-gradient(180deg, #dc2626 0%, #991b1b 100%); }
.f28b__col:nth-child(2) .f28b__header { background: linear-gradient(180deg, #ea580c 0%, #9a3412 100%); }
.f28b__col:nth-child(3) .f28b__header { background: linear-gradient(180deg, #d97706 0%, #92400e 100%); }
.f28b__body {
flex: 1 1 auto;
overflow: hidden;
padding: 6px 10px 8px;
color: #1a1a1a;
min-height: 0;
display: flex; flex-direction: column;
gap: 3px;
}
.f28b__body .text-line {
color: inherit;
font-size: var(--font-body);
line-height: var(--lh-body);
position: relative;
padding-left: 14px;
}
.f28b__body .text-line--bullet::before {
content: "!";
position: absolute;
left: 4px; top: 0;
font-weight: 700;
}
.f28b__col:nth-child(1) .f28b__body .text-line--bullet::before { color: #dc2626; }
.f28b__col:nth-child(2) .f28b__body .text-line--bullet::before { color: #ea580c; }
.f28b__col:nth-child(3) .f28b__body .text-line--bullet::before { color: #d97706; }
</style>
<div class="f28b" data-frame-id="1171281209" data-template-id="sw_reality_three_emphasis">
<div class="f28b__title">{{ slot_payload.title }}</div>
<div class="f28b__cols">
<div class="f28b__col">
<div class="f28b__header">{{ slot_payload.emphasis_1_label | safe }}</div>
<div class="f28b__body">
{% if slot_payload.emphasis_1_body %}
{% for line in slot_payload.emphasis_1_body %}<div class="text-line text-line--bullet{% if line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
{% endif %}
</div>
</div>
<div class="f28b__col">
<div class="f28b__header">{{ slot_payload.emphasis_2_label | safe }}</div>
<div class="f28b__body">
{% if slot_payload.emphasis_2_body %}
{% for line in slot_payload.emphasis_2_body %}<div class="text-line text-line--bullet{% if line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
{% endif %}
</div>
</div>
<div class="f28b__col">
<div class="f28b__header">{{ slot_payload.emphasis_3_label | safe }}</div>
<div class="f28b__body">
{% if slot_payload.emphasis_3_body %}
{% for line in slot_payload.emphasis_3_body %}<div class="text-line text-line--bullet{% if line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
{% endif %}
</div>
</div>
</div>
</div>
@@ -0,0 +1,157 @@
<!-- Phase Z-2 MVP-1.5b frame-derived adapted block.
§17 룰 — Figma 시각 언어 promote, geometry 만 zone-compatible adapt. -->
{#
─────────────────────────────────────────────────────────────────────────────
Visual Provenance — figma_to_html_agent/blocks/1171281190/index.html
─────────────────────────────────────────────────────────────────────────────
PROMOTED (Figma 1:1 변환물 → 그대로 사용):
- tech bar gradient : line 71 → linear-gradient(180deg, #0D78D0 0%, #023056 100%)
- people bar gradient : line 72 → linear-gradient(180deg, #FF9A23 0%, #CC5200 100%)
- nature bar gradient : line 73 → linear-gradient(180deg, #39BE49 21%, #23742C 100%)
- tech heading text : line 123 → linear-gradient(180deg, #0D78D0, #134D7F)
- people heading text : line 124 → linear-gradient(180deg, #FE900C, #D18B37)
- nature heading text : line 125 → linear-gradient(180deg, #3CA649, #23742C)
- title gradient : line 40 → linear-gradient(180deg, #000, #883700)
- desc color : line 118 → #3E3523
- col 외곽 border : line 57 → 2px solid #000 (top + bottom)
- col-section divider : line 103 → 2px dashed #000
NOT PROMOTED (Figma 데코지만 MDX 에 없으므로 주입 X):
- 한자 (技/術 / 人/材 / 天/地) ← MDX 에 없는 content. Figma frame 의 visual 데코일 뿐.
ADAPTED:
- Figma px / native size 무시, flex layout
- Figma 70/50/45/35px fonts → token-fixed (zone-title 13 / sub-title 12 / body 11 / caption 10)
- desc <br>-joined → nested <ul><li> (구조 보존)
─────────────────────────────────────────────────────────────────────────────
slots: title, pillars[].{label, color_class, sections[].{heading, bullets[]}}
#}
<style>
.f13b {
width: 100%; height: 100%;
display: flex; flex-direction: column;
gap: 4px;
font-family: 'Noto Sans KR', 'Pretendard', sans-serif;
word-break: keep-all;
}
.f13b__title {
font-size: var(--font-zone-title);
font-weight: 700;
line-height: var(--lh-zone-title);
background-image: linear-gradient(180deg, #000 0%, #883700 100%);
-webkit-background-clip: text; background-clip: text;
color: transparent;
flex-shrink: 0;
}
.f13b__cols {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 8px;
flex: 1;
min-height: 0;
}
.f13b__col {
display: flex;
border-top: 2px solid #000; /* PROMOTED line 57 */
border-bottom: 2px solid #000;
overflow: hidden;
min-width: 0;
background: rgba(255,255,255,0.5);
}
.f13b__bar {
flex: 0 0 50px;
display: flex; flex-direction: column;
align-items: center; justify-content: center;
color: #fff;
font-weight: 700;
text-align: center;
padding: 6px 2px;
gap: 3px;
}
.f13b__col--tech .f13b__bar { background: linear-gradient(180deg, #0D78D0 0%, #023056 100%); }
.f13b__col--people .f13b__bar { background: linear-gradient(180deg, #FF9A23 0%, #CC5200 100%); }
.f13b__col--nature .f13b__bar { background: linear-gradient(180deg, #39BE49 21%, #23742C 100%); }
/* PROMOTED Figma frame 13 line 92-93 — main + parenthetical stacked vertically */
.f13b__bar-main {
font-size: var(--font-sub-title); /* 12px — main word "기술/사람/자연" */
font-weight: 700;
line-height: 1.2;
display: block;
}
.f13b__bar-paren {
font-size: var(--font-caption); /* 10px — "(디지털/역량/여건)" */
font-weight: 500;
line-height: 1.2;
display: block;
opacity: 0.95;
}
.f13b__body {
flex: 1;
display: flex; flex-direction: column;
padding: 8px 12px; /* breathing (was 6px 10px) */
min-width: 0;
overflow: hidden;
}
.f13b__section {
flex: 1;
padding: 6px 0; /* breathing (was 4px) */
overflow: hidden;
min-height: 0;
}
.f13b__section + .f13b__section {
border-top: 2px dashed #000; /* PROMOTED line 103 */
margin-top: 6px; /* visible section gap (was 4px) */
padding-top: 8px; /* breathing (was 6px) */
}
/* heading — 부소목차 (사용자 5-layer 직설 2026-05-07): Digital 기술과... 같은 깊이 1 굵은 bullet */
.f13b__heading {
font-size: var(--font-sub-title); /* 12px — 부소목차 위계 */
font-weight: 700;
line-height: var(--lh-sub-title);
-webkit-background-clip: text; background-clip: text;
color: transparent;
margin-bottom: 6px; /* visible heading-body gap (was 4px) */
}
.f13b__col--tech .f13b__heading { background-image: linear-gradient(180deg, #0D78D0 0%, #134D7F 100%); }
.f13b__col--people .f13b__heading { background-image: linear-gradient(180deg, #FE900C 0%, #D18B37 100%); }
.f13b__col--nature .f13b__heading { background-image: linear-gradient(180deg, #3CA649 21%, #23742C 100%); }
/* desc — frame-family text layout contract (.text-line 공통 클래스 사용)
feedback (mvp1.5b_test5): browser-native ul/li 폐기. */
.f13b__desc {
color: #3E3523; /* PROMOTED line 118 */
overflow: hidden;
}
/* desc 안 .text-line 색 override */
.f13b__desc .text-line { color: #3E3523; }
</style>
<div class="f13b" data-frame-id="1171281190" data-template-id="three_parallel_requirements">
<div class="f13b__title">{{ slot_payload.title }}</div>
<div class="f13b__cols">
{% for pillar in slot_payload.pillars %}
<div class="f13b__col f13b__col--{{ pillar.color_class }}">
<div class="f13b__bar">
<span class="f13b__bar-main">{{ pillar.label_main | safe }}</span>
{% if pillar.label_paren %}
<span class="f13b__bar-paren">{{ pillar.label_paren | safe }}</span>
{% endif %}
</div>
<div class="f13b__body">
{% for section in pillar.sections %}
<div class="f13b__section">
<div class="f13b__heading">{{ section.heading | safe }}</div>
{% if section.text_lines %}
<div class="f13b__desc">
{% for line in section.text_lines %}<div class="text-line text-line--bullet{% if line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
</div>
{% endif %}
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
</div>
@@ -0,0 +1,248 @@
<!-- Phase Z-2 MVP-1.5b frame-derived adapted block.
§17 룰 — Figma 시각 언어 promote, geometry 만 zone-compatible adapt. -->
{#
─────────────────────────────────────────────────────────────────────────────
Visual Provenance — figma_to_html_agent/blocks/1171281191/ (frame 14)
─────────────────────────────────────────────────────────────────────────────
Frame 14 = "주체별 기대효과" (cards-3col-persona, 2601×1927 px, scale 0.49213).
3 컬럼 카드 : 발주자 / 시공자 / 설계자 각 persona 별 *목표* + bullet 7 개 안팎.
본 partial 은 `figma_to_html_agent/blocks/1171281191/index.html` (184 lines) 을
base 로 Phase Z 규약 (Jinja slot + token CSS + slide-base wrap) adapt.
본 commit (Track A 1/16 second refinement, F2 re-do post-Codex round 22 catch) :
asset-promotion policy = per-asset case-by-case (round 13 §2.3 + round 24 합의).
strict CSS-only over-generalization (first refinement `2fcd8bb`) 정정.
PROMOTED — RASTER (Phase Z `copy_assets()` 가 figma_to_html_agent/blocks/1171281191/
assets/ → {run_dir}/assets/three_persona_benefits/ 복사) :
- col_bg_texture (4a17cd1d...png) — 3 컬럼 BG 텍스처. CSS noise/gradient 근사 보다 fidelity 보존.
- bottom photo client (d2c070f2...png) — 발주자 카드 하단 사진
- bottom photo constructor (2a6a58e7...png) — 시공자 카드 하단 사진
- bottom photo designer (39113493...png) — 설계자 카드 하단 사진
- badge outer client (77f31997...png) — 발주자 round badge outer ring
- badge inner client (e64c967d...png) — 발주자 round badge inner disk
- badge outer constructor (1550ec75...png)
- badge inner constructor (85beaf9d...png)
- badge outer designer (9ac089fa...png)
- badge inner designer (4b534ccf...png)
→ 10 raster (1 BG + 3 photos + 6 badge). Codex round 24 tightening : badge =
default promote (asset-capable path 검증).
PROMOTED — CSS (Figma 색/디자인 의도 → CSS 으로 충분) :
- 발주자 accent color : Figma TEXT #285b4a (dark green) — index.html L80
- 시공자 accent color : Figma TEXT #445a2f (olive) — index.html L81
- 설계자 accent color : Figma TEXT #743002 (red-brown) — index.html L82
- col-overlay tint : #d6e7c4 / #e1efe1 / #d0c0ad (opacity-80 — solid 색, CSS equivalent)
- title gradient : #000 → #883700 (F13 zone-title 와 동일 family)
- bullet check marker : ✓ unicode (실제 png 6896d5c2 와 동등)
NOT PROMOTED (MDX 무관 + Figma 데코) :
- 한자/장식 텍스트 (Figma 안 deco) — index.html 에도 없음. N/A.
ADAPTED :
- Figma 65/50/40px → token-fixed (zone-title 13 / sub-title 12 / caption / body 11)
- Figma 396×397 absolute round badge → flex card-header 안 round image
- Figma `zoom: 0.49213` absolute positioning → Phase Z flex column (zone fit)
- Figma 7 bullets absolute layout → flex column gap auto spacing
─────────────────────────────────────────────────────────────────────────────
min_height_px derivation (round 13 §2.2 — derive + confirm) :
Figma frame 1927 px @ scale 0.49213 → 948 px adapted (full Figma fit).
Phase Z slide-body ≤ 585 px → adaptive content fit.
Content density (3 col × 7 bullets + badge + photo) → F29 (345) 보다 약간 ↑.
Derived = 320 (badge 30 + bullet body ~210 + photo strip ~50 + padding 30).
Confirm via smoke + V2 validation (MDX 02 sample run).
─────────────────────────────────────────────────────────────────────────────
slots : title, personas[].{label, body, color_class}
- color_class ∈ {client, constructor, designer} (role_order 따라)
- body = list[{text:str, indent:int}] (parse_quadrant_item 출력)
- persona suffix "목표" = frame 의 visual 언어 (slot 미사용, partial 노출)
─────────────────────────────────────────────────────────────────────────────
Asset path runtime resolution :
Phase Z `copy_assets(template_id, run_dir)` (phase_z2_pipeline.py:746) copies
figma_to_html_agent/blocks/1171281191/assets/ → {run_dir}/assets/three_persona_benefits/
→ partial 의 src="assets/three_persona_benefits/<filename>.png" 가 runtime 에서 resolve.
#}
<style>
.f14b {
width: 100%; height: 100%;
display: flex; flex-direction: column;
gap: 6px;
font-family: 'Noto Sans KR', 'Pretendard', sans-serif;
word-break: keep-all;
}
.f14b__title {
font-size: var(--font-zone-title);
font-weight: 700;
line-height: var(--lh-zone-title);
background-image: linear-gradient(180deg, #000 0%, #883700 100%);
-webkit-background-clip: text; background-clip: text;
color: transparent;
flex-shrink: 0;
filter: drop-shadow(0 0 3px rgba(50,44,30,0.4));
}
.f14b__cols {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 10px;
flex: 1 1 auto;
min-height: 0;
}
.f14b__col {
position: relative;
display: flex; flex-direction: column;
border: 2px solid;
border-radius: 12px;
overflow: hidden;
min-height: 0;
isolation: isolate;
}
/* col-bg 텍스처 (PROMOTED raster) */
.f14b__col::before {
content: ""; position: absolute; inset: 0;
background: url("assets/three_persona_benefits/4a17cd1dddaba8a220b706df3ec052d2bfde4f47.png") center/cover no-repeat;
opacity: 0.6;
z-index: -2;
}
/* col-overlay (CSS solid tint per persona — index.html L44-47) */
.f14b__col::after {
content: ""; position: absolute; inset: 0;
opacity: 0.8;
z-index: -1;
}
.f14b__col--client { border-color: #285b4a; }
.f14b__col--constructor { border-color: #445a2f; }
.f14b__col--designer { border-color: #743002; }
.f14b__col--client::after { background: #d6e7c4; } /* PROMOTED CSS */
.f14b__col--constructor::after { background: #e1efe1; } /* PROMOTED CSS */
.f14b__col--designer::after { background: #d0c0ad; } /* PROMOTED CSS */
/* badge — PROMOTED raster (outer + inner ring image, name + 목표 text overlay) */
.f14b__badge {
position: relative;
display: flex; flex-direction: column;
align-items: center; justify-content: center;
padding: 6px;
flex-shrink: 0;
aspect-ratio: 1 / 1;
width: 70px; height: 70px;
margin: 6px auto 4px;
}
.f14b__badge-outer, .f14b__badge-inner {
position: absolute; inset: 0;
width: 100%; height: 100%;
object-fit: contain;
}
.f14b__badge-inner {
width: 78%; height: 78%;
top: 11%; left: 11%;
}
.f14b__badge-text {
position: relative; z-index: 1;
display: flex; flex-direction: column;
align-items: center;
text-align: center;
line-height: 1;
}
.f14b__badge-name {
font-size: var(--font-caption);
font-weight: 700;
letter-spacing: -0.04em;
}
.f14b__badge-suffix {
font-size: 0.65rem;
font-weight: 500;
margin-top: 2px;
opacity: 0.85;
}
.f14b__col--client .f14b__badge-text { color: #285b4a; }
.f14b__col--constructor .f14b__badge-text { color: #445a2f; }
.f14b__col--designer .f14b__badge-text { color: #743002; }
/* body — bullet list. check marker CSS (PROMOTED CSS, raster 6896d5c2 와 동등) */
.f14b__body {
flex: 1 1 auto;
overflow: hidden;
padding: 4px 10px;
color: #1a1a1a;
min-height: 0;
display: flex; flex-direction: column;
gap: 3px;
}
.f14b__body .text-line {
color: inherit;
font-size: var(--font-body);
line-height: var(--lh-body);
position: relative;
padding-left: 14px;
}
.f14b__body .text-line--bullet::before {
content: "\2713";
position: absolute;
left: 0; top: 0;
font-weight: 700;
}
.f14b__col--client .f14b__body .text-line--bullet::before { color: #285b4a; }
.f14b__col--constructor .f14b__body .text-line--bullet::before { color: #445a2f; }
.f14b__col--designer .f14b__body .text-line--bullet::before { color: #743002; }
/* bottom photo — PROMOTED raster (per persona 실사 사진, CSS 불가) */
.f14b__photo {
flex-shrink: 0;
width: 100%; height: 36px;
overflow: hidden;
border-radius: 0 0 8px 8px;
margin-top: 2px;
}
.f14b__photo img {
width: 100%; height: 100%;
object-fit: cover;
opacity: 0.7; /* index.html L53 — opacity 0.70 */
display: block;
}
</style>
<div class="f14b" data-frame-id="1171281191" data-template-id="three_persona_benefits">
<div class="f14b__title">{{ slot_payload.title }}</div>
<div class="f14b__cols">
{% for persona in slot_payload.personas %}
<div class="f14b__col f14b__col--{{ persona.color_class }}">
{# badge — raster outer/inner + CSS text overlay #}
<div class="f14b__badge">
{% if persona.color_class == "client" %}
<img class="f14b__badge-outer" src="assets/three_persona_benefits/77f319979c880da34ff3d423fcd97827f636c01e.png" alt="">
<img class="f14b__badge-inner" src="assets/three_persona_benefits/e64c967dd00302bfbef6cfbcbb4f7a4db5d9d96c.png" alt="">
{% elif persona.color_class == "constructor" %}
<img class="f14b__badge-outer" src="assets/three_persona_benefits/1550ec755fa7922dcfc1c90135a570d6b9df82cc.png" alt="">
<img class="f14b__badge-inner" src="assets/three_persona_benefits/85beaf9dfc17b7ed4620729a086ba22143606517.png" alt="">
{% elif persona.color_class == "designer" %}
<img class="f14b__badge-outer" src="assets/three_persona_benefits/9ac089fa9c5106b6b26d47727003641bb56ba4b0.png" alt="">
<img class="f14b__badge-inner" src="assets/three_persona_benefits/4b534ccf4e945fbe7436a0d8d96a6deffcfe5cef.png" alt="">
{% endif %}
<div class="f14b__badge-text">
<span class="f14b__badge-name">{{ persona.label | safe }}</span>
<span class="f14b__badge-suffix">목표</span>
</div>
</div>
{# body — bullets with CSS check marker #}
<div class="f14b__body">
{% if persona.body %}
{% for line in persona.body %}<div class="text-line text-line--bullet{% if line.indent > 0 %} text-line--indent-{{ line.indent }}{% endif %}">{{ line.text | safe }}</div>{% endfor %}
{% endif %}
</div>
{# bottom photo — per persona 실사 사진 #}
<div class="f14b__photo">
{% if persona.color_class == "client" %}
<img src="assets/three_persona_benefits/d2c070f200af83f563976b6b0f309d38321d204d.png" alt="">
{% elif persona.color_class == "constructor" %}
<img src="assets/three_persona_benefits/2a6a58e7bf7a645b5ede65115feb2890ccc414d1.png" alt="">
{% elif persona.color_class == "designer" %}
<img src="assets/three_persona_benefits/39113493f6e3ae76d766e86e293b0f0dcbf55d91.png" alt="">
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
@@ -0,0 +1,130 @@
# Phase Z 8-preset Layout Catalog
#
# User lock (2026-05-07) — Step 7-A:
# Layout definitions migrated from hardcoded dict (src/phase_z2_composition.py
# LAYOUT_PRESETS) to this catalog. Logic unchanged — backward compat
# (load_layout_presets() returns same dict shape).
#
# Hierarchy:
# slide_base (templates/phase_z2/slide_base.html)
# -> layout (this catalog)
# -> zone (positions)
# -> frame family partial (templates/phase_z2/families/*.html)
# -> slot payload
#
# Per-layout fields:
# render_ready: bool
# - true: layout has full grid definition + verified rendering path
# - false: layout defined but render path incomplete (future-proof marker)
# default_selection: bool
# - true: select_layout_preset() picks this as default for matching unit_count
# - false: defined alternative — not picked by current single-decision logic
# (consumed by Step 7-B multiple-candidate generation, Step 9 fit eval)
# candidate_when:
# unit_count + optional signals (orientation / layout topology hint)
# used by Step 7-B / Step 9 (currently inert — single-decision logic in
# select_layout_preset() ignores this field).
# unit_count = layout placement unit count (Step 4 output) =
# raw section_count + promoted lead_orphans 등.
single:
zones: 1
topology: single
positions: [primary]
css_areas: '"primary"'
css_cols: 1fr
css_rows: 1fr
render_ready: true
default_selection: true
candidate_when:
unit_count: 1
horizontal-2:
zones: 2
topology: rows
positions: [top, bottom]
css_areas: '"top" "bottom"'
css_cols: 1fr
css_rows: 1fr 1fr
render_ready: true
default_selection: true
candidate_when:
unit_count: 2
orientation: horizontal
vertical-2:
zones: 2
topology: cols
positions: [left, right]
css_areas: '"left right"'
css_cols: 1fr 1fr
css_rows: 1fr
render_ready: true
default_selection: false
candidate_when:
unit_count: 2
orientation: vertical
top-1-bottom-2:
zones: 3
topology: T
positions: [top, bottom-left, bottom-right]
css_areas: '"top top" "bottom-left bottom-right"'
css_cols: 1fr 1fr
css_rows: 1fr 1fr
render_ready: true
default_selection: true
candidate_when:
unit_count: 3
layout: T
top-2-bottom-1:
zones: 3
topology: inverted-T
positions: [top-left, top-right, bottom]
css_areas: '"top-left top-right" "bottom bottom"'
css_cols: 1fr 1fr
css_rows: 1fr 1fr
render_ready: true
default_selection: false
candidate_when:
unit_count: 3
layout: inverted-T
left-1-right-2:
zones: 3
topology: side-T-left
positions: [left, right-top, right-bottom]
css_areas: '"left right-top" "left right-bottom"'
css_cols: 1fr 1fr
css_rows: 1fr 1fr
render_ready: true
default_selection: false
candidate_when:
unit_count: 3
layout: side-T-left
left-2-right-1:
zones: 3
topology: side-T-right
positions: [left-top, right, left-bottom]
css_areas: '"left-top right" "left-bottom right"'
css_cols: 1fr 1fr
css_rows: 1fr 1fr
render_ready: true
default_selection: false
candidate_when:
unit_count: 3
layout: side-T-right
grid-2x2:
zones: 4
topology: 2x2
positions: [top-left, top-right, bottom-left, bottom-right]
css_areas: '"top-left top-right" "bottom-left bottom-right"'
css_cols: 1fr 1fr
css_rows: 1fr 1fr
render_ready: true
default_selection: true
candidate_when:
unit_count: 4
@@ -0,0 +1,121 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Phase Z 8-preset Layout Catalog Preview</title>
<style>
body { font-family: 'Pretendard', 'Noto Sans KR', sans-serif; padding: 24px; max-width: 1400px; margin: 0 auto; background: #f1f5f9; color: #1e293b; }
h1 { border-bottom: 2px solid #333; padding-bottom: 8px; margin-top: 0; }
.grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 24px; margin-top: 24px; }
.card { background: #fff; border: 1px solid #cbd5e1; border-radius: 8px; padding: 16px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); }
.card h2 { margin-top: 0; font-size: 18px; display: flex; justify-content: space-between; align-items: center; }
.badge { font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 999px; }
.badge.default-selection { background: #dcfce7; color: #166534; }
.badge.alternative { background: #e2e8f0; color: #475569; }
.meta { font-size: 13px; color: #64748b; margin-bottom: 12px; }
.meta code { background: #f1f5f9; padding: 1px 6px; border-radius: 3px; }
.preview { width: 100%; aspect-ratio: 16/9; border: 1px solid #94a3b8; background: #fafafa; padding: 8px; box-sizing: border-box; }
.zone { background: #dbeafe; border: 2px solid #3b82f6; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; color: #1e40af; padding: 4px; box-sizing: border-box; }
.zone:nth-child(2) { background: #fef3c7; border-color: #f59e0b; color: #92400e; }
.zone:nth-child(3) { background: #dcfce7; border-color: #16a34a; color: #166534; }
.zone:nth-child(4) { background: #fce7f3; border-color: #db2777; color: #9f1239; }
.when { margin-top: 12px; font-size: 13px; }
.when strong { color: #475569; }
</style>
</head>
<body>
<h1>Phase Z 8-preset Layout Catalog</h1>
<p>source: <code>templates/phase_z2/layouts/layouts.yaml</code> / 사람이 8 layout 시각 검증용. <strong>default selection</strong> = 현재 select_layout_preset() 단일 결정 로직에서 자동 선택. <strong>alternative</strong> = render_ready 이지만 자동 선택 안 됨 (Step 7-B 후보 활성화 대상).</p>
<div class="grid">
<div class="card">
<h2>single <span class="badge default-selection">default selection</span></h2>
<div class="meta">zones: 1 / topology: single / positions: <code>[primary]</code></div>
<div class="preview" style="display:grid; grid-template-areas:'primary'; grid-template-columns:1fr; grid-template-rows:1fr; gap:8px;">
<div class="zone" style="grid-area:primary;">primary</div>
</div>
<div class="when"><strong>candidate_when:</strong> unit_count = 1</div>
</div>
<div class="card">
<h2>horizontal-2 <span class="badge default-selection">default selection</span></h2>
<div class="meta">zones: 2 / topology: rows / positions: <code>[top, bottom]</code></div>
<div class="preview" style="display:grid; grid-template-areas:'top' 'bottom'; grid-template-columns:1fr; grid-template-rows:1fr 1fr; gap:8px;">
<div class="zone" style="grid-area:top;">top</div>
<div class="zone" style="grid-area:bottom;">bottom</div>
</div>
<div class="when"><strong>candidate_when:</strong> unit_count = 2 / orientation = horizontal</div>
</div>
<div class="card">
<h2>vertical-2 <span class="badge alternative">alternative</span></h2>
<div class="meta">zones: 2 / topology: cols / positions: <code>[left, right]</code></div>
<div class="preview" style="display:grid; grid-template-areas:'left right'; grid-template-columns:1fr 1fr; grid-template-rows:1fr; gap:8px;">
<div class="zone" style="grid-area:left;">left</div>
<div class="zone" style="grid-area:right;">right</div>
</div>
<div class="when"><strong>candidate_when:</strong> unit_count = 2 / orientation = vertical</div>
</div>
<div class="card">
<h2>top-1-bottom-2 <span class="badge default-selection">default selection</span></h2>
<div class="meta">zones: 3 / topology: T / positions: <code>[top, bottom-left, bottom-right]</code></div>
<div class="preview" style="display:grid; grid-template-areas:'top top' 'bottom-left bottom-right'; grid-template-columns:1fr 1fr; grid-template-rows:1fr 1fr; gap:8px;">
<div class="zone" style="grid-area:top;">top</div>
<div class="zone" style="grid-area:bottom-left;">bottom-left</div>
<div class="zone" style="grid-area:bottom-right;">bottom-right</div>
</div>
<div class="when"><strong>candidate_when:</strong> unit_count = 3 / layout = T</div>
</div>
<div class="card">
<h2>top-2-bottom-1 <span class="badge alternative">alternative</span></h2>
<div class="meta">zones: 3 / topology: inverted-T / positions: <code>[top-left, top-right, bottom]</code></div>
<div class="preview" style="display:grid; grid-template-areas:'top-left top-right' 'bottom bottom'; grid-template-columns:1fr 1fr; grid-template-rows:1fr 1fr; gap:8px;">
<div class="zone" style="grid-area:top-left;">top-left</div>
<div class="zone" style="grid-area:top-right;">top-right</div>
<div class="zone" style="grid-area:bottom;">bottom</div>
</div>
<div class="when"><strong>candidate_when:</strong> unit_count = 3 / layout = inverted-T</div>
</div>
<div class="card">
<h2>left-1-right-2 <span class="badge alternative">alternative</span></h2>
<div class="meta">zones: 3 / topology: side-T-left / positions: <code>[left, right-top, right-bottom]</code></div>
<div class="preview" style="display:grid; grid-template-areas:'left right-top' 'left right-bottom'; grid-template-columns:1fr 1fr; grid-template-rows:1fr 1fr; gap:8px;">
<div class="zone" style="grid-area:left;">left</div>
<div class="zone" style="grid-area:right-top;">right-top</div>
<div class="zone" style="grid-area:right-bottom;">right-bottom</div>
</div>
<div class="when"><strong>candidate_when:</strong> unit_count = 3 / layout = side-T-left</div>
</div>
<div class="card">
<h2>left-2-right-1 <span class="badge alternative">alternative</span></h2>
<div class="meta">zones: 3 / topology: side-T-right / positions: <code>[left-top, right, left-bottom]</code></div>
<div class="preview" style="display:grid; grid-template-areas:'left-top right' 'left-bottom right'; grid-template-columns:1fr 1fr; grid-template-rows:1fr 1fr; gap:8px;">
<div class="zone" style="grid-area:left-top;">left-top</div>
<div class="zone" style="grid-area:right;">right</div>
<div class="zone" style="grid-area:left-bottom;">left-bottom</div>
</div>
<div class="when"><strong>candidate_when:</strong> unit_count = 3 / layout = side-T-right</div>
</div>
<div class="card">
<h2>grid-2x2 <span class="badge default-selection">default selection</span></h2>
<div class="meta">zones: 4 / topology: 2x2 / positions: <code>[top-left, top-right, bottom-left, bottom-right]</code></div>
<div class="preview" style="display:grid; grid-template-areas:'top-left top-right' 'bottom-left bottom-right'; grid-template-columns:1fr 1fr; grid-template-rows:1fr 1fr; gap:8px;">
<div class="zone" style="grid-area:top-left;">top-left</div>
<div class="zone" style="grid-area:top-right;">top-right</div>
<div class="zone" style="grid-area:bottom-left;">bottom-left</div>
<div class="zone" style="grid-area:bottom-right;">bottom-right</div>
</div>
<div class="when"><strong>candidate_when:</strong> unit_count = 4</div>
</div>
</div>
</body>
</html>
@@ -0,0 +1,62 @@
# Phase Z Display Strategy Catalog
#
# User lock (2026-05-07) — Step 8-A:
# Display strategy vocabulary — how to present content within a region.
# Source: docs/architecture/PHASE-Z-CONTENT-OBJECT-SUBZONE-SPEC.md (4 entry).
#
# Axis separation:
# region_layouts.yaml = how to divide zone into regions (structure axis)
# display_strategies.yaml (this file) = how to display content within region (policy axis)
# The two axes are orthogonal — same region layout can use different strategies.
#
# Absolute user locks (must NOT be violated):
# - Original text/table/image/details content is NEVER dropped or summarized.
# - 오답노트 #5: 텍스트 압축 / trim / restructure 금지.
# - IMPROVEMENT-REDESIGN.md line 110: "본문은 preview, 원문은 popup/detail 무손실 보존".
# - "dropped" applies ONLY to decorative elements — strict forbidden_for enforcement.
#
# Per-entry fields:
# description: str
# applies_to: list[str] (content types that can use this strategy)
# forbidden_for: list[str] (content types that MUST NOT use this strategy)
# preserves_original: bool (true = original content kept somewhere — popup/detail)
inline_full:
description: Content fully inline — entire content rendered within region.
applies_to: [text_block, table, image, details, decorative_element]
forbidden_for: []
preserves_original: true # all content is inline, original = inline
inline_preview_with_details:
description: Partial inline preview + remaining content in details/popup.
applies_to: [text_block, table, details]
forbidden_for: [decorative_element]
preserves_original: true # User lock — original content kept in popup
detail_trigger:
placement: top-right # 본문 흐름 방해 X / 보조 동작 위치 / 안정 (user 2026-05-07)
label: details # identifier — display text 는 partial/UI 별 axis
preserves_original_note: "버튼은 원문 대체 X — 원문 전체 진입문"
details_only:
description: Summary inline only, full content in popup.
applies_to: [text_block, table, details]
forbidden_for: [decorative_element]
preserves_original: true # User lock — full content in popup
detail_trigger:
placement: top-right # user lock — popup 진입 일관 위치
label: details
preserves_original_note: "버튼은 원문 대체 X — 원문 전체 진입문"
dropped:
description: |
Decorative element omitted due to space constraints.
USER LOCK: applies ONLY to decorative_element.
NEVER drop text_block / table / image / details — original content preservation
is absolute (오답노트 #5, IMPROVEMENT-REDESIGN.md §3.6 line 110).
applies_to: [decorative_element]
forbidden_for: [text_block, table, image, details]
preserves_original: false # decorative only — no original to preserve
@@ -0,0 +1,95 @@
# Phase Z Region Layout Catalog (Internal Region structure)
#
# User lock (2026-05-07) — Step 8-A:
# Internal Region (= child zone) layout vocabulary catalog.
# Source: docs/architecture/PHASE-Z-CONTENT-OBJECT-SUBZONE-SPEC.md §2.5 (v1, 6 entry).
# Code connection (2026-05-08, Step 8-conn):
# src/phase_z2_composition.py::load_region_layouts() reads this file.
# src/phase_z2_pipeline.py step08 artifact records select_region_layout_candidates() output
# per zone (placeholder signals: region_count=1, Step 3/4 부재 종속).
# Step 9 v0 (application_plan) consumes the candidate list per unit.
#
# Hierarchy:
# Slide -> Layout (slide-body zone 분배)
# -> Zone -> Internal Region (this catalog) -> Frame -> Frame Slot -> Content
#
# Decision rule (SPEC §2.5 deterministic, AI X):
# 1. region_count == 1 -> region-single
# 2. details_presence == true / large content -> region-preview-details
# 3. region_count == 4 AND 4 equal items -> region-grid-2x2
# 4. region_count == 2 AND primary+supporting + ratio -> region-main-support
# 5. region_count == 2 AND visual element (image/diagram)-> region-horizontal-split
# 6. fallback -> region-vertical-stack
#
# Per-entry fields:
# region_count: int | str (e.g. 1, 2, 4, "2+")
# topology: vertical | horizontal | grid | weighted | preview-details | single
# default_fallback: bool (true only for region-vertical-stack — SPEC §2.5)
# description: str
# candidate_when: dict (decision rule signals — Step 8-B input)
region-single:
region_count: 1
topology: single
default_fallback: false
description: 1 region (zone entire = single region)
candidate_when:
region_count: 1
region-vertical-stack:
region_count: "2+"
topology: vertical
default_fallback: true
description: Vertical top-bottom stack. Default fallback when no other rule matches.
candidate_when:
region_count_ge: 2
flow_type: sequential
region-horizontal-split:
region_count: 2
topology: horizontal
default_fallback: false
description: Left-right horizontal split (text + image, text + diagram).
candidate_when:
region_count: 2
has_visual_element: true # image / diagram in content_type_mix
region-main-support:
region_count: 2
topology: weighted
default_fallback: false
description: Main region + supporting region (asymmetric ratio, e.g. 0.7 / 0.3).
ratios_default: [0.7, 0.3]
candidate_when:
region_count: 2
role_pattern: primary_supporting
ratio_asymmetric: true # max / min >= 2
region-preview-details:
region_count: 2
topology: preview-details
default_fallback: false
description: Inline preview region + details/popup region.
# preserves_original 은 display_strategies.yaml 의 책임 (axis 분리 lock 2026-05-07).
# User lock (원문 무손실 보존) 은 display_strategies 의 inline_preview_with_details
# / details_only 의 preserves_original: true 로 single source of truth 박힘.
candidate_when:
or:
- details_presence: true
- large_table: ">= 5 rows"
- long_text: true
region-grid-2x2:
region_count: 4
topology: grid
default_fallback: false
description: 2x2 grid (4 equal regions).
candidate_when:
region_count: 4
flow_type: parallel_4
@@ -0,0 +1,166 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Phase Z Child Zone / Internal Region Catalog Preview</title>
<style>
body { font-family: 'Pretendard', 'Noto Sans KR', sans-serif; padding: 24px; max-width: 1400px; margin: 0 auto; background: #f1f5f9; color: #1e293b; }
h1 { border-bottom: 2px solid #333; padding-bottom: 8px; margin-top: 0; }
h2 { margin-top: 40px; padding: 8px 12px; background: #1e293b; color: #fff; border-radius: 4px; }
.grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 24px; margin-top: 24px; }
.card { background: #fff; border: 1px solid #cbd5e1; border-radius: 8px; padding: 16px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); }
.card h3 { margin-top: 0; font-size: 17px; display: flex; justify-content: space-between; align-items: center; }
.badge { font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 999px; }
.badge.fallback { background: #fef3c7; color: #92400e; }
.badge.normal { background: #e0e7ff; color: #3730a3; }
.badge.danger { background: #fecaca; color: #991b1b; border: 1px solid #f87171; }
.badge.safe { background: #dcfce7; color: #166534; }
.meta { font-size: 13px; color: #64748b; margin-bottom: 12px; }
.meta code { background: #f1f5f9; padding: 1px 6px; border-radius: 3px; }
.preview { width: 100%; aspect-ratio: 16/9; border: 1px solid #94a3b8; background: #fafafa; padding: 8px; box-sizing: border-box; }
.region { background: #dbeafe; border: 2px solid #3b82f6; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; color: #1e40af; padding: 4px; box-sizing: border-box; }
.region:nth-child(2) { background: #fef3c7; border-color: #f59e0b; color: #92400e; }
.region:nth-child(3) { background: #dcfce7; border-color: #16a34a; color: #166534; }
.region:nth-child(4) { background: #fce7f3; border-color: #db2777; color: #9f1239; }
.when, .applies, .forbidden { margin-top: 12px; font-size: 13px; }
.when strong, .applies strong, .forbidden strong { color: #475569; }
.forbidden-block { background: #fef2f2; border: 2px solid #f87171; padding: 10px 12px; border-radius: 6px; margin-top: 8px; }
.forbidden-block strong { color: #991b1b; display: block; margin-bottom: 4px; font-size: 13px; }
.forbidden-block code { background: #fff; color: #991b1b; padding: 2px 6px; border-radius: 3px; font-weight: 600; }
.strategy-card { background: #fff; border: 1px solid #cbd5e1; border-radius: 8px; padding: 16px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); }
.strategy-card h3 { margin-top: 0; font-size: 17px; display: flex; justify-content: space-between; align-items: center; }
.strategy-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 24px; margin-top: 24px; }
.strategy-card.dropped-card { border: 2px solid #f87171; background: #fef2f2; }
.trigger-demo { position: relative; background: #f8fafc; border: 1px dashed #94a3b8; padding: 12px 16px; margin-top: 12px; border-radius: 4px; font-size: 12px; color: #475569; min-height: 70px; }
.trigger-btn { position: absolute; top: 6px; right: 6px; background: #2563eb; color: #fff; padding: 3px 10px; border-radius: 4px; font-size: 11px; font-weight: 600; cursor: pointer; }
.trigger-info { font-size: 11px; color: #64748b; margin-top: 6px; }
.trigger-info code { background: #fff; padding: 1px 5px; border-radius: 2px; }
</style>
</head>
<body>
<h1>Phase Z Child Zone / Internal Region Catalog</h1>
<p>source: <code>region_layouts.yaml</code> + <code>display_strategies.yaml</code> / 사람이 두 axis (구조 + 정책) 시각 검증.</p>
<p>SPEC: <code>docs/architecture/PHASE-Z-CONTENT-OBJECT-SUBZONE-SPEC.md §2.5</code> (region 6 entry, display 4 entry)</p>
<h2>1. Region Layouts (zone 안 분할 구조 — 6 entry)</h2>
<div class="grid">
<div class="card">
<h3>region-single <span class="badge normal">1 region</span></h3>
<div class="meta">topology: single / region_count: 1</div>
<div class="preview" style="display:grid; grid-template-areas:'r1'; grid-template-columns:1fr; grid-template-rows:1fr; gap:8px;">
<div class="region" style="grid-area:r1;">single region</div>
</div>
<div class="when"><strong>candidate_when:</strong> region_count = 1</div>
</div>
<div class="card">
<h3>region-vertical-stack <span class="badge fallback">default fallback</span></h3>
<div class="meta">topology: vertical / region_count: 2+</div>
<div class="preview" style="display:grid; grid-template-areas:'r1' 'r2'; grid-template-columns:1fr; grid-template-rows:1fr 1fr; gap:8px;">
<div class="region" style="grid-area:r1;">region 1 (top)</div>
<div class="region" style="grid-area:r2;">region 2 (bottom)</div>
</div>
<div class="when"><strong>candidate_when:</strong> region_count >= 2 AND flow_type = sequential. Default fallback when no other rule matches.</div>
</div>
<div class="card">
<h3>region-horizontal-split <span class="badge normal">2 region</span></h3>
<div class="meta">topology: horizontal / region_count: 2 / has_visual_element: true</div>
<div class="preview" style="display:grid; grid-template-areas:'r1 r2'; grid-template-columns:1fr 1fr; grid-template-rows:1fr; gap:8px;">
<div class="region" style="grid-area:r1;">region 1 (left)</div>
<div class="region" style="grid-area:r2;">region 2 (right)</div>
</div>
<div class="when"><strong>candidate_when:</strong> region_count = 2 AND visual element (image / diagram) in content_type_mix</div>
</div>
<div class="card">
<h3>region-main-support <span class="badge normal">2 region weighted</span></h3>
<div class="meta">topology: weighted / ratios: [0.7, 0.3]</div>
<div class="preview" style="display:grid; grid-template-areas:'r1 r2'; grid-template-columns:7fr 3fr; grid-template-rows:1fr; gap:8px;">
<div class="region" style="grid-area:r1;">main (0.7)</div>
<div class="region" style="grid-area:r2;">support (0.3)</div>
</div>
<div class="when"><strong>candidate_when:</strong> region_count = 2 AND role = [primary, supporting] AND ratio max/min >= 2</div>
</div>
<div class="card">
<h3>region-preview-details <span class="badge normal">preview + popup</span></h3>
<div class="meta">topology: preview-details / preserves_original: true</div>
<div class="preview" style="display:grid; grid-template-areas:'r1 r2'; grid-template-columns:2fr 1fr; grid-template-rows:1fr; gap:8px;">
<div class="region" style="grid-area:r1;">preview (inline)</div>
<div class="region" style="grid-area:r2;">details / popup</div>
</div>
<div class="when"><strong>candidate_when:</strong> details_presence = true OR large_table (>=5 rows) OR long_text. <strong style="color:#16a34a;">User lock: 원문 무손실 보존.</strong></div>
</div>
<div class="card">
<h3>region-grid-2x2 <span class="badge normal">4 region</span></h3>
<div class="meta">topology: grid / region_count: 4</div>
<div class="preview" style="display:grid; grid-template-areas:'r1 r2' 'r3 r4'; grid-template-columns:1fr 1fr; grid-template-rows:1fr 1fr; gap:8px;">
<div class="region" style="grid-area:r1;">region 1</div>
<div class="region" style="grid-area:r2;">region 2</div>
<div class="region" style="grid-area:r3;">region 3</div>
<div class="region" style="grid-area:r4;">region 4</div>
</div>
<div class="when"><strong>candidate_when:</strong> region_count = 4 AND content type 4 equal items</div>
</div>
</div>
<h2>2. Display Strategies (콘텐츠 처리 정책 — 4 entry)</h2>
<div class="strategy-grid">
<div class="strategy-card">
<h3>inline_full <span class="badge safe">all content types</span></h3>
<div class="meta">Content fully inline — entire content rendered within region.</div>
<div class="applies"><strong>applies_to:</strong> <code>text_block</code> <code>table</code> <code>image</code> <code>details</code> <code>decorative_element</code></div>
<div class="applies"><strong>preserves_original:</strong> true (all inline = original is inline)</div>
</div>
<div class="strategy-card">
<h3>inline_preview_with_details <span class="badge safe">popup preserved</span></h3>
<div class="meta">Partial inline preview + remaining in details/popup. <strong style="color:#16a34a;">User lock: 원문 popup 보존.</strong></div>
<div class="applies"><strong>applies_to:</strong> <code>text_block</code> <code>table</code> <code>details</code></div>
<div class="applies"><strong>preserves_original:</strong> true (popup 안에 보존)</div>
<div class="trigger-demo">
<div class="trigger-btn">details</div>
<div>preview content (일부 inline)</div>
<div>preview content ...</div>
</div>
<div class="trigger-info"><strong>detail_trigger:</strong> <code>placement: top-right</code> / <code>label: details</code> / 버튼은 원문 대체 X — 원문 전체 진입문</div>
</div>
<div class="strategy-card">
<h3>details_only <span class="badge safe">popup preserved</span></h3>
<div class="meta">Summary inline only, full content in popup. <strong style="color:#16a34a;">User lock: 전체 popup 보존.</strong></div>
<div class="applies"><strong>applies_to:</strong> <code>text_block</code> <code>table</code> <code>details</code></div>
<div class="applies"><strong>preserves_original:</strong> true (popup)</div>
<div class="trigger-demo">
<div class="trigger-btn">details</div>
<div>summary inline only</div>
</div>
<div class="trigger-info"><strong>detail_trigger:</strong> <code>placement: top-right</code> / <code>label: details</code> / 전체 원문 popup 안 보존</div>
</div>
<div class="strategy-card dropped-card">
<h3>dropped <span class="badge danger">decorative only</span></h3>
<div class="meta">Decorative element omitted due to space constraints.</div>
<div class="applies"><strong>applies_to:</strong> <code>decorative_element</code></div>
<div class="forbidden-block">
<strong>FORBIDDEN_FOR (절대 금지):</strong>
<code>text_block</code> <code>table</code> <code>image</code> <code>details</code><br>
<span style="font-size:12px; color:#7f1d1d; margin-top:4px; display:block;">
User lock — 사용자 절대 룰. 텍스트 / 표 / 이미지 / details 콘텐츠는 절대 dropped X.<br>
오답노트 #5 / IMPROVEMENT-REDESIGN.md §3.6 line 110 — 원문 무손실 보존.
</span>
</div>
</div>
</div>
</body>
</html>
@@ -0,0 +1,318 @@
<!-- Phase Z-2 MVP-1.5b — single slide + Type B layout (top/bottom zones).
원래 Phase Z 설계 복귀: MDX 1 = slide 1, layout preset = zone 분할, frame-derived block ⊂ zone. -->
<!DOCTYPE html>
<html lang="ko"{% if embedded_mode == "embedded" %} class="embedded"{% endif %}>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1280">
<title>{{ slide_title }}</title>
{% if embedded_mode == "auto" %}
<script>
(function(){
try {
var params = new URLSearchParams(window.location.search);
if (params.get('embedded') === '1' || window.self !== window.top) {
document.documentElement.classList.add('embedded');
}
} catch (e) {}
})();
</script>
{% endif %}
<style>
/* ── existing tokens (inlined) ── */
{{ token_css | safe }}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Noto Sans KR', 'Pretendard', sans-serif;
background: #e8ecf0;
display: flex; justify-content: center; align-items: center;
min-height: 100vh;
word-break: keep-all;
padding: 20px 0;
}
/* ── IMP-14 A-4: embedded mode reset (iframe consumer) ──
standalone-only body centering/min-height/padding undone so the .slide
(1280×720) sits at origin without vertical shift or clipping. */
html.embedded body {
background: transparent;
display: block;
min-height: 0;
padding: 0;
}
html.embedded .slide {
box-shadow: none;
}
/* ── 16:9 슬라이드 (single, 1280×720) ── */
.slide {
width: 1280px; height: 720px;
position: relative;
overflow: hidden;
background: #fff;
box-shadow: 0 4px 20px rgba(0,0,0,.15);
flex-shrink: 0;
}
.slide-bg {
position: absolute; inset: 0;
background: linear-gradient(180deg, #f0f0f0 0%, #ffffff 100%);
z-index: 0;
}
.slide-title {
position: absolute;
left: 52px; top: 22px;
width: calc(100% - 104px);
font-weight: 700;
font-size: var(--font-slide-title);
line-height: var(--lh-slide-title);
background-image: linear-gradient(180deg, #296b55 0%, #123328 100%);
-webkit-background-clip: text; background-clip: text;
color: transparent;
text-shadow: 0 0 2px #322c1e;
z-index: 2;
}
.slide-divider {
position: absolute;
left: 50px; top: 58px;
width: calc(100% - 100px);
height: 2px;
background: #cbd5e1;
z-index: 2;
}
.slide-body {
position: absolute;
left: 50px; top: 76px;
width: calc(100% - 100px); /* 1180 */
height: 585px;
z-index: 1;
overflow: hidden;
}
/* ── Layout preset (composition planner v0 — 8 vocabulary) ──
단일 .layout-{preset} 클래스에 grid CSS 주입.
layout_css = pipeline build_layout_css() 결과 :
- horizontal-2 : rows = dynamic px (content_weight + frame_min_height)
- 그 외 : rows / cols = fr default (LAYOUT_PRESETS)
positions (single / top / bottom / left / right / top-left / ... / grid-2x2 9개)
은 .zone 의 inline grid-area 로 지정 — preset CSS 는 areas 만 정의. */
.layout-{{ layout_preset }} {
display: grid;
grid-template-columns: {{ layout_css.cols }};
grid-template-rows: {{ layout_css.rows }};
grid-template-areas: {{ layout_css.areas | safe }};
gap: {{ gap_px }}px;
height: 100%;
}
/* ── Zone — block 가용 공간 최대화. 프레임 자체 styling 이 boundary 역할 ── */
.zone {
position: relative;
overflow: hidden;
padding: 0;
min-width: 0;
min-height: 0;
}
/* ── IMP-30 u5 : provisional zone marker (first-render invariant) ──
When V4 rank-1 candidate falls outside MVP1_ALLOWED_STATUSES (chain_exhausted)
the pipeline still renders the rank-1 frame so the first-render invariant
holds, but the zone is tagged `provisional` so the user/AI can adapt later
(IMP-31). Visual contract:
- dashed amber border + striped wash → "needs adaptation" at a glance
- inline badge top-right → text label for non-color-perceiving readers
MDX content is preserved as-is; no shrink, no rewrite. */
.zone--provisional {
outline: 2px dashed #b8860b;
outline-offset: -2px;
background-image: repeating-linear-gradient(
45deg,
rgba(184, 134, 11, 0.04) 0,
rgba(184, 134, 11, 0.04) 8px,
transparent 8px,
transparent 16px
);
}
.zone--provisional .zone__needs-adaptation-badge {
position: absolute;
top: 4px;
right: 4px;
z-index: 10;
padding: 2px 6px;
background: #b8860b;
color: #fff;
font-size: 9px;
font-weight: 700;
line-height: 1.2;
letter-spacing: 0.04em;
border-radius: 2px;
text-transform: uppercase;
pointer-events: none;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
}
/* ── Frame-family text layout contract (shared, reusable) ──
feedback-1 (mvp1.5b_test7): visible improvement 강화.
Stronger hanging indent + breathing line spacing + visible hierarchy. */
.text-line {
font-size: var(--font-body); /* 11px */
font-weight: var(--weight-body);
line-height: 1.6; /* breathing room */
margin: 0;
}
.text-line + .text-line {
margin-top: 4px; /* visible gap between lines (was 2px) */
}
/* bullet line — hanging indent via padding + absolute marker (안정적) */
.text-line--bullet {
padding-left: 14px;
position: relative;
}
.text-line--bullet::before {
content: "•"; /* visible bullet 마커 */
position: absolute;
left: 2px;
top: 0;
font-weight: 700;
line-height: inherit;
color: inherit;
}
/* indent levels — visible nested hierarchy */
.text-line--indent-1 {
padding-left: 30px;
}
.text-line--indent-1::before {
content: "◦"; /* sub-bullet 다른 마커 */
left: 18px;
}
.text-line--indent-2 {
padding-left: 46px;
}
.text-line--indent-2::before {
content: "▪";
left: 34px;
}
/* body line — 마커 없음 */
.text-line--body {
padding-left: 0;
}
.text-line strong { color: #000; font-weight: 700; }
/* ── Transform-block family (frame 29 AS-IS/TO-BE dedicated comparison component) ──
feedback-3: bullet rows 가 아닌 *전용 비교 component*. Header (AS-IS / TO-BE) + paired rows.
color-coded cells, left-border accent → 단번에 *비교 구조* 인지. */
.transform-block {
margin-top: 4px;
padding: 6px 8px 6px;
background: rgba(255, 255, 255, 0.4);
border-radius: 3px;
border: 1px solid rgba(0,0,0,0.05);
}
.transform-block__header {
display: grid;
grid-template-columns: 1fr 22px 1fr;
gap: 6px;
margin-bottom: 4px;
padding-bottom: 3px;
border-bottom: 1px dashed rgba(0,0,0,0.12);
font-size: 9px;
font-weight: 900;
letter-spacing: 0.5px;
}
.transform-block__label--from { color: #6b5444; }
.transform-block__label--to { color: #2563eb; text-align: left; }
.transform-rows {
display: flex; flex-direction: column;
gap: 4px;
}
.transform-row {
display: grid;
grid-template-columns: 1fr 22px 1fr;
gap: 6px;
align-items: stretch;
font-size: var(--font-body);
line-height: 1.45;
}
.transform-row__from {
padding: 3px 8px;
background: linear-gradient(90deg, rgba(140,120,90,0.16), rgba(140,120,90,0.08));
border-left: 2px solid #b8a98a;
border-radius: 2px;
color: #4b3f30;
font-weight: 500;
}
.transform-row__arrow {
text-align: center;
color: #2563eb;
font-weight: 900;
font-size: 14px;
align-self: center;
}
.transform-row__to {
padding: 3px 8px;
background: linear-gradient(90deg, rgba(37,99,235,0.10), rgba(37,99,235,0.18));
border-left: 2px solid #2563eb;
border-radius: 2px;
color: #1e40af;
font-weight: 700;
}
/* ── Footer pill — feedback-3: body 와 weight balance, 살짝 가벼워짐 ── */
.slide-footer {
position: absolute;
left: 50px; bottom: 8px;
width: calc(100% - 100px); /* front 기준 (body 와 정렬) */
height: 41px; /* front 기준 */
border-radius: 999px;
overflow: hidden;
z-index: 2;
display: flex; align-items: center; justify-content: center;
background: linear-gradient(90deg, #3b3523 5%, #263a2a 50%, #113f31 95%);
opacity: 0.92; /* slightly lighter */
}
.slide-footer-text {
position: relative; z-index: 1;
font-size: 15px; /* was 20px (--font-footer) */
font-weight: 700;
line-height: 1.2;
color: #fff;
text-align: center;
text-shadow: 0 0 3px rgba(0,0,0,.4);
letter-spacing: -0.01em;
}
.slide-footer-text em { color: #fe3; font-style: normal; }
.phase-z2-marker {
position: absolute;
top: 4px; right: 8px;
font-size: 9px; color: #94a3b8;
font-family: monospace;
z-index: 3;
}
</style>
</head>
<body>
<div class="slide" data-page="1">
<div class="slide-bg"></div>
<div class="phase-z2-marker">phase_z2 / mvp-1.5b / {{ layout_preset }} / single slide</div>
<div class="slide-title">{{ slide_title }}</div>
<div class="slide-divider"></div>
<div class="slide-body">
<div class="layout-{{ layout_preset }}">
{% for zone in zones %}
<div class="zone{% if zone.provisional %} zone--provisional{% endif %}" data-zone-position="{{ zone.position }}" data-template-id="{{ zone.template_id }}"{% if zone.provisional %} data-provisional="1"{% endif %} style="grid-area: {{ zone.position }};">
{% if zone.provisional %}<span class="zone__needs-adaptation-badge" aria-label="needs user or AI adaptation">needs adaptation</span>{% endif %}
{{ zone.partial_html | safe }}
</div>
{% endfor %}
</div>
</div>
{% if slide_footer %}
<div class="slide-footer">
<div class="slide-footer-text">{{ slide_footer | safe }}</div>
</div>
{% endif %}
</div>
</body>
</html>
@@ -0,0 +1,133 @@
/* ══════════════════════════════════════
slide-base.css
══════════════════════════════════════
슬라이드 공통 프레임 스타일.
slide-base.html에서 분리.
직접값 대신 토큰 변수 참조.
══════════════════════════════════════ */
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Noto Sans KR', sans-serif;
background: var(--color-bg-page);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
word-break: keep-all;
}
/* ── 16:9 슬라이드 컨테이너 ── */
.slide {
width: 1280px;
height: 720px;
position: relative;
overflow: hidden;
background: var(--color-bg);
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
}
/* ── 배경 ── */
.slide-bg {
position: absolute;
inset: 0;
background: linear-gradient(180deg, #f0f0f0 0%, #ffffff 100%);
z-index: 0;
}
.slide-bg-texture {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
opacity: 0.02;
pointer-events: none;
}
/* ── 대목차 제목 ── */
.slide-title {
position: absolute;
left: var(--slide-title-left);
top: var(--slide-title-top);
width: calc(100% - 104px);
font-weight: var(--weight-slide-title);
font-size: var(--font-slide-title);
line-height: var(--lh-slide-title);
background-image: linear-gradient(180deg, var(--color-slide-title-from) 0%, var(--color-slide-title-to) 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
text-shadow: 0 0 2px #322c1e;
z-index: 2;
}
/* ── 구분선 ── */
.slide-divider {
position: absolute;
left: 50px;
top: var(--slide-divider-top);
width: calc(100% - 100px);
height: 2px;
z-index: 2;
}
.slide-divider img {
width: 100%;
height: 100%;
display: block;
}
/* ── 본문 영역 ── */
.slide-body {
position: absolute;
left: var(--slide-padding-x);
top: var(--slide-body-top);
width: calc(100% - var(--slide-padding-x) * 2);
height: var(--slide-body-height);
z-index: 1;
overflow: hidden;
}
/* ── 하단 footer pill ── */
.slide-footer {
position: absolute;
left: 50px;
bottom: var(--slide-footer-bottom);
width: calc(100% - 100px);
height: var(--slide-footer-height);
border-radius: 999px;
overflow: hidden;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
}
.slide-footer-bg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 999px;
z-index: 0;
}
.slide-footer--css {
background: linear-gradient(90deg, var(--color-footer-bg-from) 5%, var(--color-footer-bg-mid) 50%, var(--color-footer-bg-to) 95%);
}
/* ── footer 텍스트 ── */
.slide-footer-text {
position: relative;
z-index: 1;
font-size: var(--font-footer);
font-weight: var(--weight-footer);
line-height: var(--lh-footer);
color: var(--color-footer-text);
text-align: center;
text-shadow: 0 0 4px rgba(0,0,0,0.5);
letter-spacing: -0.01em;
}
.slide-footer-text em {
color: var(--color-footer-accent);
font-style: normal;
}
@@ -0,0 +1,76 @@
/* ══════════════════════════════════════
Color Tokens v1
══════════════════════════════════════
2층 구조: 공통 테마색 + 블록 의미색.
══════════════════════════════════════ */
:root {
/* ══ 1층: 공통 테마색 ══ */
/* 모든 블록이 공유하는 기본 팔레트 */
/* 텍스트 */
--color-title: #1e293b; /* 제목 (진한 남색) */
--color-zone-title: #1a365d; /* zone 중목차 제목 */
--color-body: #475569; /* 본문 */
--color-body-strong: #1e293b; /* 본문 강조 */
--color-caption: #94a3b8; /* 캡션, 보조 */
--color-muted: #64748b; /* 약한 텍스트 */
/* 배경 */
--color-bg: #ffffff; /* 슬라이드 배경 */
--color-bg-subtle: #f8fafc; /* 카드/영역 배경 */
--color-bg-page: #e8ecf0; /* 페이지 배경 (슬라이드 바깥) */
/* 보더 */
--color-border: #e2e8f0; /* 기본 보더 */
--color-border-strong: #cbd5e1; /* 강한 보더 */
/* 강조 */
--color-accent: #2563eb; /* 링크, 강조 (파랑) */
--color-accent-hover: #eff6ff; /* 링크 hover 배경 */
--color-danger: #dc2626; /* 경고, 에러 */
/* slide-base 전용 */
--color-slide-title-from: #296b55; /* 대목차 gradient 시작 */
--color-slide-title-to: #123328; /* 대목차 gradient 끝 */
--color-footer-bg-from: #3b3523; /* footer gradient 시작 */
--color-footer-bg-mid: #263a2a; /* footer gradient 중간 */
--color-footer-bg-to: #113f31; /* footer gradient 끝 */
--color-footer-text: #ffffff; /* footer 텍스트 */
--color-footer-accent: #ffee33; /* footer 강조 (em) */
/* ══ 2층: 블록 의미색 ══ */
/* 특정 블록 유형에서 의미를 가지는 색 */
/* 3열 비교 (prerequisites-3col) */
--color-col-1-from: #0D78D0; /* 1열 gradient 시작 */
--color-col-1-to: #023056; /* 1열 gradient 끝 */
--color-col-2-from: #FF9A23; /* 2열 gradient 시작 */
--color-col-2-to: #CC5200; /* 2열 gradient 끝 */
--color-col-3-from: #39BE49; /* 3열 gradient 시작 */
--color-col-3-to: #23742C; /* 3열 gradient 끝 */
/* 비교 블록 (compare) */
--color-compare-left: #5c3714; /* 좌측 (AS-IS 계열) */
--color-compare-right: #285b4a; /* 우측 (TO-BE 계열) */
--color-compare-badge: #ae3607; /* VS 뱃지 */
/* 다크 카드 (direct render) */
--color-dark-card-1: #1a365d; /* 카드 1 배경 */
--color-dark-card-2: #1e3a2f; /* 카드 2 배경 */
--color-dark-card-3: #3b1f2b; /* 카드 3 배경 */
--color-dark-card-title: #fbbf24; /* 다크카드 제목 (금색) */
--color-dark-card-body: #e2e8f0; /* 다크카드 본문 */
/* 표 */
--color-table-header-bg: #64748b; /* 표 헤더 배경 */
--color-table-header-text: #ffffff; /* 표 헤더 텍스트 */
/* 블록 제목 gradient (Figma 원본) */
--color-block-title-from: #CC5200; /* 주황 gradient 시작 */
--color-block-title-to: #883700; /* 주황 gradient 끝 */
/* pill */
--color-pill-bg: #1e293b; /* pill 배경 */
--color-pill-text: #ffffff; /* pill 텍스트 */
}
@@ -0,0 +1,47 @@
/* ══════════════════════════════════════
Spacing Tokens v1
══════════════════════════════════════
여백/간격/들여쓰기 공통 기준.
══════════════════════════════════════ */
:root {
/* ── 기본 스케일 ── */
--space-xs: 4px;
--space-sm: 8px;
--space-md: 12px;
--space-lg: 16px;
--space-xl: 24px;
/* ── slide-base 레이아웃 ── */
--slide-padding-x: 40px; /* 본문 좌우 여백 */
--slide-title-left: 52px; /* 대목차 시작 */
--slide-title-top: 22px; /* 대목차 위치 */
--slide-divider-top: 58px; /* 구분선 위치 (slide_base.html 와 일치 2026-05-07) */
--slide-body-top: 76px; /* 본문 시작 (사용자 직설: divider-body 16px) */
--slide-body-height: 585px; /* 본문 높이 (사용자 직설: body-footer 10px) */
--slide-footer-bottom: 8px; /* footer 하단 여백 (slide_base.html 와 일치) */
--slide-footer-height: 41px; /* footer 높이 */
/* ── zone ── */
--zone-gap: 8px; /* zone 간 간격 */
--zone-padding-left: 12px; /* zone 안쪽 좌측 여백 */
--zone-padding-right: 8px; /* zone 안쪽 우측 여백 */
--zone-title-mb: 8px; /* zone 제목 아래 여백 */
/* ── 블록 공통 ── */
--card-padding: 16px; /* 카드 내부 여백 */
--card-padding-sm: 8px; /* 작은 카드 내부 여백 */
--card-gap: 12px; /* 카드 간 간격 */
--card-radius: 6px; /* 카드 모서리 */
/* ── 블릿/텍스트 ── */
--bullet-indent: 14px; /* 불릿 hanging indent */
--bullet-gap: 2px; /* 불릿 항목 간 간격 */
--heading-gap: 4px; /* 소제목 아래 간격 */
--section-gap: 10px; /* 섹션 간 간격 */
/* ── flex/grid 기본 ── */
--flex-gap: 12px; /* 기본 flex gap */
--flex-gap-sm: 6px; /* 좁은 flex gap */
--grid-gap: 16px; /* 기본 grid gap */
}
@@ -0,0 +1,77 @@
/* ══════════════════════════════════════
Typography Tokens v1
══════════════════════════════════════
슬라이드 전체의 공통 글자 위계.
모든 블록은 이 토큰을 참조한다.
직접값(font-size: 11px) 금지 → var(--font-body) 사용.
══════════════════════════════════════ */
:root {
/* ── 1층: Global Hierarchy ── */
/* 대목차: 슬라이드 상단 제목 */
--font-slide-title: 22px;
--weight-slide-title: 700;
--lh-slide-title: 1.4;
/* 중목차: zone 제목 */
--font-zone-title: 16px;
--weight-zone-title: 700;
--lh-zone-title: 1.4;
/* 소목차: 블록 내 소제목, 카드 제목 */
--font-sub-title: 12px;
--weight-sub-title: 700;
--lh-sub-title: 1.45;
/* 본문: 블릿, 설명 텍스트 */
--font-body: 10px;
--weight-body: 500;
--lh-body: 1.55;
/* 캡션: 각주, 출처, 보조 텍스트 */
--font-caption: 10px;
--weight-caption: 400;
--lh-caption: 1.4;
/* footer: 핵심 인사이트 pill */
--font-footer: 20px;
--weight-footer: 700;
--lh-footer: 1.2;
/* ── 2층: Component Semantic Tokens ── */
/* 가까운 위계에서 기본값, 필요시 override */
/* 본문 강조 (인라인 heading) */
--font-body-strong: var(--font-body);
--weight-body-strong: 600;
/* 자세히보기 링크 */
--font-detail-link: var(--font-caption);
--weight-detail-link: 500;
/* pill/badge 라벨 */
--font-pill-label: var(--font-sub-title);
--weight-pill-label: 700;
/* 표 헤더 */
--font-table-header: var(--font-sub-title);
--weight-table-header: 700;
/* 표 셀 */
--font-table-cell: var(--font-body);
--weight-table-cell: 400;
/* 비교 뱃지 */
--font-compare-badge: var(--font-sub-title);
--weight-compare-badge: 700;
/* 강조 인용 */
--font-callout: var(--font-sub-title);
--weight-callout: 700;
/* 상단 라벨 (overline) */
--font-overline: var(--font-caption);
--weight-overline: 600;
--ls-overline: 0.05em;
}