"""Phase S-2: 디자인 토큰 + CSS 패턴 + 레이아웃 규칙을 프롬프트용으로 추출. AI(Claude Sonnet)가 HTML을 생성할 때 참고하는 디자인 시스템 컨텍스트. 블록을 "선택"하는 것이 아니라, CSS 스타일을 "참고"하는 것. Phase S 검증 결과 기반: 이미지 배치, 들여쓰기, 팝업 규칙 포함. """ from __future__ import annotations from pathlib import Path ROOT = Path(__file__).parent.parent def load_design_tokens() -> str: """tokens.css 전문을 반환.""" tokens_path = ROOT / "static" / "tokens.css" if tokens_path.exists(): return tokens_path.read_text(encoding="utf-8") return "" def load_base_css() -> str: """base.css 전문을 반환.""" base_path = ROOT / "static" / "base.css" if base_path.exists(): return base_path.read_text(encoding="utf-8") return "" def get_block_css_patterns() -> str: """기존 블록에서 자주 쓰이는 CSS 패턴을 발췌하여 참고용 텍스트로 반환. 블록을 "선택"하기 위한 것이 아니라, AI가 HTML 생성 시 스타일을 "참고"하기 위한 것. """ return """ ## 블록 CSS 패턴 참고 (스타일만 참고, 구조는 AI가 결정) ### 다크 배경 패턴 배경: linear-gradient(135deg, #1e293b, #0f172a) 텍스트: #ffffff, 제목: #93c5fd, 강조: #fbbf24 border-radius: 8px, padding: 14px 20px 불릿 마커: #60a5fa 카드 내부: rgba(255,255,255,0.06), border-left: 3px solid #60a5fa ### 밝은 카드 패턴 배경: #f8fafc, 테두리: 1px solid #e2e8f0 border-radius: 8px, padding: 12px 16px 제목: 15px bold #1e293b, 설명: 13px #475569 원형 번호: 32px, background: #2563eb, color: white ### 포함 관계 시각화 패턴 외곽 박스: border: 3px solid #2563eb, border-radius: 14px 배경: linear-gradient(180deg, #eff6ff, #dbeafe) 라벨 배지: background: #2563eb, color: white, border-radius: 10px, position: absolute top: -11px 내부 카드: background: white, border: 2px solid #93c5fd, border-radius: 8px 아이콘 원: background: linear-gradient(135deg, #93c5fd, #2563eb), 36px, border-radius: 50% ### 강조 메시지 패턴 배경: #f0f9ff, 테두리: 2px solid #bae6fd 텍스트: #0c4a6e, 강조 색상: #dc2626 (danger) border-radius: 8px, padding: 10px 16px, text-align: center ### 배너/결론 패턴 배경: linear-gradient(135deg, #006aff, #00aaff) 텍스트: white, font-size: 15px bold border-radius: 8px, padding: 14px 30px, text-align: center ### 테이블 패턴 헤더: background: #1e293b, color: white, padding: 8px 12px 행: border-bottom: 1px solid #e2e8f0, 짝수행: background: #f8fafc 첫 열: font-weight: 600, color: #1e293b ### 구분선 패턴 좌우 선: height: 1px, background: #cbd5e1 중앙 텍스트: 13px bold #64748b ### Sidebar 정의 카드 패턴 배경: #f8fafc, 테두리: 1px solid #e2e8f0 번호: 22px 원형, background: #2563eb 제목: 14px bold, 설명: 12px #475569 출처: 10px italic #94a3b8 """ def get_layout_rules() -> str: """Phase S 검증 결과 기반 레이아웃 규칙.""" return """ ## 레이아웃 규칙 (검증 결과 기반 — 반드시 따를 것) ### 들여쓰기 규칙 (절대 규칙) - 불릿(•) 다음 줄은 반드시 불릿 옆 글자 시작 위치에 맞춤 - CSS: padding-left: 14px; text-indent: -14px; - 하위 불릿(◦): padding-left: 28px; text-indent: -14px; - 줄바꿈은 단어 기준 (word-break: keep-all) ### 이미지 배치 규칙 - 이미지가 있으면 float: right 또는 left로 텍스트와 어우러지게 (텍스트 감싸기) - 이미지 크기: 컨테이너 너비의 30-40% (이미지 비율에 따라 판단) - 이미지 border 없이 깔끔하게 - 이미지 바로 아래에 캡션 (이미지에 가까이 붙임, margin-top: 1-2px) - 이미지 위치: 상단 텍스트 1-2줄은 전체 너비 → 그 아래에서 이미지 시작 (margin-top으로 조정) - 이미지 아래 빈 공간에 관련 요소 배치 (팝업 링크, 부가 정보 등) ### 팝업 규칙 - 비교표, 상세 데이터 등은
/로 팝업 - 팝업 링크: 상단 오른쪽에 텍스트 링크 (박스 불필요) 예: 📊 DX와 BIM의 상세 비교 - 팝업 내용: position: absolute, background: white, box-shadow, z-index: 10 ### 여백 규칙 - 빈 공간 최소화, 컨테이너를 꽉 채움 - 패딩/간격을 과도하게 넣지 않는다 - 이미지 아래 빈 공간에는 관련 요소(캡션, 팝업 링크 등) 배치 ### 텍스트 보존 규칙 (절대 규칙) - 원본 MDX의 80-95%를 그대로 사용 - source_data에 있는 텍스트를 그대로 가져온다 - 축약/요약/재작성 금지 - 삭제는 공간 부족 시에만, 뒤에서부터 자르기 - "상위개념", "하위기술" 같은 임의 라벨을 붙이지 않는다 """ def build_design_context(container_specs: dict, images: list[dict] | None = None) -> str: """AI HTML 생성을 위한 전체 디자인 컨텍스트를 구성한다. Args: container_specs: 역할별 ContainerSpec images: 이미지 정보 리스트 [{"path", "width", "height", "ratio", "topic_id"}, ...] Returns: AI 프롬프트에 포함할 디자인 시스템 텍스트 """ if images is None: images = [] tokens = load_design_tokens() patterns = get_block_css_patterns() # 컨테이너 정보 container_lines = [] for role, spec in container_specs.items(): container_lines.append( f"- {role} ({spec.zone}): {spec.height_px}px × {spec.width_px}px, " f"topics={spec.topic_ids}, weight={spec.weight}" ) container_text = "\n".join(container_lines) # 이미지 정보 image_text = "" if images: img_lines = [] for img in images: img_lines.append( f"- 경로: {img.get('path', '')}\n" f" 크기: {img.get('width', '?')}px × {img.get('height', '?')}px\n" f" 비율: {img.get('ratio', '?')} ({'가로형' if img.get('ratio', 1) > 1.5 else '세로형' if img.get('ratio', 1) < 0.7 else '정방형'})\n" f" 소속 topic: {img.get('topic_id', '?')}" ) image_text = "\n## 이미지 정보\n" + "\n".join(img_lines) return f"""## 디자인 토큰 (CSS 변수 — 이 패턴을 참고) {tokens} ## 슬라이드 프레임 - 크기: 1280px × 720px (고정) - 폰트: Pretendard Variable - 한국어 word-break: keep-all - padding: 36px 40px 24px ## 컨테이너 스펙 (각 영역의 크기) {container_text} {image_text} {patterns} {get_layout_rules()} """