Phase X'-1~5 완료: 제목/들여쓰기/캡션/빈칸/카드 디자인

X'-1: 제목 원본 MDX frontmatter에서 가져오기 (Kei가 바꾸지 않음)
X'-2: 들여쓰기 계층 (소제목→불릿 indent 적용)
X'-3: 이미지 캡션 normalized.images alt text에서 추출
X'-4: 상단 컨테이너 justify-content:space-between
X'-5: 카드 디자인 다크 그라데이션 + 밝은 텍스트

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

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-06 11:40:53 +09:00
parent c4d7212ff3
commit 56fd9fa71e
2 changed files with 53 additions and 24 deletions

View File

@@ -726,38 +726,56 @@ def _assemble_type_b(run: Path, ctx: dict):
if current_section[0] or current_section[1]:
sections.append(current_section)
# 카드형 HTML 생성
# X'-2: 카드형 HTML — 소제목별 들여쓰기 계층
# X'-5: 카드 디자인 — 다크 그라데이션 배경, 밝은 텍스트
_card_colors = [
("linear-gradient(135deg, #1a365d, #2d3748)", "#e2e8f0"),
("linear-gradient(135deg, #1e3a2f, #2d4a3e)", "#e2e8f0"),
("linear-gradient(135deg, #3b1f2b, #4a2d3b)", "#e2e8f0"),
("linear-gradient(135deg, #2d2b55, #3d3b65)", "#e2e8f0"),
]
card_pad = int(font_size * 0.6)
card_gap = max(3, int(font_size * 0.4))
indent_body = int(font_size * 1.2) # 본문 들여쓰기
bullets = ""
if len(sections) > 1 and sections[0][0]:
# 소제목이 있는 경우 → 카드형
card_gap = max(3, int(font_size * 0.4))
for sec_title, sec_items in sections:
for ci, (sec_title, sec_items) in enumerate(sections):
bg, text_color = _card_colors[ci % len(_card_colors)]
items_html = "".join(
f'<div class="bl" style="font-size:{font_size}px;"><span class="bl-m">•</span><span class="bl-t">{item}</span></div>'
f'<div style="padding-left:{indent_body}px;margin-bottom:1px;">'
f'<span style="color:{text_color};font-size:{font_size-1}px;line-height:1.5;">• {item}</span></div>'
for item in sec_items
)
if sec_title:
bullets += (
f'<div style="background:#1e293b;color:#fff;border-radius:4px;'
f'padding:{int(font_size*0.4)}px {int(font_size*0.6)}px;margin-bottom:{card_gap}px;">'
f'<div style="font-size:{font_size}px;font-weight:700;color:#fbbf24;margin-bottom:2px;">{bold(sec_title, rn)}</div>'
f'<div style="font-size:{font_size-1}px;line-height:1.5;">{items_html}</div></div>\n'
f'<div style="background:{bg};border-radius:{int(font_size*0.4)}px;'
f'padding:{card_pad}px {int(card_pad*1.5)}px;margin-bottom:{card_gap}px;">'
f'<div style="font-size:{font_size}px;font-weight:700;color:#fbbf24;'
f'margin-bottom:{int(font_size*0.3)}px;">{bold(sec_title, rn)}</div>'
f'{items_html}</div>\n'
)
else:
bullets += items_html
else:
# 소제목 없는 경우 → 일반 불릿
for sec_title, sec_items in sections:
for item in sec_items:
bullets += f'<div class="bl" style="font-size:{font_size}px;"><span class="bl-m">•</span><span class="bl-t">{item}</span></div>\n'
bullets += (
f'<div style="padding-left:{indent_body}px;margin-bottom:1px;">'
f'<span style="font-size:{font_size}px;">• {item}</span></div>\n'
)
# 이미지 캡션: 출처 → [이미지:] 마커 → 없으면 빈 문자열
# X'-3: 이미지 캡션 — normalized.images alt → 출처 → [이미지:] 마커
img_caption = ""
for line in all_text.split("\n"):
stripped = line.strip().lstrip("")
if stripped.startswith("출처:"):
img_caption = re.sub(r'^출처:\s*', '', stripped)
break
norm_images = ctx.get("normalized", {}).get("images", [])
if norm_images:
img_caption = norm_images[0].get("alt", "")
if not img_caption:
for line in all_text.split("\n"):
stripped = line.strip().lstrip("")
if stripped.startswith("출처:"):
img_caption = re.sub(r'^출처:\s*', '', stripped)
break
if not img_caption:
img_marker = re.search(r'\[이미지:\s*([^\]]+)\]', all_text)
if img_marker:
@@ -778,11 +796,13 @@ def _assemble_type_b(run: Path, ctx: dict):
primary_topic = topic_map.get(tids[0], {}) if tids else {}
topic_title = bold(primary_topic.get("title", ""), rn)
# X'-4: 상단 컨테이너 — 내용을 전체 높이에 균등 배분
top_html = (
f'<div style="position:relative;height:100%;padding:{gap_small}px;box-sizing:border-box;">'
f'<div style="position:relative;height:100%;padding:{gap_small}px;box-sizing:border-box;'
f'display:flex;flex-direction:column;justify-content:space-between;">'
f'{popup_html}'
f'<div style="font-weight:700;font-size:{font_size+1}px;color:#1a365d;margin-bottom:4px;">{topic_title}</div>'
f'<div style="display:flex;gap:{max(6, int(font_size*0.8))}px;align-items:flex-start;">'
f'<div style="display:flex;gap:{max(6, int(font_size*0.8))}px;align-items:flex-start;flex:1;">'
f'<div style="flex:1;font-size:{font_size}px;line-height:1.55;color:#333;">{bullets}</div>'
f'{img_block}</div></div>'
)
@@ -798,19 +818,26 @@ def _assemble_type_b(run: Path, ctx: dict):
primary_topic = topic_map.get(tids[0], {}) if tids else {}
topic_title = bold(primary_topic.get("title", ""), rn)
# X'-2: 들여쓰기 계층 (소제목+불릿)
bl_indent = int(font_size * 1.2)
bullets = ""
for line in all_text.split("\n"):
stripped = line.strip()
if not stripped or re.search(r'\[팝업:|\[이미지:', stripped):
continue
clean = stripped.lstrip(" ")
clean = bold(clean, rn)
bullets += f'<div class="bl" style="font-size:{font_size}px;"><span class="bl-m">•</span><span class="bl-t">{clean}</span></div>\n'
if stripped.startswith("### "):
# 소제목
sub_title = stripped.lstrip("# ").strip()
bullets += f'<div style="font-weight:700;font-size:{font_size}px;color:#1e40af;margin-top:{int(font_size*0.4)}px;">{bold(sub_title, rn)}</div>\n'
else:
clean = stripped.lstrip("")
clean = bold(clean, rn)
bullets += f'<div style="padding-left:{bl_indent}px;font-size:{font_size}px;margin-bottom:1px;">• {clean}</div>\n'
bl_html = (
f'<div style="height:100%;padding:{gap_small}px;box-sizing:border-box;">'
f'<div style="font-weight:700;font-size:{font_size+1}px;color:#1a365d;margin-bottom:4px;">{topic_title}</div>'
f'<div style="font-size:{font_size}px;line-height:1.55;color:#333;">{bullets}</div></div>'
f'<div style="line-height:1.55;color:#333;">{bullets}</div></div>'
)
# 하단 우측