..."
}
```
- **검증:** 선택된 블록이 catalog.yaml에 실제 존재, min_height_px ≤ container.height_px
- **저장:** `context.references["본심"].*`
---
### Stage 1.5b: 디자인 예산 재계산 (블록 선택 후)
- **담당:** 코드 (AI 아님)
- **입력:** Stage 1.7에서 선택된 블록의 schema + Stage 1.5a의 컨테이너 스펙
- **목적:** 텍스트 영역 확보 후 남은 공간 = 디자인 요소 예산. **텍스트를 줄이는 것이 아니라 도형·이미지·CSS 요소의 크기를 맞추는 방향.**
```python
def calculate_design_budget(container, text_budget, block_schema):
# 블록 schema에서 텍스트 슬롯별 높이 합산
text_height = 0
for slot_name, spec in block_schema.items():
if slot_name.startswith("max_"):
continue
slot_lines = spec.get("max_lines", 1)
slot_font = spec.get("font_size", 14)
text_height += slot_lines * (slot_font * 1.6)
remaining_height = container.height_px - text_height - padding
remaining_width = container.width_px - padding
return DesignBudget(
available_height_px=remaining_height,
available_width_px=remaining_width,
max_circle_diameter=min(remaining_height, remaining_width) - 4,
max_img_width=remaining_width * 0.4,
max_img_height=remaining_height,
fits=remaining_height >= 0,
)
```
- **검증:** available_height_px ≥ 0 (음수 = 블록이 컨테이너에 안 맞음 → Stage 1.7 재선택 또는 ADJUSTABLE)
- **저장:** `context.containers["본심"].design_budget`
---
### Stage 2: HTML 생성
- **담당:** AI (Claude Sonnet 4, Anthropic API 직접, 현재 모델: `claude-sonnet-4-20250514`)
- **입력:** 원본 텍스트 + 누적 컨텍스트 전체
- **처리:** 영역별(배경/본심/첨부/결론) **각각 개별 호출**로 HTML 생성
프롬프트 구성 — 모든 수치를 **구체적으로** 전달 (Phase S 교훈: 추상적 프롬프트는 실패):
| 출처 | 포함 내용 |
|------|----------|
| Stage 0 | clean_text (원본 텍스트 — "이 텍스트를 그대로 사용하라") |
| Stage 1A | core_message |
| Stage 1B | expression_hint, relation_type |
| Stage 1.5a | 확정된 폰트 크기, 줄 수, 글자 수, 컨테이너 px |
| Stage 1.5b | 디자인 요소 크기 제약 (max_circle_px, max_img_width 등) |
| Stage 1.7 | 디자인 레퍼런스 HTML + visual_diff 설명 |
프롬프트 예시:
```
[디자인 레퍼런스]
아래 HTML의 구조와 색상 패턴을 따르되 콘텐츠를 교체하세요.
[수치 제약 — 반드시 준수]
- 컨테이너: 너비 707px, 높이 176px
- 폰트: 11px (배경 영역 위계)
- 줄당 최대 68자
- 최대 10줄
- 디자인 요소 예산: 높이 84px, 너비 707px
[원본 텍스트 — 축약/변형 금지]
"DX와 BIM이 개념적으로 명확히 정립되지 않은채 혼용되어 사용되고 있음..."
[필수 규칙]
- inline style만 사용,