phase z catalog: Step 7-A (layouts) + 8-A (regions/display) 박힘

사용자 lock 2026-05-07 — catalog data 는 yaml/HTML 에서 사람이 보고 modify
가능 (= hardcoded dict 위배 제거).

추가:
- templates/phase_z2/layouts/layouts.yaml — 8 preset (single / horizontal-2 /
  vertical-2 / top-1-bottom-2 / top-2-bottom-1 / left-1-right-2 / left-2-right-1
  / grid-2x2). 기존 hardcoded LAYOUT_PRESETS dict (src/phase_z2_composition.py)
  → catalog 이전. backward compat (load_layout_presets() 가 같은 dict shape).
  필드: zones / topology / positions / css_areas / css_cols / css_rows /
  render_ready / default_selection / candidate_when. (Step 7-A)
- templates/phase_z2/layouts/layouts_preview.html — 8 preset 시각 검증.

- templates/phase_z2/regions/region_layouts.yaml — Internal Region 6 entry
  (region-single / vertical-stack / horizontal-split / main-support /
  preview-details / grid-2x2). SPEC §2.5 의 sequential first-match
  decision tree. region-vertical-stack only default_fallback. (Step 8-A)
- templates/phase_z2/regions/display_strategies.yaml — display 4 entry
  (inline_full / inline_preview_with_details / details_only / dropped).
  applies_to / forbidden_for / detail_trigger.placement: top-right.
  사용자 절대 lock: text/table/image/details 절대 dropped X (forbidden_for).
- templates/phase_z2/regions/regions_preview.html — 6 region + 4 display 카드
  시각 검증 (axis 분리 lock — region structure ≠ display policy).

axis 분리 lock (사용자 2026-05-07):
- region (structure axis) ≠ display (policy axis) → 두 catalog 분리.
- preserves_original 은 display_strategies 의 single source of truth.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-08 09:43:18 +09:00
parent f66497cf8d
commit 8e1f5c67c1
5 changed files with 574 additions and 0 deletions

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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>