04. design_agent 추가 — 콘텐츠 시각 구조화 슬라이드 생성기

5단계 AI 파이프라인:
1. Kei 실장(Opus via Kei API) — 꼭지 추출 + 정보 구조 파악
2. 디자인 팀장 — FAISS 블록 검색 + Opus 추천 + Sonnet 블록 매핑
3. Kei 편집자(Kei API) — 도메인 전문 텍스트 정리
4. 디자인 실무자(Sonnet + Jinja2) — CSS 변수 조정 + HTML 조립
5. 디자인 팀장(Sonnet) — 균형 재검토 (최대 2회 루프)

블록 라이브러리 46개 (6 카테고리) + _legacy 13개
FAISS 블록 검색 (bge-m3, 1024차원)
SVG N개 동적 배치 (cos/sin 좌표 계산)
Pillow 이미지 크기 측정 + base64 인라인
컨테이너 예산 기반 블록 배치 (zone별 높이 px)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 18:47:13 +09:00
parent 9b905a4313
commit 688ddbbb17
244 changed files with 23955 additions and 0 deletions

View File

@@ -0,0 +1,112 @@
# 블록 라이브러리 인덱스 (46개)
디자인 팀장이 콘텐츠에 맞는 블록을 선택할 때 참조하는 라이브러리.
각 카테고리 안에 변형이 여러 개 있으며, 콘텐츠 성격에 따라 적절한 변형을 선택한다.
**시각화(visuals/)는 SVG로 제작한다** — CSS/AI 이미지 금지.
---
## 📁 headers/ (5개) — 타이틀, 꼭지 헤더
| 파일 | 설명 | 언제 사용 |
|------|------|---------|
| `section-title-with-bg.html` | 배경 이미지 위 영문+한글 타이틀 (500px) | 페이지 맨 첫 화면, 배경 이미지 있을 때 |
| `section-header-bar.html` | 파란 바 + 중앙 흰 제목 (컴팩트) | 섹션 시작 가볍게, 주제 전환 |
| `topic-left-right.html` | 좌:파란 제목 + 우:설명 | 질문+답변 구조, 주장+근거 |
| `topic-center.html` | 중앙 정렬 대제목 + 서브+설명 | 단독 강조, 주제 선언 |
| `topic-numbered.html` | 번호 원형 + 제목 + 구분선 + 설명 | 순서 있는 꼭지 (1번, 2번, 3번) |
---
## 📁 cards/ (10개) — 카드 계열
| 파일 | 설명 | 언제 사용 |
|------|------|---------|
| `card-image-3col.html` | 이미지(160px) + 색상 제목 + 영문 + 불릿 (3열) | 단계별 설명에 이미지 핵심 |
| `card-text-grid.html` | 파란 액센트 + 제목 + 뱃지 + 설명 + 출처 (2~4열) | 용어 정의, 텍스트만 나열 |
| `card-dark-overlay.html` | 다크 이미지 배경 + 흰 제목 + 짧은 설명 (3~5열) | 키워드 시각 강조, 임팩트 |
| `card-tag-image.html` | 색상 태그 라벨 + 이미지 + 제목 + 설명 (3열) | 카테고리별 분류 (제조/건축/토목) |
| `card-icon-desc.html` | 큰 이모지 아이콘 + 제목 + 설명 (2~4열) | 기능/특성/장점 아이콘 나열 |
| `card-compare-3col.html` | 색상 헤더 카드 3열 + 불릿 | 3개 카테고리 비교 (상용/범용/전문) |
| `card-step-vertical.html` | 좌 색상 마커 + 우 콘텐츠 (세로, 연결선) | 생애주기 단계별 (이미지+설명) |
| `card-image-round.html` | 원형 이미지(140px) + 제목 + 설명 (2~3열) | 포트폴리오형, 비전/가치 |
| `card-stat-number.html` | 큰 숫자(36px) + 단위 + 라벨 (2~4열) | KPI, 성과 수치, 비용 절감율 |
| `card-numbered.html` | 색상 원형 번호 + 제목 + 설명 (세로) | 순서 있는 항목 (실행 단계, 조건) |
---
## 📁 tables/ (3개) — 표/비교 계열
| 파일 | 설명 | 언제 사용 |
|------|------|---------|
| `compare-3col-badge.html` | A \| VS배지 \| B 3단 비교 (행별) | 두 개념 다항목 비교 (BIM vs DX) |
| `compare-2col-split.html` | 파란 헤더 좌/구분/우 + 행별 비교 | 두 기술 항목별 상세 비교 (GIS vs BIM) |
| `table-simple-striped.html` | 남색 헤더 + 줄무늬 행. 범용 | 스펙표, 일정표, 수치 목록 |
---
## 📁 visuals/ (10개) — 시각 요소 (**SVG**)
**SVG 노하우:**
- `<text>` = 원 좌표와 같은 공간 → 위치 100% 정확
- `radialGradient`/`linearGradient` → 고급 그라데이션
- `filter` → 글로우/그림자
- 수학적 계산 (cos/sin) → N개 자동 배치 (Phase 2)
- **AI 이미지로 시각화 만들면 안 됨** — 원 위치 맞출 수 없음
| 파일 | 설명 | 언제 사용 |
|------|------|---------|
| `venn-diagram.html` | **SVG premium** 벤 다이어그램 (그라데이션+글로우) | 포함 관계, 기술 융합 (**★단독 배치**) |
| `circle-gradient.html` | 파란 그라데이션 원 + 중앙 텍스트 | 섹션 전환 키워드 강조 |
| `compare-pill-pair.html` | 이중 테두리 둥근 박스 2개 + VS | 2개 개념 시각 대비 (표 위 헤더) |
| `process-horizontal.html` | 파란 번호 원 + 카드 + → 화살표 (가로) | 논리적 프로세스 흐름 |
| `flow-arrow-horizontal.html` | 색상 캡슐 + 화살표 (SVG, 컴팩트) | 기술 발전/전환 흐름 간결하게 |
| `keyword-circle-row.html` | SVG 원형 안 큰 글자 + 라벨 + 설명 | 약어 풀이 (G-S-I-M) |
| `layer-diagram.html` | SVG 겹친 사다리꼴 레이어 (3D) | 기술 스택/계층 구조 |
| `timeline-vertical.html` | 세로 선 + SVG 마커 + 연도+제목+설명 | 연혁, 로드맵 (4개+ 이벤트) |
| `timeline-horizontal.html` | SVG 가로 선 + 마커 + 연도+제목 | 짧은 일정 (3~5개, 컴팩트) |
| `pyramid-hierarchy.html` | SVG 위→아래 넓어지는 사각형 | 위계, 우선순위 (좁은→넓은) |
---
## 📁 emphasis/ (13개) — 강조, 인용, 결론
| 파일 | 설명 | 언제 사용 |
|------|------|---------|
| `quote-left-border.html` | 좌측 빨간 라인 + 연한 배경 + 인용+출처 | 짧은 인용, 문제 제기 |
| `quote-big-mark.html` | ❝❞ 큰따옴표 장식 + 인용+출처 | 임팩트 인용, 핵심 발언 |
| `quote-question.html` | 파란 배경+테두리 + 큰 질문 텍스트 | 독자에게 질문, 전환점 |
| `conclusion-accent-bar.html` | 회색 배경 + 좌측 파란 라인 + 결론 | 페이지 하단 핵심 한 줄 |
| `comparison-2col.html` | 좌 파란 vs 우 빨간 헤더 + 본문 | A vs B 직접 비교 |
| `banner-gradient.html` | 파란 그라데이션 배너 + 중앙 흰 텍스트 | 섹션 구분, 핵심 선언 |
| `dark-bullet-list.html` | 짙은 남색 배경 + 파란 제목 + 흰 불릿 | 핵심 포인트 강조 (무게감) |
| `highlight-strip.html` | 가로 색상 구간 + 흰 라벨 | 카테고리 색상 분류 바 |
| `callout-solution.html` | 파란 배경+테두리 + 아이콘 + 제목+설명 | 해결책, 솔루션, 방향성 |
| `callout-warning.html` | 빨간 배경+테두리 + 아이콘 + 제목+설명 | 문제점, 주의, 잘못된 접근 |
| `tab-label-row.html` | 가로 탭 버튼 (선택됨=색상, 나머지=회색) | 카테고리 전환/분류 표시 |
| `divider-text.html` | 좌우 회색 선 + 중앙 텍스트 | 가벼운 섹션 구분, 휴식점 |
| `details-block.html` | `<details>/<summary>` 접기/펼치기 | 상세 콘텐츠, 자세히보기 |
---
## 📁 media/ (5개) — 이미지/미디어
| 파일 | 설명 | 언제 사용 |
|------|------|---------|
| `image-row-2col.html` | 이미지 2장 나란히 (354px) | 시공 사진 2장, 현장 비교 |
| `image-grid-2x2.html` | 이미지 4장 2x2 격자 (200px) | 현장 사진 4장, 갤러리 |
| `image-side-text.html` | 좌:이미지(320px) + 우:제목+설명+불릿 | 이미지 설명, 시스템 소개 |
| `image-full-caption.html` | 전체 너비 이미지 1장 + 캡션 | 핵심 도표, 대형 다이어그램 |
| `image-before-after.html` | Before(회색) + → + After(파란) | 변화 전후 비교 |
---
## 추가 규칙
1. **이름 규칙:** `{유형}-{특징}.html`
2. **모든 변형은 catalog.yaml에 등록** — 미등록 = 팀장이 모름
3. **각 파일 상단에 HTML 주석으로 용도/슬롯 명시**
4. **디자인 토큰 사용** — 하드코딩 색상 금지 (가능한 범위에서)
5. **높이 고정 금지** — 모드 독립적 (슬라이드/웹 겸용)
6. **visuals/는 SVG로 제작** — CSS 원/AI 이미지 금지

View File

@@ -0,0 +1,65 @@
<!-- 카드 그리드 블록: 2~4열 카드 배열 -->
<div class="block-card-grid" style="--card-count: {{ cards|length }}">
{% for card in cards %}
<div class="card" style="border-top-color: {{ card.color | default('var(--color-accent)') }}">
{% if card.icon %}<div class="card-icon">{{ card.icon }}</div>{% endif %}
<div class="card-title">{{ card.title }}</div>
{% if card.category %}<span class="card-category">{{ card.category }}</span>{% endif %}
<div class="card-description">{{ card.description }}</div>
{% if card.source %}<div class="card-source">{{ card.source }}</div>{% endif %}
</div>
{% endfor %}
</div>
<style>
.block-card-grid {
display: grid;
grid-template-columns: repeat(var(--card-count, 3), 1fr);
gap: var(--spacing-inner);
height: 100%;
}
.card {
background: var(--color-bg);
border: var(--border-width) solid var(--color-border);
border-top: var(--accent-border) solid var(--color-accent);
border-radius: var(--radius);
padding: var(--spacing-inner);
display: flex;
flex-direction: column;
}
.card-icon {
font-size: 1.5rem;
margin-bottom: var(--spacing-small);
}
.card-title {
font-size: var(--font-subtitle);
font-weight: var(--weight-bold);
color: var(--color-primary);
margin-bottom: 4px;
}
.card-category {
font-size: var(--font-small);
font-weight: var(--weight-medium);
color: var(--color-accent);
background: #dbeafe;
padding: 2px 8px;
border-radius: 12px;
display: inline-block;
margin-bottom: var(--spacing-small);
width: fit-content;
}
.card-description {
font-size: var(--font-body);
color: var(--color-text);
line-height: var(--line-height-ko);
flex: 1;
}
.card-source {
font-size: var(--font-small);
color: var(--color-text-light);
font-style: italic;
margin-top: var(--spacing-small);
border-top: var(--border-width) solid var(--color-border);
padding-top: var(--spacing-small);
}
</style>

View File

@@ -0,0 +1,95 @@
<!-- 이미지 카드: 상단 이미지 + 하단 텍스트 (2~4열) -->
<!--
📋 card-image
─────────────────
용도: 단계별 설명, 카테고리별 설명 (이미지가 핵심인 카드)
슬롯: cards[] 배열 (각 카드에 image, title, title_en, items[])
Figma 원본: 2-1_02 > Group 1171281594 (카드 3열)
-->
<div class="block-card-image" style="--ci-count: {{ cards|length }}">
{% for card in cards %}
<div class="ci-card">
{% if card.image %}
<img class="ci-img" src="{{ card.image }}" alt="{{ card.title }}">
{% endif %}
<div class="ci-body">
<div class="ci-title" style="color: {{ card.color | default('var(--color-accent, #006aff)') }}">{{ card.title }}</div>
{% if card.title_en %}<div class="ci-title-en">{{ card.title_en }}</div>{% endif %}
<div class="ci-divider"></div>
<ul class="ci-list">
{% for item in card.bullets %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% if card.source %}<div class="ci-source">{{ card.source }}</div>{% endif %}
</div>
</div>
{% endfor %}
</div>
<style>
.block-card-image {
display: grid;
grid-template-columns: repeat(var(--ci-count, 3), 1fr);
gap: 16px;
}
.ci-card {
background: var(--color-bg, #ffffff);
border-radius: var(--radius, 8px);
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
overflow: hidden;
display: flex;
flex-direction: column;
}
.ci-img {
width: 100%;
height: 160px;
object-fit: contain;
background: #f8f9fb;
padding: 10px;
}
.ci-body {
padding: 16px;
flex: 1;
display: flex;
flex-direction: column;
}
.ci-title {
font-size: 14px;
font-weight: var(--weight-bold, 700);
text-decoration: underline;
text-underline-offset: 3px;
margin-bottom: 2px;
}
.ci-title-en {
font-size: 12px;
font-weight: var(--weight-normal, 400);
color: var(--color-text-secondary, #666);
margin-bottom: 10px;
}
.ci-divider {
width: 100%;
height: 1px;
background: #000;
margin-bottom: 10px;
}
.ci-list {
list-style: disc;
padding-left: 18px;
font-size: 13px;
line-height: 1.7;
color: var(--color-text, #000);
flex: 1;
}
.ci-list li {
margin-bottom: 3px;
}
.ci-source {
font-size: 11px;
color: var(--color-text-light, #94a3b8);
font-style: italic;
margin-top: 8px;
border-top: 1px solid var(--color-border, #e2e8f0);
padding-top: 6px;
}
</style>

View File

@@ -0,0 +1,56 @@
<!-- 원형 라벨: CSS 그라데이션 원 + 중앙 텍스트 -->
<!--
📋 circle-label
─────────────────
용도: 섹션 전환점, 핵심 키워드 강조, 시각적 구분자
슬롯: label (필수), sub_label (선택)
Figma 원본: 2-1_02 > Group 1171281590 (190x190)
-->
<div class="block-circle-label">
<div class="cl-outer">
<div class="cl-inner">
<div class="cl-text">{{ label }}</div>
{% if sub_label %}<div class="cl-sub">{{ sub_label }}</div>{% endif %}
</div>
</div>
</div>
<style>
.block-circle-label {
display: flex;
justify-content: center;
padding: 20px 0;
}
.cl-outer {
width: 190px;
height: 190px;
border-radius: 50%;
background: linear-gradient(180deg, #3db8ff 0%, #006aff 100%);
box-shadow: 0 0 30px rgba(0, 106, 255, 0.25), 0 0 60px rgba(0, 106, 255, 0.1);
display: flex;
align-items: center;
justify-content: center;
}
.cl-inner {
width: 170px;
height: 170px;
border-radius: 50%;
background: linear-gradient(180deg, #4dc4ff 0%, #0080ff 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #ffffff;
text-align: center;
}
.cl-text {
font-size: 20px;
font-weight: var(--weight-bold, 700);
line-height: 1.4;
}
.cl-sub {
font-size: 12px;
opacity: 0.8;
margin-top: 4px;
}
</style>

View File

@@ -0,0 +1,67 @@
<!-- 비교 박스: 둥근 테두리 박스 2개 + VS 라벨 -->
<!--
📋 compare-box
─────────────────
용도: 2개 개념을 시각적으로 대비 (비교 테이블 위 헤더로 사용)
슬롯: left_label, left_sub, right_label, right_sub
Figma 원본: 2-1_02 > 하늘색 둥근 박스 2개 + 시안 텍스트
-->
<div class="block-compare-box">
<div class="cb-item">
<div class="cb-text">
<div class="cb-label">{{ left_label }}</div>
{% if left_sub %}<div class="cb-sub">{{ left_sub }}</div>{% endif %}
</div>
</div>
<div class="cb-vs">VS</div>
<div class="cb-item">
<div class="cb-text">
<div class="cb-label">{{ right_label }}</div>
{% if right_sub %}<div class="cb-sub">{{ right_sub }}</div>{% endif %}
</div>
</div>
</div>
<style>
.block-compare-box {
display: flex;
gap: 16px;
align-items: center;
justify-content: center;
padding: 15px 0;
}
.cb-item {
width: 340px;
height: 110px;
border-radius: 55px;
border: 3px solid #7ec8f0;
background: linear-gradient(135deg, #e8f4fd 0%, #d4ecfa 100%);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 10px rgba(0, 140, 220, 0.1);
}
.cb-text {
text-align: center;
}
.cb-label {
font-size: 19px;
font-weight: 800;
color: #0090d0;
line-height: 1.4;
}
.cb-sub {
font-size: 13px;
color: #0090d0;
margin-top: 3px;
line-height: 1.5;
white-space: pre-line;
font-weight: 500;
}
.cb-vs {
font-size: 22px;
font-weight: 700;
color: #333;
flex-shrink: 0;
}
</style>

View File

@@ -0,0 +1,97 @@
<!-- 비교 테이블: BIM vs DX 스타일 3단 테이블 -->
<!--
📋 comparison-table
─────────────────
용도: 다항목 비교 (좌측 A | 중앙 기준 | 우측 B)
슬롯: headers[] (3개), rows[][] (각 행 3칸)
Figma 원본: 2-1_02 > BIM VS D/X 테이블
특징: 중앙 칼럼에 파란 그라데이션 배지, 좌우 불릿 대비
-->
<div class="block-table-figma">
<table>
<thead>
<tr>
{% for header in headers %}
<th class="{% if loop.index == 1 %}th-left{% elif loop.index == 2 %}th-center{% else %}th-right{% endif %}">
{% if loop.index == 2 %}<span class="th-badge">{{ header }}</span>{% else %}{{ header }}{% endif %}
</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in rows %}
<tr>
{% for cell in row %}
<td class="{% if loop.index == 1 %}td-left{% elif loop.index == 2 %}td-center{% else %}td-right{% endif %}">{{ cell }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
<style>
.block-table-figma {
overflow: auto;
}
.block-table-figma table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
line-height: 1.7;
}
/* 헤더 */
.block-table-figma thead th {
padding: 14px 12px;
font-size: 16px;
font-weight: 700;
border-bottom: 2px solid #e8edf2;
}
.th-left {
text-align: center;
color: #6bcdff;
}
.th-center {
text-align: center;
width: 120px;
}
.th-badge {
display: inline-block;
background: linear-gradient(135deg, #006eff 0%, #00aaff 100%);
color: #ffffff;
font-size: 15px;
font-weight: 700;
padding: 8px 28px;
border-radius: 25px;
}
.th-right {
text-align: center;
color: #006eff;
}
/* 본문 */
.block-table-figma tbody td {
padding: 10px 14px;
border-bottom: 1px solid #f0f2f5;
vertical-align: middle;
}
.td-left {
text-align: center;
color: #444;
}
.td-center {
text-align: center;
font-weight: 700;
color: #333;
background: #f6f8fb;
font-size: 13px;
}
.td-right {
text-align: center;
color: #444;
}
.block-table-figma tbody tr:hover {
background: #fafbfd;
}
</style>

View File

@@ -0,0 +1,51 @@
<!-- 비교 블록: 2단 병렬 레이아웃 -->
<div class="block-comparison">
<div class="comparison-left">
<div class="comparison-header comparison-header--left">{{ left_title }}</div>
{% if left_subtitle %}<div class="comparison-subtitle">{{ left_subtitle }}</div>{% endif %}
<div class="comparison-content">{{ left_content }}</div>
</div>
<div class="comparison-divider"></div>
<div class="comparison-right">
<div class="comparison-header comparison-header--right">{{ right_title }}</div>
{% if right_subtitle %}<div class="comparison-subtitle">{{ right_subtitle }}</div>{% endif %}
<div class="comparison-content">{{ right_content }}</div>
</div>
</div>
<style>
.block-comparison {
display: grid;
grid-template-columns: 1fr auto 1fr;
gap: var(--spacing-inner);
height: 100%;
}
.comparison-divider {
width: 1px;
background: var(--color-border);
}
.comparison-header {
font-size: var(--font-subtitle);
font-weight: var(--weight-bold);
padding-bottom: var(--spacing-small);
margin-bottom: var(--spacing-small);
border-bottom: var(--accent-border) solid;
}
.comparison-header--left {
border-color: var(--color-accent);
color: var(--color-accent);
}
.comparison-header--right {
border-color: var(--color-danger);
color: var(--color-danger);
}
.comparison-subtitle {
font-size: var(--font-caption);
color: var(--color-text-secondary);
margin-bottom: var(--spacing-small);
}
.comparison-content {
font-size: var(--font-body);
line-height: var(--line-height-ko);
}
</style>

View File

@@ -0,0 +1,38 @@
<!-- 결론 바: Figma 톤에 맞춘 하단 핵심 메시지 -->
<!--
📋 conclusion-bar
─────────────────
용도: 슬라이드/페이지 하단 핵심 한 줄 요약
슬롯: conclusion_text (필수), label (선택)
Figma 톤: 밝은 회색 배경 + 좌측 파란 액센트 라인 + 진한 텍스트
-->
<div class="block-conclusion-figma">
{% if label %}<div class="cf-label">{{ label }}</div>{% endif %}
<div class="cf-text">{{ conclusion_text }}</div>
</div>
<style>
.block-conclusion-figma {
background: #f4f6f9;
border-left: 4px solid #006aff;
border-radius: 0 8px 8px 0;
padding: 18px 28px;
display: flex;
flex-direction: column;
gap: 4px;
}
.cf-label {
font-size: 11px;
font-weight: 600;
color: #006aff;
text-transform: uppercase;
letter-spacing: 1px;
}
.cf-text {
font-size: 15px;
font-weight: 700;
color: #1e293b;
line-height: 1.6;
word-break: keep-all;
}
</style>

View File

@@ -0,0 +1,39 @@
<!-- 이미지 행: 2~4장 이미지 나란히 -->
<!--
📋 image-row
─────────────────
용도: 시공 사진, 근거 자료, 현장 이미지 나란히 배치
슬롯: images[] 배열 (각 이미지에 src, alt, caption)
Figma 원본: 2-1_02 > image grid (460x354 x 2)
-->
<div class="block-image-row" style="--ir-count: {{ images|length }}">
{% for img in images %}
<div class="ir-item">
<img src="{{ img.src }}" alt="{{ img.alt | default('') }}">
{% if img.caption %}<div class="ir-caption">{{ img.caption }}</div>{% endif %}
</div>
{% endfor %}
</div>
<style>
.block-image-row {
display: grid;
grid-template-columns: repeat(var(--ir-count, 2), 1fr);
gap: 0;
}
.ir-item {
overflow: hidden;
}
.ir-item img {
width: 100%;
height: 354px;
object-fit: cover;
display: block;
}
.ir-caption {
font-size: 11px;
color: var(--color-text-light, #94a3b8);
text-align: center;
padding: 4px;
}
</style>

View File

@@ -0,0 +1,61 @@
<!-- 프로세스 블록: 가로 단계 흐름 -->
<div class="block-process">
{% for step in steps %}
<div class="process-step">
<div class="process-number">{{ step.number | default(loop.index) }}</div>
<div class="process-title">{{ step.title }}</div>
{% if step.description %}<div class="process-desc">{{ step.description }}</div>{% endif %}
</div>
{% if not loop.last %}
<div class="process-arrow"></div>
{% endif %}
{% endfor %}
</div>
<style>
.block-process {
display: flex;
align-items: center;
justify-content: center;
gap: var(--spacing-small);
height: 100%;
}
.process-step {
flex: 1;
text-align: center;
padding: var(--spacing-inner);
background: var(--color-bg-subtle);
border: var(--border-width) solid var(--color-border);
border-radius: var(--radius);
}
.process-number {
width: 36px;
height: 36px;
border-radius: 50%;
background: var(--color-accent);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: var(--weight-bold);
font-size: var(--font-body);
margin: 0 auto var(--spacing-small);
}
.process-title {
font-size: var(--font-body);
font-weight: var(--weight-bold);
color: var(--color-primary);
margin-bottom: 4px;
}
.process-desc {
font-size: var(--font-caption);
color: var(--color-text-secondary);
line-height: var(--line-height-ko);
}
.process-arrow {
font-size: 1.5rem;
color: var(--color-accent);
font-weight: var(--weight-bold);
flex-shrink: 0;
}
</style>

View File

@@ -0,0 +1,29 @@
<!-- 강조 인용 블록: 문제 제기, 핵심 메시지 -->
<div class="block-quote">
<div class="quote-text">{{ quote_text }}</div>
{% if source %}<div class="quote-source">{{ source }}</div>{% endif %}
</div>
<style>
.block-quote {
background: var(--color-bg-subtle);
border-left: var(--accent-border) solid var(--color-danger);
padding: var(--spacing-inner) var(--spacing-block);
border-radius: 0 var(--radius) var(--radius) 0;
display: flex;
flex-direction: column;
justify-content: center;
}
.quote-text {
font-size: var(--font-body);
color: var(--color-text);
line-height: var(--line-height-ko);
font-weight: var(--weight-medium);
}
.quote-source {
font-size: var(--font-caption);
color: var(--color-text-light);
font-style: italic;
margin-top: var(--spacing-small);
}
</style>

View File

@@ -0,0 +1,88 @@
<!-- 관계도 블록: 벤 다이어그램 -->
<div class="block-relationship">
<div class="venn-container">
<div class="venn-outer">
<span class="venn-outer-label">{{ center_label }}</span>
<span class="venn-outer-sub">{{ center_sub }}</span>
</div>
{% for item in items %}
<div class="venn-inner venn-inner--{{ loop.index }}" style="background: {{ item.color | default('rgba(37, 99, 235, 0.8)') }}">
<span>{{ item.label }}</span>
</div>
{% endfor %}
</div>
{% if description %}
<div class="relationship-desc">
{{ description }}
</div>
{% endif %}
</div>
<style>
.block-relationship {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
gap: var(--spacing-inner);
}
.venn-container {
position: relative;
width: 280px;
height: 280px;
}
.venn-outer {
width: 280px;
height: 280px;
border-radius: 50%;
border: 3px solid var(--color-accent);
background: rgba(37, 99, 235, 0.05);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
}
.venn-outer-label {
font-size: var(--font-subtitle);
font-weight: var(--weight-black);
color: var(--color-accent);
}
.venn-outer-sub {
font-size: var(--font-caption);
color: var(--color-text-secondary);
}
.venn-inner {
position: absolute;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: var(--font-caption);
font-weight: var(--weight-bold);
}
.venn-inner--1 {
width: 70px; height: 70px;
top: 30px; left: 30px;
background: rgba(16, 185, 129, 0.85);
}
.venn-inner--2 {
width: 80px; height: 80px;
bottom: 40px; left: 30px;
background: rgba(59, 130, 246, 0.85);
}
.venn-inner--3 {
width: 75px; height: 75px;
top: 60px; right: 25px;
background: rgba(139, 92, 246, 0.85);
}
.relationship-desc {
font-size: var(--font-body);
color: var(--color-text);
text-align: center;
max-width: 500px;
line-height: var(--line-height-ko);
}
</style>

View File

@@ -0,0 +1,69 @@
<!-- 섹션 타이틀: 배경 헤더 위 영문+한글 타이틀 오버레이 -->
<!--
📋 section-title
─────────────────
용도: 자세히보기 페이지 상단, 배경 이미지 위에 타이틀 표시
슬롯: title_ko (필수), title_en (선택), breadcrumb (선택), bg_image (선택)
Figma 원본: 공통 > section_title + bg 컴포넌트
-->
<div class="block-section-title">
{% if bg_image %}
<img class="st-bg" src="{{ bg_image }}" alt="">
{% else %}
<div class="st-bg st-bg-default"></div>
{% endif %}
{% if breadcrumb %}
<div class="st-breadcrumb">{{ breadcrumb }}</div>
{% endif %}
<div class="st-text">
{% if title_en %}<div class="st-en">{{ title_en }}</div>{% endif %}
<div class="st-ko">{{ title_ko }}</div>
</div>
</div>
<style>
.block-section-title {
position: relative;
width: 100%;
height: 500px;
overflow: hidden;
}
.st-bg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
z-index: 1;
}
.st-bg-default {
background: linear-gradient(135deg, #1e3a5f 0%, #2563eb 50%, #4dc4ff 100%);
}
.st-breadcrumb {
position: absolute;
top: 18px;
left: 89px;
z-index: 5;
font-size: 13px;
color: rgba(255,255,255,0.7);
}
.st-text {
position: absolute;
bottom: 40px;
left: 89px;
z-index: 5;
}
.st-en {
font-size: 15px;
font-weight: var(--weight-normal, 400);
color: #ffffff;
opacity: 0.85;
margin-bottom: 4px;
}
.st-ko {
font-size: 35px;
font-weight: var(--weight-bold, 700);
color: #ffffff;
line-height: 1.3;
}
</style>

View File

@@ -0,0 +1,38 @@
<!-- 꼭지 제목+설명: 좌측 질문/소제목 + 우측 설명 -->
<!--
📋 topic-header
─────────────────
용도: 각 꼭지의 시작부, 좌측에 파란 굵은 제목 + 우측에 본문 설명
슬롯: title (필수), description (필수)
비율: 좌 240px : 우 나머지
Figma 원본: sub_제목,내용 (742x68~78)
-->
<div class="block-topic-header">
<div class="th-title">{{ title }}</div>
<div class="th-desc">{{ description }}</div>
</div>
<style>
.block-topic-header {
display: flex;
gap: 20px;
padding: 12px 0;
}
.th-title {
width: 240px;
flex-shrink: 0;
font-size: 24px;
font-weight: var(--weight-bold, 700);
color: var(--color-accent-deep, #004cbe);
line-height: 1.4;
word-break: keep-all;
}
.th-desc {
flex: 1;
font-size: 16px;
font-weight: var(--weight-normal, 400);
color: var(--color-text, #000000);
line-height: 1.7;
word-break: keep-all;
}
</style>

View File

@@ -0,0 +1,74 @@
<!-- 3단 비교 카드: 각각 다른 색상 헤더 + 아이콘/이미지 + 불릿 -->
<!--
📋 card-compare-3col
─────────────────
용도: 3개 카테고리 비교 (예: 상용SW / 3rd Party / 전문SW)
슬롯: cards[] (각 카드에 title, subtitle, color, image, bullets[])
Figma 원본: 2-2_03 "상용 / 3rd Party(범용) / 전문·전용 S/W"
-->
<div class="block-compare-3" style="--cc-count: {{ cards|length }}">
{% for card in cards %}
<div class="cc-card">
<div class="cc-header" style="background: {{ card.color | default('#2563eb') }}">
<div class="cc-title">{{ card.title }}</div>
{% if card.subtitle %}<div class="cc-sub">{{ card.subtitle }}</div>{% endif %}
</div>
{% if card.image %}
<img class="cc-img" src="{{ card.image }}" alt="{{ card.title }}">
{% endif %}
{% if card.bullets %}
<ul class="cc-list">
{% for item in card.bullets %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endfor %}
</div>
<style>
.block-compare-3 {
display: grid;
grid-template-columns: repeat(var(--cc-count, 3), 1fr);
gap: 14px;
}
.cc-card {
border: 1px solid #e2e8f0;
border-radius: var(--radius);
overflow: hidden;
background: #ffffff;
}
.cc-header {
padding: 12px 16px;
text-align: center;
color: #ffffff;
}
.cc-title {
font-size: 15px;
font-weight: 800;
}
.cc-sub {
font-size: 11px;
opacity: 0.85;
margin-top: 2px;
}
.cc-img {
width: 100%;
height: 120px;
object-fit: contain;
background: #f8fafc;
padding: 8px;
}
.cc-list {
list-style: disc;
padding: 12px 16px 14px 30px;
font-size: 13px;
color: #334155;
line-height: 1.7;
}
.cc-list li {
margin-bottom: 3px;
white-space: pre-line;
}
</style>

View File

@@ -0,0 +1,77 @@
<!-- 다크 오버레이 카드: 배경 이미지 + 흰 텍스트 오버레이 -->
<!--
📋 card-dark-overlay
─────────────────
용도: 키워드+짧은 설명을 시각적으로 강조. 이미지 위에 다크 오버레이 + 흰 텍스트.
슬롯: cards[] (각 카드에 image, title, description)
Figma 원본: 2-2_01 > 아이콘 카드 5열 (협업지원, 오류감소, 생산성향상 등)
-->
<div class="block-card-dark" style="--cd-count: {{ cards|length }}">
{% for card in cards %}
<div class="cd-card">
{% if card.image %}
<img class="cd-bg" src="{{ card.image }}" alt="">
{% else %}
<div class="cd-bg cd-bg-default"></div>
{% endif %}
<div class="cd-overlay">
<div class="cd-title">{{ card.title }}</div>
{% if card.description %}<div class="cd-desc">{{ card.description }}</div>{% endif %}
</div>
</div>
{% endfor %}
</div>
<style>
.block-card-dark {
display: grid;
grid-template-columns: repeat(var(--cd-count, 3), 1fr);
gap: 12px;
}
.cd-card {
position: relative;
border-radius: var(--radius);
overflow: hidden;
height: 200px;
}
.cd-bg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
z-index: 1;
}
.cd-bg-default {
background: linear-gradient(135deg, #1e3a5f 0%, #2c5282 100%);
}
.cd-overlay {
position: absolute;
inset: 0;
z-index: 2;
background: linear-gradient(180deg, rgba(0,0,0,0.15) 0%, rgba(0,0,0,0.55) 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 16px;
text-align: center;
color: #ffffff;
}
.cd-title {
font-size: 18px;
font-weight: 800;
line-height: 1.3;
margin-bottom: 6px;
text-shadow: 0 1px 4px rgba(0,0,0,0.3);
}
.cd-desc {
white-space: pre-line;
font-size: 12px;
font-weight: 400;
line-height: 1.5;
opacity: 0.9;
text-shadow: 0 1px 3px rgba(0,0,0,0.3);
white-space: pre-line;
}
</style>

View File

@@ -0,0 +1,48 @@
<!-- 아이콘 설명 카드: 아이콘 + 제목 + 설명 (2~4열) -->
<!--
📋 card-icon-desc
─────────────────
용도: 기능/특성/장점을 아이콘과 함께 나열. 시각적으로 분류.
슬롯: cards[] (각 카드에 icon, title, description)
Figma 원본: 2-3_01 아이콘 3열 설명
-->
<div class="block-card-icon" style="--ci-count: {{ cards|length }}">
{% for card in cards %}
<div class="cid-card">
{% if card.icon %}<div class="cid-icon">{{ card.icon }}</div>{% endif %}
<div class="cid-title">{{ card.title }}</div>
{% if card.description %}<div class="cid-desc">{{ card.description }}</div>{% endif %}
</div>
{% endfor %}
</div>
<style>
.block-card-icon {
display: grid;
grid-template-columns: repeat(var(--ci-count, 3), 1fr);
gap: 16px;
}
.cid-card {
text-align: center;
padding: 20px 16px;
background: #f8fafc;
border-radius: 8px;
border: 1px solid #e2e8f0;
}
.cid-icon {
font-size: 2.5rem;
margin-bottom: 10px;
}
.cid-title {
font-size: 15px;
font-weight: 700;
color: #1e293b;
margin-bottom: 6px;
}
.cid-desc {
font-size: 13px;
color: #475569;
line-height: 1.7;
white-space: pre-line;
}
</style>

View File

@@ -0,0 +1,96 @@
<!-- 이미지 카드: 상단 이미지 + 하단 텍스트 (2~4열) -->
<!--
📋 card-image
─────────────────
용도: 단계별 설명, 카테고리별 설명 (이미지가 핵심인 카드)
슬롯: cards[] 배열 (각 카드에 image, title, title_en, items[])
Figma 원본: 2-1_02 > Group 1171281594 (카드 3열)
-->
<div class="block-card-image" style="--ci-count: {{ cards|length }}">
{% for card in cards %}
<div class="ci-card">
{% if card.image %}
<img class="ci-img" src="{{ card.image }}" alt="{{ card.title }}">
{% endif %}
<div class="ci-body">
<div class="ci-title" style="color: {{ card.color | default('var(--color-accent, #006aff)') }}">{{ card.title }}</div>
{% if card.title_en %}<div class="ci-title-en">{{ card.title_en }}</div>{% endif %}
<div class="ci-divider"></div>
<ul class="ci-list">
{% for item in card.bullets %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% if card.source %}<div class="ci-source">{{ card.source }}</div>{% endif %}
</div>
</div>
{% endfor %}
</div>
<style>
.block-card-image {
display: grid;
grid-template-columns: repeat(var(--ci-count, 3), 1fr);
gap: 16px;
}
.ci-card {
background: var(--color-bg, #ffffff);
border-radius: var(--radius, 8px);
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
overflow: hidden;
display: flex;
flex-direction: column;
}
.ci-img {
width: 100%;
height: 160px;
object-fit: contain;
background: #f8f9fb;
padding: 10px;
}
.ci-body {
padding: 16px;
flex: 1;
display: flex;
flex-direction: column;
}
.ci-title {
font-size: 14px;
font-weight: var(--weight-bold, 700);
text-decoration: underline;
text-underline-offset: 3px;
margin-bottom: 2px;
}
.ci-title-en {
font-size: 12px;
font-weight: var(--weight-normal, 400);
color: var(--color-text-secondary, #666);
margin-bottom: 10px;
}
.ci-divider {
width: 100%;
height: 1px;
background: #000;
margin-bottom: 10px;
}
.ci-list {
white-space: pre-line;
list-style: disc;
padding-left: 18px;
font-size: 13px;
line-height: 1.7;
color: var(--color-text, #000);
flex: 1;
}
.ci-list li {
margin-bottom: 3px;
}
.ci-source {
font-size: 11px;
color: var(--color-text-light, #94a3b8);
font-style: italic;
margin-top: 8px;
border-top: 1px solid var(--color-border, #e2e8f0);
padding-top: 6px;
}
</style>

View File

@@ -0,0 +1,62 @@
<!-- 원형 이미지 카드: 원형 이미지 + 하단 제목/설명 -->
<!--
📋 card-image-round
─────────────────
용도: 포트폴리오형, 팀 소개, 가치/비전 표현 (원형 이미지가 핵심)
슬롯: cards[] (각 카드에 image, title, description)
Figma 원본: 1장_가치 하단 3열 원형 이미지 + 설명
-->
<div class="block-card-round" style="--cr-count: {{ cards|length }}">
{% for card in cards %}
<div class="cr-card">
{% if card.image %}
<div class="cr-img-wrap">
<img src="{{ card.image }}" alt="{{ card.title | default('') }}">
</div>
{% endif %}
<div class="cr-title">{{ card.title }}</div>
{% if card.description %}<div class="cr-desc">{{ card.description }}</div>{% endif %}
</div>
{% endfor %}
</div>
<style>
.block-card-round {
display: grid;
grid-template-columns: repeat(var(--cr-count, 3), 1fr);
gap: 24px;
text-align: center;
}
.cr-card {
display: flex;
flex-direction: column;
align-items: center;
}
.cr-img-wrap {
width: 140px;
height: 140px;
border-radius: 50%;
overflow: hidden;
border: 3px solid #e2e8f0;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
margin-bottom: 12px;
}
.cr-img-wrap img {
width: 100%;
height: 100%;
object-fit: cover;
}
.cr-title {
font-size: 15px;
font-weight: 700;
color: #1e293b;
margin-bottom: 4px;
}
.cr-desc {
font-size: 13px;
color: #475569;
line-height: 1.6;
white-space: pre-line;
max-width: 200px;
}
</style>

View File

@@ -0,0 +1,60 @@
<!-- 번호 카드: 순서 번호 + 제목 + 설명 (세로 나열) -->
<!--
📋 card-numbered
─────────────────
용도: 순서가 있는 항목 나열 (1. 2. 3.), 실행 조건, 요구사항
슬롯: items[] (각 항목에 title, description)
card-icon-desc와 다른 점: 아이콘 대신 순서 번호, 세로 나열
-->
<div class="block-card-num">
{% for item in items %}
<div class="cn-item">
<div class="cn-number" style="background: {{ item.color | default('#2563eb') }}">{{ loop.index }}</div>
<div class="cn-body">
<div class="cn-title">{{ item.title }}</div>
{% if item.description %}<div class="cn-desc">{{ item.description }}</div>{% endif %}
</div>
</div>
{% endfor %}
</div>
<style>
.block-card-num {
display: flex;
flex-direction: column;
gap: 10px;
}
.cn-item {
display: flex;
gap: 14px;
align-items: flex-start;
padding: 12px 16px;
background: #f8fafc;
border-radius: 8px;
border: 1px solid #e2e8f0;
}
.cn-number {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
font-size: 14px;
font-weight: 800;
flex-shrink: 0;
}
.cn-title {
font-size: 15px;
font-weight: 700;
color: #1e293b;
margin-bottom: 2px;
}
.cn-desc {
font-size: 13px;
color: #475569;
line-height: 1.7;
white-space: pre-line;
}
</style>

View File

@@ -0,0 +1,57 @@
<!-- 통계 숫자 카드: 큰 숫자 + 라벨 + 설명 -->
<!--
📋 card-stat-number
─────────────────
용도: KPI, 성과 수치, 목표 달성률, 비용 절감율 등 핵심 지표 강조
슬롯: stats[] (각 항목에 number, unit, label, description, color)
Figma 참고: 건설 정책 수치 (30% 절감, 40% 감소 등)
-->
<div class="block-stat" style="--st-count: {{ stats|length }}">
{% for stat in stats %}
<div class="st-card">
<div class="st-number" style="color: {{ stat.color | default('#2563eb') }}">
{{ stat.number }}<span class="st-unit">{{ stat.unit | default('') }}</span>
</div>
<div class="st-label">{{ stat.label }}</div>
{% if stat.description %}<div class="st-desc">{{ stat.description }}</div>{% endif %}
</div>
{% endfor %}
</div>
<style>
.block-stat {
display: grid;
grid-template-columns: repeat(var(--st-count, 3), 1fr);
gap: 16px;
}
.st-card {
text-align: center;
padding: 20px 12px;
background: #f8fafc;
border-radius: var(--radius);
border: 1px solid #e2e8f0;
}
.st-number {
font-size: 36px;
font-weight: 900;
line-height: 1.2;
}
.st-unit {
font-size: 18px;
font-weight: 700;
margin-left: 2px;
}
.st-label {
font-size: 14px;
font-weight: 700;
color: #1e293b;
margin-top: 6px;
}
.st-desc {
font-size: 12px;
color: #64748b;
margin-top: 4px;
line-height: 1.5;
white-space: pre-line;
}
</style>

View File

@@ -0,0 +1,93 @@
<!-- 세로 단계 카드: 좌측 단계 마커 + 우측 이미지/텍스트 -->
<!--
📋 card-step-vertical
─────────────────
용도: 생애주기 단계별 설명, 시간순 프로세스 (설계→시공→운영→유지관리)
슬롯: steps[] (각 단계에 phase, title, description, image, color)
Figma 원본: 2-3_04 "건설 생애주기와 정보모델 연계" (설계단계/시공단계/운영관리/유지관리)
-->
<div class="block-step-v">
{% for step in steps %}
<div class="sv-step">
<div class="sv-marker" style="background: {{ step.color | default('#2563eb') }}">
<div class="sv-phase">{{ step.phase }}</div>
</div>
<div class="sv-content">
<div class="sv-title">{{ step.title }}</div>
{% if step.image %}
<img class="sv-img" src="{{ step.image }}" alt="{{ step.title }}">
{% endif %}
{% if step.description %}<div class="sv-desc">{{ step.description }}</div>{% endif %}
</div>
</div>
{% if not loop.last %}
<div class="sv-connector">
<div class="sv-line" style="background: {{ step.color | default('#2563eb') }}"></div>
</div>
{% endif %}
{% endfor %}
</div>
<style>
.block-step-v {
display: flex;
flex-direction: column;
gap: 0;
}
.sv-step {
display: flex;
gap: 16px;
align-items: flex-start;
}
.sv-marker {
width: 100px;
flex-shrink: 0;
border-radius: 8px;
padding: 10px 12px;
text-align: center;
color: #ffffff;
}
.sv-phase {
font-size: 13px;
font-weight: 700;
white-space: nowrap;
}
.sv-content {
flex: 1;
background: #f8fafc;
border-radius: 8px;
padding: 14px;
border: 1px solid #e2e8f0;
}
.sv-title {
font-size: 15px;
font-weight: 700;
color: #1e293b;
margin-bottom: 8px;
}
.sv-img {
width: 100%;
max-height: 150px;
object-fit: cover;
border-radius: 6px;
margin-bottom: 8px;
}
.sv-desc {
font-size: 13px;
color: #475569;
line-height: 1.7;
white-space: pre-line;
}
.sv-connector {
display: flex;
justify-content: 50px;
padding-left: 48px;
height: 20px;
}
.sv-line {
width: 3px;
height: 100%;
border-radius: 2px;
opacity: 0.4;
}
</style>

View File

@@ -0,0 +1,63 @@
<!-- 태그 카드: 상단 태그 라벨 + 이미지 + 설명 -->
<!--
📋 card-tag-image
─────────────────
용도: 카테고리별 분류 (제조/건축/토목 등), 태그로 구분되는 항목
슬롯: cards[] (각 카드에 tag, tag_color, image, title, description)
Figma 원본: 2-3_01 "산업별 특성과 현장의 모습" (제조, 건축, 인프라/토목)
-->
<div class="block-card-tag" style="--ct-count: {{ cards|length }}">
{% for card in cards %}
<div class="ct-card">
<div class="ct-tag" style="background: {{ card.tag_color | default('#2563eb') }}">{{ card.tag }}</div>
{% if card.image %}
<img class="ct-img" src="{{ card.image }}" alt="{{ card.title | default('') }}">
{% endif %}
{% if card.title %}<div class="ct-title">{{ card.title }}</div>{% endif %}
{% if card.description %}<div class="ct-desc">{{ card.description }}</div>{% endif %}
</div>
{% endfor %}
</div>
<style>
.block-card-tag {
display: grid;
grid-template-columns: repeat(var(--ct-count, 3), 1fr);
gap: 16px;
}
.ct-card {
background: #ffffff;
border: 1px solid #e2e8f0;
border-radius: 8px;
overflow: hidden;
display: flex;
flex-direction: column;
}
.ct-tag {
display: inline-block;
padding: 4px 14px;
font-size: 12px;
font-weight: 700;
color: #ffffff;
border-radius: 0 0 8px 0;
align-self: flex-start;
}
.ct-img {
width: 100%;
height: 140px;
object-fit: cover;
}
.ct-title {
font-size: 14px;
font-weight: 700;
color: #1e293b;
padding: 10px 14px 4px;
}
.ct-desc {
font-size: 13px;
color: #475569;
line-height: 1.7;
padding: 0 14px 14px;
white-space: pre-line;
}
</style>

View File

@@ -0,0 +1,66 @@
<!-- 카드 그리드 블록: 2~4열 카드 배열 -->
<div class="block-card-grid" style="--card-count: {{ cards|length }}">
{% for card in cards %}
<div class="card" style="border-top-color: {{ card.color | default('var(--color-accent)') }}">
{% if card.icon %}<div class="card-icon">{{ card.icon }}</div>{% endif %}
<div class="card-title">{{ card.title }}</div>
{% if card.category %}<span class="card-category">{{ card.category }}</span>{% endif %}
<div class="card-description">{{ card.description }}</div>
{% if card.source %}<div class="card-source">{{ card.source }}</div>{% endif %}
</div>
{% endfor %}
</div>
<style>
.block-card-grid {
display: grid;
grid-template-columns: repeat(var(--card-count, 3), 1fr);
gap: var(--spacing-inner);
height: 100%;
}
.card {
background: var(--color-bg);
border: var(--border-width) solid var(--color-border);
border-top: var(--accent-border) solid var(--color-accent);
border-radius: var(--radius);
padding: var(--spacing-inner);
display: flex;
flex-direction: column;
}
.card-icon {
font-size: 1.5rem;
margin-bottom: var(--spacing-small);
}
.card-title {
font-size: var(--font-subtitle);
font-weight: var(--weight-bold);
color: var(--color-primary);
margin-bottom: 4px;
}
.card-category {
font-size: var(--font-small);
font-weight: var(--weight-medium);
color: var(--color-accent);
background: #dbeafe;
padding: 2px 8px;
border-radius: var(--radius);
display: inline-block;
margin-bottom: var(--spacing-small);
width: fit-content;
}
.card-description {
white-space: pre-line;
font-size: var(--font-body);
color: var(--color-text);
line-height: var(--line-height-ko);
flex: 1;
}
.card-source {
font-size: var(--font-small);
color: var(--color-text-light);
font-style: italic;
margin-top: var(--spacing-small);
border-top: var(--border-width) solid var(--color-border);
padding-top: var(--spacing-small);
}
</style>

View File

@@ -0,0 +1,33 @@
<!-- 그라데이션 배너 바: 전체 너비 파란 그라데이션 + 중앙 텍스트 -->
<!--
📋 banner-gradient
─────────────────
용도: 섹션 구분, 핵심 선언, 강조 문구를 전체 너비 배너로
슬롯: text (필수), sub_text (선택)
Figma 원본: 2-2_01 하단, 2-2_03 분류 바
-->
<div class="block-banner-grad">
<div class="bg-text">{{ text }}</div>
{% if sub_text %}<div class="bg-sub">{{ sub_text }}</div>{% endif %}
</div>
<style>
.block-banner-grad {
background: linear-gradient(135deg, #006aff 0%, #00aaff 100%);
border-radius: 8px;
padding: 16px 30px;
text-align: center;
color: #ffffff;
}
.bg-text {
font-size: 16px;
font-weight: 700;
line-height: 1.5;
}
.bg-sub {
font-size: 12px;
font-weight: 400;
opacity: 0.85;
margin-top: 4px;
}
</style>

View File

@@ -0,0 +1,55 @@
<!-- 솔루션 콜아웃: 강조 배경 + 아이콘 + 제목 + 설명 -->
<!--
📋 callout-solution
─────────────────
용도: 핵심 해결책/솔루션/방향성 강조. 눈에 띄는 콜아웃 박스.
슬롯: icon (선택), title (필수), description (필수), source (선택)
Figma 원본: 2-3_05 "Solution 제시 포인트"
-->
<div class="block-callout-sol">
{% if icon %}<div class="cs-icon">{{ icon }}</div>{% endif %}
<div class="cs-body">
<div class="cs-title">{{ title }}</div>
<div class="cs-desc">{{ description }}</div>
{% if source %}<div class="cs-source">{{ source }}</div>{% endif %}
</div>
</div>
<style>
.block-callout-sol {
background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
border: 2px solid #93c5fd;
border-radius: var(--radius);
padding: 20px 24px;
display: flex;
gap: 16px;
align-items: flex-start;
}
.cs-icon {
font-size: 2rem;
flex-shrink: 0;
margin-top: 2px;
}
.cs-body {
flex: 1;
}
.cs-title {
font-size: 17px;
font-weight: 800;
color: #1e40af;
margin-bottom: 6px;
}
.cs-desc {
font-size: 14px;
color: #334155;
line-height: 1.7;
white-space: pre-line;
word-break: keep-all;
}
.cs-source {
font-size: 11px;
color: #64748b;
font-style: italic;
margin-top: 8px;
}
</style>

View File

@@ -0,0 +1,45 @@
<!-- 경고 콜아웃: 주의/경고/문제점 강조 -->
<!--
📋 callout-warning
─────────────────
용도: 문제점 지적, 주의사항, 잘못된 접근 경고
슬롯: title (필수), description (필수), icon (선택)
callout-solution과 다른 점: 경고 톤 (빨간/주황), 문제 지적용
-->
<div class="block-callout-warn">
{% if icon %}<div class="cw-icon">{{ icon }}</div>{% endif %}
<div class="cw-body">
<div class="cw-title">{{ title }}</div>
<div class="cw-desc">{{ description }}</div>
</div>
</div>
<style>
.block-callout-warn {
background: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%);
border: 2px solid #fca5a5;
border-radius: var(--radius);
padding: 20px 24px;
display: flex;
gap: 16px;
align-items: flex-start;
}
.cw-icon {
font-size: 2rem;
flex-shrink: 0;
}
.cw-body { flex: 1; }
.cw-title {
font-size: 17px;
font-weight: 800;
color: #991b1b;
margin-bottom: 6px;
}
.cw-desc {
font-size: 14px;
color: #7f1d1d;
line-height: 1.7;
white-space: pre-line;
word-break: keep-all;
}
</style>

View File

@@ -0,0 +1,52 @@
<!-- 비교 블록: 2단 병렬 레이아웃 -->
<div class="block-comparison">
<div class="comparison-left">
<div class="comparison-header comparison-header--left">{{ left_title }}</div>
{% if left_subtitle %}<div class="comparison-subtitle">{{ left_subtitle }}</div>{% endif %}
<div class="comparison-content">{{ left_content }}</div>
</div>
<div class="comparison-divider"></div>
<div class="comparison-right">
<div class="comparison-header comparison-header--right">{{ right_title }}</div>
{% if right_subtitle %}<div class="comparison-subtitle">{{ right_subtitle }}</div>{% endif %}
<div class="comparison-content">{{ right_content }}</div>
</div>
</div>
<style>
.block-comparison {
display: grid;
grid-template-columns: 1fr auto 1fr;
gap: var(--spacing-inner);
height: 100%;
}
.comparison-divider {
width: 1px;
background: var(--color-border);
}
.comparison-header {
font-size: var(--font-subtitle);
font-weight: var(--weight-bold);
padding-bottom: var(--spacing-small);
margin-bottom: var(--spacing-small);
border-bottom: var(--accent-border) solid;
}
.comparison-header--left {
border-color: var(--color-accent);
color: var(--color-accent);
}
.comparison-header--right {
border-color: var(--color-danger);
color: var(--color-danger);
}
.comparison-subtitle {
font-size: var(--font-caption);
color: var(--color-text-secondary);
margin-bottom: var(--spacing-small);
}
.comparison-content {
white-space: pre-line;
font-size: var(--font-body);
line-height: var(--line-height-ko);
}
</style>

View File

@@ -0,0 +1,38 @@
<!-- 결론 바: Figma 톤에 맞춘 하단 핵심 메시지 -->
<!--
📋 conclusion-bar
─────────────────
용도: 슬라이드/페이지 하단 핵심 한 줄 요약
슬롯: conclusion_text (필수), label (선택)
Figma 톤: 밝은 회색 배경 + 좌측 파란 액센트 라인 + 진한 텍스트
-->
<div class="block-conclusion-figma">
{% if label %}<div class="cf-label">{{ label }}</div>{% endif %}
<div class="cf-text">{{ conclusion_text }}</div>
</div>
<style>
.block-conclusion-figma {
background: #f4f6f9;
border-left: 4px solid #006aff;
border-radius: 0 8px 8px 0;
padding: 18px 28px;
display: flex;
flex-direction: column;
gap: 4px;
}
.cf-label {
font-size: 11px;
font-weight: 600;
color: #006aff;
text-transform: uppercase;
letter-spacing: 1px;
}
.cf-text {
font-size: 15px;
font-weight: 700;
color: #1e293b;
line-height: 1.6;
word-break: keep-all;
}
</style>

View File

@@ -0,0 +1,48 @@
<!-- 다크 배경 불릿 리스트: 짙은 배경 + 흰 텍스트 불릿 목록 -->
<!--
📋 dark-bullet-list
─────────────────
용도: 핵심 포인트를 짙은 배경 위에 강조. 시각적 무게감.
슬롯: title (선택), bullets[] (필수)
Figma 원본: 2-2_01 하단, 2-3_01 하단 다크 섹션
-->
<div class="block-dark-bullets">
{% if title %}<div class="db-title">{{ title }}</div>{% endif %}
<ul class="db-list">
{% for item in bullets %}
<li>{{ item }}</li>
{% endfor %}
</ul>
</div>
<style>
.block-dark-bullets {
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
border-radius: 8px;
padding: 20px 28px;
color: #ffffff;
}
.db-title {
font-size: 16px;
font-weight: 700;
margin-bottom: 12px;
color: #93c5fd;
}
.db-list {
list-style: none;
padding: 0;
}
.db-list li {
font-size: 14px;
line-height: 1.8;
padding-left: 16px;
position: relative;
margin-bottom: 4px;
}
.db-list li::before {
content: '•';
position: absolute;
left: 0;
color: #60a5fa;
}
</style>

View File

@@ -0,0 +1,67 @@
<!-- 자세히보기: HTML 네이티브 <details>/<summary> 접기/펼치기 -->
<!--
📋 details-block
─────────────────
용도: 상세 데이터(비교표, 스펙 등)를 접어서 표시. 클릭하면 펼침.
슬롯: summary_text (필수), detail_content (필수), label (선택)
원칙: 본문 흐름을 끊지 않으면서 상세 정보 제공
인쇄: window.onbeforeprint에서 자동 펼침 (slide-base.html의 JS)
-->
<details class="block-details">
<summary class="dt-summary">
{% if label %}<span class="dt-label">{{ label }}</span>{% endif %}
<span class="dt-summary-text">{{ summary_text }}</span>
</summary>
<div class="dt-content">{{ detail_content }}</div>
</details>
<style>
.block-details {
background: var(--color-bg-subtle);
border: var(--border-width) solid var(--color-border);
border-left: var(--accent-border) solid var(--color-accent);
border-radius: 0 var(--radius) var(--radius) 0;
overflow: hidden;
}
.dt-summary {
padding: var(--spacing-inner) var(--spacing-block);
cursor: pointer;
display: flex;
align-items: center;
gap: var(--spacing-small);
font-size: var(--font-body);
font-weight: var(--weight-medium);
color: var(--color-text);
line-height: var(--line-height-ko);
list-style: none;
}
.dt-summary::-webkit-details-marker {
display: none;
}
.dt-summary::before {
content: "▶";
font-size: var(--font-caption);
color: var(--color-accent);
transition: none;
}
details[open] .dt-summary::before {
content: "▼";
}
.dt-label {
font-size: var(--font-caption);
font-weight: var(--weight-bold);
color: var(--color-accent);
white-space: nowrap;
}
.dt-summary-text {
word-break: keep-all;
}
.dt-content {
padding: 0 var(--spacing-block) var(--spacing-inner) var(--spacing-block);
font-size: var(--font-body);
color: var(--color-text);
line-height: var(--line-height-ko);
word-break: keep-all;
border-top: var(--border-width) solid var(--color-border);
}
</style>

View File

@@ -0,0 +1,32 @@
<!-- 텍스트 구분선: 좌우 선 + 중앙 텍스트 -->
<!--
📋 divider-text
─────────────────
용도: 섹션 구분, 주제 전환, 시각적 휴식점
슬롯: text (필수)
-->
<div class="block-divider-text">
<div class="dt-line"></div>
<div class="dt-text">{{ text }}</div>
<div class="dt-line"></div>
</div>
<style>
.block-divider-text {
display: flex;
align-items: center;
gap: 16px;
padding: 8px 0;
}
.dt-line {
flex: 1;
height: 1px;
background: #cbd5e1;
}
.dt-text {
font-size: 13px;
font-weight: 600;
color: #64748b;
white-space: nowrap;
}
</style>

View File

@@ -0,0 +1,31 @@
<!-- 강조 스트립: 3구간 색상 분류 바 -->
<!--
📋 highlight-strip
─────────────────
용도: 카테고리별 색상 분류 (예: 상용/범용/전문), 비교 카드 상단 헤더
슬롯: segments[] (각 구간에 label, color)
Figma 원본: 2-2_03 "상용 | 3rd Party(범용) | 전문·전용 S/W" 색상 바
-->
<div class="block-strip">
{% for seg in segments %}
<div class="strip-seg" style="background: {{ seg.color | default('#2563eb') }}; flex: {{ seg.flex | default(1) }}">
{{ seg.label }}
</div>
{% endfor %}
</div>
<style>
.block-strip {
display: flex;
border-radius: 6px;
overflow: hidden;
}
.strip-seg {
padding: 10px 16px;
color: #ffffff;
font-size: 14px;
font-weight: 700;
text-align: center;
white-space: nowrap;
}
</style>

View File

@@ -0,0 +1,59 @@
<!-- 큰따옴표 장식 인용: ❝❞ 큰따옴표 + 인용 텍스트 -->
<!--
📋 quote-big-mark
─────────────────
용도: 문제 제기, 핵심 발언, 임팩트 있는 인용
슬롯: quote_text (필수), source (선택)
Figma 원본: DX와 BIM 슬라이드 상단 인용 박스 (큰따옴표 장식)
-->
<div class="block-quote-big">
<div class="qb-mark qb-open"></div>
<div class="qb-content">
<div class="qb-text">{{ quote_text }}</div>
{% if source %}<div class="qb-source">— {{ source }}</div>{% endif %}
</div>
<div class="qb-mark qb-close"></div>
</div>
<style>
.block-quote-big {
background: #f8fafc;
border-radius: var(--radius);
padding: 24px 28px;
position: relative;
border: 1px solid #e2e8f0;
}
.qb-mark {
font-size: 3rem;
color: #cbd5e1;
font-weight: 900;
line-height: 1;
position: absolute;
}
.qb-open {
top: 8px;
left: 12px;
}
.qb-close {
bottom: -8px;
right: 16px;
}
.qb-content {
padding: 10px 30px 0;
}
.qb-text {
font-size: 15px;
font-weight: 500;
color: #1e293b;
line-height: 1.8;
word-break: keep-all;
white-space: pre-line;
}
.qb-source {
font-size: 12px;
color: #64748b;
font-style: italic;
margin-top: 10px;
text-align: right;
}
</style>

View File

@@ -0,0 +1,30 @@
<!-- 강조 인용 블록: 문제 제기, 핵심 메시지 -->
<div class="block-quote">
<div class="quote-text">{{ quote_text }}</div>
{% if source %}<div class="quote-source">{{ source }}</div>{% endif %}
</div>
<style>
.block-quote {
background: var(--color-bg-subtle);
border-left: var(--accent-border) solid var(--color-danger);
padding: var(--spacing-inner) var(--spacing-block);
border-radius: 0 var(--radius) var(--radius) 0;
display: flex;
flex-direction: column;
justify-content: center;
}
.quote-text {
white-space: pre-line;
font-size: var(--font-body);
color: var(--color-text);
line-height: var(--line-height-ko);
font-weight: var(--weight-medium);
}
.quote-source {
font-size: var(--font-caption);
color: var(--color-text-light);
font-style: italic;
margin-top: var(--spacing-small);
}
</style>

View File

@@ -0,0 +1,37 @@
<!-- 질문형 강조 박스: 큰 질문 텍스트 + 부연 설명 -->
<!--
📋 quote-question
─────────────────
용도: 독자에게 질문 던지기, 문제 인식 유도, 전환점 강조
슬롯: question (필수), description (선택)
Figma 원본: 2-3_05 "지금의 방식으로도 가능할까?"
-->
<div class="block-quote-q">
<div class="qq-question">{{ question }}</div>
{% if description %}<div class="qq-desc">{{ description }}</div>{% endif %}
</div>
<style>
.block-quote-q {
background: linear-gradient(135deg, #f0f7ff 0%, #e8f1fb 100%);
border: 2px solid #b8d4f0;
border-radius: var(--radius);
padding: 28px 36px;
text-align: center;
}
.qq-question {
font-size: 22px;
font-weight: 800;
color: #1e3a5f;
line-height: 1.5;
word-break: keep-all;
}
.qq-desc {
white-space: pre-line;
font-size: 14px;
color: #4a6b8a;
margin-top: 10px;
line-height: 1.7;
word-break: keep-all;
}
</style>

View File

@@ -0,0 +1,39 @@
<!-- 탭 라벨 행: 가로로 나열된 탭 버튼 형태 -->
<!--
📋 tab-label-row
─────────────────
용도: 카테고리 전환, 분류 표시, 선택된 항목 강조
슬롯: tabs[] (각 탭에 label, active, color)
Figma 원본: 2-3_02 상단 "건축과 인프라의 건설프로세스 특성" 탭, 2-2_01 탭
-->
<div class="block-tab-row">
{% for tab in tabs %}
<div class="tr-tab {% if tab.active %}tr-active{% endif %}" style="{% if tab.active %}background: {{ tab.color | default('#2563eb') }}{% endif %}">
{{ tab.label }}
</div>
{% endfor %}
</div>
<style>
.block-tab-row {
display: flex;
gap: 4px;
background: #f1f5f9;
border-radius: 8px;
padding: 4px;
}
.tr-tab {
flex: 1;
padding: 10px 16px;
text-align: center;
font-size: 14px;
font-weight: 600;
color: #64748b;
border-radius: 6px;
cursor: default;
}
.tr-active {
color: #ffffff;
font-weight: 700;
}
</style>

View File

@@ -0,0 +1,32 @@
<!-- 섹션 헤더 바: 파란 배경 + 흰 텍스트 제목 -->
<!--
📋 section-header-bar
─────────────────
용도: 섹션 시작부 강조 헤더. 전체 너비 파란 바 + 흰 대제목.
슬롯: title (필수), subtitle (선택)
Figma 원본: 2-2_01 "Eng. S/W의 구성", "Eng. S/W의 특성"
-->
<div class="block-section-bar">
<div class="sb-title">{{ title }}</div>
{% if subtitle %}<div class="sb-sub">{{ subtitle }}</div>{% endif %}
</div>
<style>
.block-section-bar {
background: linear-gradient(135deg, #0d47a1 0%, #1565c0 100%);
border-radius: 6px;
padding: 14px 24px;
text-align: center;
}
.sb-title {
font-size: 18px;
font-weight: 800;
color: #ffffff;
line-height: 1.4;
}
.sb-sub {
font-size: 12px;
color: rgba(255,255,255,0.8);
margin-top: 4px;
}
</style>

View File

@@ -0,0 +1,69 @@
<!-- 섹션 타이틀: 배경 헤더 위 영문+한글 타이틀 오버레이 -->
<!--
📋 section-title
─────────────────
용도: 자세히보기 페이지 상단, 배경 이미지 위에 타이틀 표시
슬롯: title_ko (필수), title_en (선택), breadcrumb (선택), bg_image (선택)
Figma 원본: 공통 > section_title + bg 컴포넌트
-->
<div class="block-section-title">
{% if bg_image %}
<img class="st-bg" src="{{ bg_image }}" alt="">
{% else %}
<div class="st-bg st-bg-default"></div>
{% endif %}
{% if breadcrumb %}
<div class="st-breadcrumb">{{ breadcrumb }}</div>
{% endif %}
<div class="st-text">
{% if title_en %}<div class="st-en">{{ title_en }}</div>{% endif %}
<div class="st-ko">{{ title_ko }}</div>
</div>
</div>
<style>
.block-section-title {
position: relative;
width: 100%;
height: 500px;
overflow: hidden;
}
.st-bg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
z-index: 1;
}
.st-bg-default {
background: linear-gradient(135deg, #1e3a5f 0%, #2563eb 50%, #4dc4ff 100%);
}
.st-breadcrumb {
position: absolute;
top: 18px;
left: 89px;
z-index: 5;
font-size: 13px;
color: rgba(255,255,255,0.7);
}
.st-text {
position: absolute;
bottom: 40px;
left: 89px;
z-index: 5;
}
.st-en {
font-size: 15px;
font-weight: var(--weight-normal, 400);
color: #ffffff;
opacity: 0.85;
margin-bottom: 4px;
}
.st-ko {
font-size: 35px;
font-weight: var(--weight-bold, 700);
color: #ffffff;
line-height: 1.3;
}
</style>

View File

@@ -0,0 +1,41 @@
<!-- 중앙 정렬 꼭지 헤더: 대제목 + 하단 설명 -->
<!--
📋 topic-center
─────────────────
용도: 단독 강조 꼭지, 페이지 중심 주제 선언
슬롯: title (필수), subtitle (선택), description (선택)
Figma 원본: 2-2_02 "디지털전환을 위한 S/W 필요성"
-->
<div class="block-topic-center">
<div class="tc-title">{{ title }}</div>
{% if subtitle %}<div class="tc-sub">{{ subtitle }}</div>{% endif %}
{% if description %}<div class="tc-desc">{{ description }}</div>{% endif %}
</div>
<style>
.block-topic-center {
text-align: center;
padding: 20px 40px;
}
.tc-title {
font-size: 26px;
font-weight: 900;
color: #1e293b;
line-height: 1.4;
word-break: keep-all;
}
.tc-sub {
font-size: 14px;
color: #2563eb;
font-weight: 600;
margin-top: 6px;
}
.tc-desc {
font-size: 15px;
color: #475569;
line-height: 1.7;
margin-top: 12px;
word-break: keep-all;
white-space: pre-line;
}
</style>

View File

@@ -0,0 +1,39 @@
<!-- 꼭지 제목+설명: 좌측 질문/소제목 + 우측 설명 -->
<!--
📋 topic-header
─────────────────
용도: 각 꼭지의 시작부, 좌측에 파란 굵은 제목 + 우측에 본문 설명
슬롯: title (필수), description (필수)
비율: 좌 240px : 우 나머지
Figma 원본: sub_제목,내용 (742x68~78)
-->
<div class="block-topic-header">
<div class="th-title">{{ title }}</div>
<div class="th-desc">{{ description }}</div>
</div>
<style>
.block-topic-header {
display: flex;
gap: 20px;
padding: 12px 0;
}
.th-title {
width: 240px;
flex-shrink: 0;
font-size: 24px;
font-weight: var(--weight-bold, 700);
color: var(--color-accent-deep, #004cbe);
line-height: 1.4;
word-break: keep-all;
}
.th-desc {
white-space: pre-line;
flex: 1;
font-size: 16px;
font-weight: var(--weight-normal, 400);
color: var(--color-text, #000000);
line-height: 1.7;
word-break: keep-all;
}
</style>

View File

@@ -0,0 +1,57 @@
<!-- 번호 꼭지 헤더: 번호 원형 + 제목 + 구분선 + 설명 -->
<!--
📋 topic-numbered
─────────────────
용도: 순서가 있는 꼭지 시작, 단계별 섹션 헤더
슬롯: number, title, description (선택), color (선택)
topic-left-right와 다른 점: 좌우 배치가 아닌 번호+세로 배치
-->
<div class="block-topic-num">
<div class="tn-header">
<div class="tn-number" style="background: {{ color | default('#2563eb') }}">{{ number }}</div>
<div class="tn-title">{{ title }}</div>
</div>
<div class="tn-divider" style="background: {{ color | default('#2563eb') }}"></div>
{% if description %}<div class="tn-desc">{{ description }}</div>{% endif %}
</div>
<style>
.block-topic-num {
padding: 8px 0;
}
.tn-header {
display: flex;
align-items: center;
gap: 12px;
}
.tn-number {
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
font-size: 16px;
font-weight: 800;
flex-shrink: 0;
}
.tn-title {
font-size: 20px;
font-weight: 800;
color: #1e293b;
}
.tn-divider {
height: 2px;
margin: 8px 0 10px;
border-radius: 1px;
opacity: 0.3;
}
.tn-desc {
font-size: 15px;
color: #475569;
line-height: 1.7;
white-space: pre-line;
word-break: keep-all;
}
</style>

View File

@@ -0,0 +1,60 @@
<!-- Before/After 이미지: 좌측 Before + 우측 After + 화살표 -->
<!--
📋 image-before-after
─────────────────
용도: 변화 전후 비교, 공정 전후, 디지털 전환 전후
슬롯: before_src, before_label, after_src, after_label, caption (선택)
-->
<div class="block-ba">
<div class="ba-item">
<div class="ba-label ba-before">{{ before_label | default('Before') }}</div>
<img src="{{ before_src }}" alt="before">
</div>
<div class="ba-arrow"></div>
<div class="ba-item">
<div class="ba-label ba-after">{{ after_label | default('After') }}</div>
<img src="{{ after_src }}" alt="after">
</div>
</div>
{% if caption %}<div class="ba-caption">{{ caption }}</div>{% endif %}
<style>
.block-ba {
display: flex;
gap: 12px;
align-items: center;
}
.ba-item {
flex: 1;
border-radius: 8px;
overflow: hidden;
border: 1px solid #e2e8f0;
}
.ba-label {
padding: 6px 14px;
font-size: 13px;
font-weight: 700;
text-align: center;
color: #ffffff;
}
.ba-before { background: #6b7280; }
.ba-after { background: #2563eb; }
.ba-item img {
width: 100%;
height: 180px;
object-fit: cover;
display: block;
}
.ba-arrow {
font-size: 2rem;
color: #2563eb;
font-weight: 900;
flex-shrink: 0;
}
.ba-caption {
font-size: 12px;
color: #64748b;
text-align: center;
margin-top: 8px;
}
</style>

View File

@@ -0,0 +1,32 @@
<!-- 전체 너비 이미지 + 캡션 -->
<!--
📋 image-full-caption
─────────────────
용도: 핵심 도표, 대형 다이어그램, 전경 사진을 전체 너비로 표시
슬롯: src (필수), alt (선택), caption (선택)
Figma 원본: 2-3_05 하단 전경 사진, 2-3_03 하단 항공 사진
-->
<div class="block-img-full">
<img src="{{ src }}" alt="{{ alt | default('') }}">
{% if caption %}<div class="if-caption">{{ caption }}</div>{% endif %}
</div>
<style>
.block-img-full {
width: 100%;
border-radius: 8px;
overflow: hidden;
}
.block-img-full img {
width: 100%;
height: auto;
display: block;
}
.if-caption {
font-size: 12px;
color: #64748b;
text-align: center;
padding: 6px;
background: #f8fafc;
}
</style>

View File

@@ -0,0 +1,42 @@
<!-- 이미지 2x2 그리드: 4장 이미지 격자 배치 -->
<!--
📋 image-grid-2x2
─────────────────
용도: 현장 사진 4장, 참고 이미지 4장, 사례 이미지 갤러리
슬롯: images[] (4개, 각각 src, alt, caption)
Figma 원본: 2-1_03 GIS 항공/위성/현장 사진
-->
<div class="block-img-2x2">
{% for img in images %}
<div class="ig-item">
<img src="{{ img.src }}" alt="{{ img.alt | default('') }}">
{% if img.caption %}<div class="ig-caption">{{ img.caption }}</div>{% endif %}
</div>
{% endfor %}
</div>
<style>
.block-img-2x2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.ig-item {
overflow: hidden;
border-radius: 6px;
}
.ig-item img {
width: 100%;
height: 100%;
object-fit: contain;
display: block;
background: var(--color-bg-subtle, #f8fafc);
}
.ig-caption {
font-size: 11px;
color: #64748b;
text-align: center;
padding: 4px;
background: #f8f9fb;
}
</style>

View File

@@ -0,0 +1,40 @@
<!-- 이미지 행: 2~4장 이미지 나란히 -->
<!--
📋 image-row
─────────────────
용도: 시공 사진, 근거 자료, 현장 이미지 나란히 배치
슬롯: images[] 배열 (각 이미지에 src, alt, caption)
Figma 원본: 2-1_02 > image grid (460x354 x 2)
-->
<div class="block-image-row" style="--ir-count: {{ images|length }}">
{% for img in images %}
<div class="ir-item">
<img src="{{ img.src }}" alt="{{ img.alt | default('') }}">
{% if img.caption %}<div class="ir-caption">{{ img.caption }}</div>{% endif %}
</div>
{% endfor %}
</div>
<style>
.block-image-row {
display: grid;
grid-template-columns: repeat(var(--ir-count, 2), 1fr);
gap: 0;
}
.ir-item {
overflow: hidden;
}
.ir-item img {
width: 100%;
height: 100%;
object-fit: contain;
display: block;
background: var(--color-bg-subtle, #f8fafc);
}
.ir-caption {
font-size: 11px;
color: var(--color-text-light, #94a3b8);
text-align: center;
padding: 4px;
}
</style>

View File

@@ -0,0 +1,71 @@
<!-- 이미지+텍스트 가로 배치: 좌측 이미지 + 우측 텍스트 -->
<!--
📋 image-side-text
─────────────────
용도: 이미지에 대한 설명, 제품/시스템 소개, 참고 자료 설명
슬롯: image_src, image_alt, title, description, bullets[]
Figma 원본: 2-2_01 하단 이미지+텍스트 영역
-->
<div class="block-img-side">
<div class="is-image">
<img src="{{ image_src }}" alt="{{ image_alt | default('') }}">
</div>
<div class="is-text">
{% if title %}<div class="is-title">{{ title }}</div>{% endif %}
{% if description %}<div class="is-desc">{{ description }}</div>{% endif %}
{% if bullets %}
<ul class="is-bullets">
{% for item in bullets %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
<style>
.block-img-side {
display: flex;
gap: 24px;
align-items: flex-start;
}
.is-image {
flex-shrink: 0;
width: 320px;
border-radius: 8px;
overflow: hidden;
}
.is-image img {
width: 100%;
height: auto;
display: block;
}
.is-text {
flex: 1;
padding-top: 4px;
}
.is-title {
font-size: 18px;
font-weight: 700;
color: #1e293b;
margin-bottom: 8px;
line-height: 1.4;
}
.is-desc {
font-size: 14px;
color: #444;
line-height: 1.7;
word-break: keep-all;
margin-bottom: 10px;
}
.is-bullets {
list-style: disc;
padding-left: 18px;
font-size: 13px;
color: #333;
line-height: 1.7;
}
.is-bullets li {
margin-bottom: 4px;
}
</style>

View File

@@ -0,0 +1,71 @@
<!-- 2단 분할 비교: 좌측 GIS / 우측 BIM 같은 상세 비교 -->
<!--
📋 compare-2col-split
─────────────────
용도: 두 개념/기술의 상세 항목별 비교 (좌우 나란히, 중앙 기준 라벨)
슬롯: left_title, right_title, rows[] (각 행에 criteria, left, right)
Figma 원본: 2-3_03 GIS vs BIM 비교 (개념/분석/도면/발전 등)
-->
<div class="block-split-compare">
<div class="sc-header">
<div class="sc-h-left">{{ left_title }}</div>
<div class="sc-h-center">구분</div>
<div class="sc-h-right">{{ right_title }}</div>
</div>
{% for row in rows %}
<div class="sc-row {% if loop.index is odd %}sc-row-odd{% endif %}">
<div class="sc-left">{{ row.left }}</div>
<div class="sc-center">{{ row.criteria }}</div>
<div class="sc-right">{{ row.right }}</div>
</div>
{% endfor %}
</div>
<style>
.block-split-compare {
border: 1px solid #e2e8f0;
border-radius: 8px;
overflow: hidden;
}
.sc-header {
display: grid;
grid-template-columns: 1fr 100px 1fr;
background: linear-gradient(135deg, #0d47a1, #1565c0);
color: #ffffff;
font-weight: 700;
font-size: 15px;
text-align: center;
}
.sc-h-left, .sc-h-right, .sc-h-center {
padding: 12px;
}
.sc-h-center {
background: rgba(0,0,0,0.15);
font-size: 13px;
}
.sc-row {
display: grid;
grid-template-columns: 1fr 100px 1fr;
border-top: 1px solid #e2e8f0;
}
.sc-row-odd {
background: #f8fafc;
}
.sc-left, .sc-right {
padding: 10px 14px;
font-size: 13px;
line-height: 1.7;
color: #334155;
white-space: pre-line;
}
.sc-center {
padding: 10px 8px;
font-size: 12px;
font-weight: 700;
color: #1565c0;
text-align: center;
background: #f0f7ff;
border-left: 1px solid #e2e8f0;
border-right: 1px solid #e2e8f0;
}
</style>

View File

@@ -0,0 +1,110 @@
<!-- 비교 테이블: BIM vs DX 스타일 3단 테이블 -->
<!--
📋 comparison-table
─────────────────
용도: 다항목 비교 (좌측 A | 중앙 기준 | 우측 B)
슬롯: headers[] (3개), rows[][] (각 행 3칸)
Figma 원본: 2-1_02 > BIM VS D/X 테이블
특징: 중앙 칼럼에 파란 그라데이션 배지, 좌우 불릿 대비
-->
<div class="block-table-figma">
<table>
<thead>
<tr>
{% for header in headers %}
<th class="{% if loop.index == 1 %}th-left{% elif loop.index == 2 %}th-center{% else %}th-right{% endif %}">
{% if loop.index == 2 %}<span class="th-badge">{{ header }}</span>{% else %}{{ header }}{% endif %}
</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in rows %}
<tr>
{% for cell in row %}
<td class="{% if loop.index == 1 %}td-left{% elif loop.index == 2 %}td-center{% else %}td-right{% endif %}">{{ cell }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
<style>
.block-table-figma {
overflow: auto;
container-type: inline-size;
}
.block-table-figma table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
font-size: 13px;
line-height: 1.7;
}
/* 헤더 */
.block-table-figma thead th {
padding: 14px 12px;
font-size: 16px;
font-weight: 700;
border-bottom: 2px solid #e8edf2;
}
.th-left {
text-align: center;
color: #6bcdff;
}
.th-center {
text-align: center;
width: 120px;
}
.th-badge {
display: inline-block;
background: linear-gradient(135deg, #006eff 0%, #00aaff 100%);
color: #ffffff;
font-size: 15px;
font-weight: 700;
padding: 8px 28px;
border-radius: var(--radius);
}
.th-right {
text-align: center;
color: #006eff;
}
/* 본문 */
.block-table-figma tbody td {
padding: 10px 14px;
border-bottom: 1px solid #f0f2f5;
vertical-align: middle;
}
.td-left {
white-space: pre-line;
text-align: center;
color: #444;
}
.td-center {
text-align: center;
font-weight: 700;
color: #333;
background: #f6f8fb;
font-size: 13px;
}
.td-right {
white-space: pre-line;
text-align: center;
color: #444;
}
/* container query: 좁은 영역(sidebar 등)에서 폰트 자동 축소 */
@container (max-width: 40rem) {
.block-table-figma table { font-size: var(--font-caption, 0.8rem); }
.block-table-figma thead th { font-size: 14px; padding: 10px 8px; }
.block-table-figma tbody td { padding: 8px 10px; }
}
@container (max-width: 25rem) {
.block-table-figma table { font-size: var(--font-small, 0.7rem); }
.block-table-figma thead th { font-size: 12px; padding: 8px 6px; }
.block-table-figma tbody td { padding: 6px 8px; }
.th-badge { padding: 4px 14px; font-size: 12px; }
}
</style>

View File

@@ -0,0 +1,58 @@
<!-- 심플 줄무늬 테이블: 범용 데이터 테이블 -->
<!--
📋 table-simple-striped
─────────────────
용도: 스펙, 수치, 일정, 항목별 데이터 나열
슬롯: headers[], rows[][]
compare-3col-badge와 다른 점: VS 배지 없음, 범용 데이터용
-->
<div class="block-table-striped">
<table>
<thead>
<tr>
{% for h in headers %}
<th>{{ h }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in rows %}
<tr>
{% for cell in row %}
<td>{{ cell }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
<style>
.block-table-striped table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
line-height: 1.7;
}
.block-table-striped thead th {
background: #1e293b;
color: #ffffff;
font-weight: 700;
padding: 10px 14px;
text-align: left;
font-size: 13px;
}
.block-table-striped tbody td {
padding: 9px 14px;
border-bottom: 1px solid #e2e8f0;
white-space: pre-line;
color: #334155;
}
.block-table-striped tbody tr:nth-child(even) {
background: #f8fafc;
}
.block-table-striped tbody td:first-child {
font-weight: 600;
color: #1e293b;
}
</style>

View File

@@ -0,0 +1,58 @@
<!-- 원형 라벨: CSS 그라데이션 원 + 중앙 텍스트 -->
<!--
📋 circle-label
─────────────────
용도: 섹션 전환점, 핵심 키워드 강조, 시각적 구분자
슬롯: label (필수), sub_label (선택)
Figma 원본: 2-1_02 > Group 1171281590 (190x190)
-->
<div class="block-circle-label">
<div class="cl-outer">
<div class="cl-inner">
<div class="cl-text">{{ label }}</div>
{% if sub_label %}<div class="cl-sub">{{ sub_label }}</div>{% endif %}
</div>
</div>
</div>
<style>
.block-circle-label {
display: flex;
justify-content: center;
padding: 8px 0;
min-height: 0;
flex-shrink: 1;
}
.cl-outer {
width: 190px;
height: 190px;
border-radius: 50%;
background: linear-gradient(180deg, #3db8ff 0%, #006aff 100%);
box-shadow: 0 0 30px rgba(0, 106, 255, 0.25);
display: flex;
align-items: center;
justify-content: center;
}
.cl-inner {
width: 170px;
height: 170px;
border-radius: 50%;
background: linear-gradient(180deg, #4dc4ff 0%, #0080ff 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #ffffff;
text-align: center;
}
.cl-text {
font-size: 20px;
font-weight: var(--weight-bold, 700);
line-height: 1.4;
}
.cl-sub {
font-size: 12px;
opacity: 0.8;
margin-top: 4px;
}
</style>

View File

@@ -0,0 +1,74 @@
<!-- 비교 박스: 이중 테두리 둥근 박스 2개 + VS -->
<!--
📋 compare-pill-pair
─────────────────
용도: 2개 개념 시각적 대비 (비교 테이블 위 헤더)
슬롯: left_label, left_sub, right_label, right_sub
Figma 원본: 이중 테두리 (바깥 얇은 선 + 안쪽 둥근 박스)
-->
<div class="block-compare-pill">
<div class="cp-item">
<div class="cp-outer">
<div class="cp-inner">
<div class="cp-label">{{ left_label }}</div>
{% if left_sub %}<div class="cp-sub">{{ left_sub }}</div>{% endif %}
</div>
</div>
</div>
<div class="cp-vs">VS</div>
<div class="cp-item">
<div class="cp-outer">
<div class="cp-inner">
<div class="cp-label">{{ right_label }}</div>
{% if right_sub %}<div class="cp-sub">{{ right_sub }}</div>{% endif %}
</div>
</div>
</div>
</div>
<style>
.block-compare-pill {
display: flex;
gap: 20px;
align-items: center;
justify-content: center;
padding: 20px 0;
}
.cp-item {
width: 350px;
}
.cp-outer {
border: 2px solid #a8d8f0;
border-radius: 60px;
padding: 6px;
background: transparent;
}
.cp-inner {
border: 2px solid #7ec8f0;
border-radius: 55px;
background: linear-gradient(135deg, #e8f4fd 0%, #d6edfa 100%);
padding: 18px 30px;
text-align: center;
}
.cp-label {
font-size: 18px;
font-weight: 800;
color: #0088cc;
line-height: 1.4;
white-space: pre-line;
}
.cp-sub {
font-size: 12px;
color: #0088cc;
margin-top: 4px;
line-height: 1.5;
white-space: pre-line;
font-weight: 500;
}
.cp-vs {
font-size: 24px;
font-weight: 800;
color: #333;
flex-shrink: 0;
}
</style>

View File

@@ -0,0 +1,34 @@
<!-- 가로 흐름도: 좌→우 화살표로 연결된 단계 (SVG) -->
<!--
📋 flow-arrow-horizontal
─────────────────
용도: 발전 과정, 기술 진화, 전환 흐름 (A → B → C)
슬롯: steps[] (각 단계에 label, sub)
Figma 원본: 2-3_03 "GIS ↔ SPCC → 토공 → ..." / 2-2_04 개발 패러다임
-->
<div class="block-flow-h">
<svg viewBox="0 0 {{ steps|length * 180 }} 80" width="100%" xmlns="http://www.w3.org/2000/svg" font-family="Pretendard Variable, sans-serif">
{% for step in steps %}
{% set x = loop.index0 * 180 + 70 %}
<rect x="{{ x - 60 }}" y="10" width="120" height="50" rx="25" fill="{{ step.color | default('#2563eb') }}" opacity="0.9" />
<text x="{{ x }}" y="32" text-anchor="middle" fill="white" font-size="13" font-weight="700">{{ step.label }}</text>
{% if step.sub %}
<text x="{{ x }}" y="48" text-anchor="middle" fill="white" font-size="10" opacity="0.8">{{ step.sub }}</text>
{% endif %}
{% if not loop.last %}
<polygon points="{{ x + 65 }},35 {{ x + 80 }},35 {{ x + 75 }},28" fill="#94a3b8" />
<polygon points="{{ x + 65 }},35 {{ x + 80 }},35 {{ x + 75 }},42" fill="#94a3b8" />
{% endif %}
{% endfor %}
</svg>
</div>
<style>
.block-flow-h {
padding: 10px 0;
overflow-x: auto;
}
.block-flow-h svg {
min-width: 400px;
}
</style>

View File

@@ -0,0 +1,56 @@
<!-- 키워드 원형 행: 원 안에 키워드 + 하단 설명 (SVG) -->
<!--
📋 keyword-circle-row
─────────────────
용도: 핵심 키워드를 원형으로 나열하며 각각 설명. 약어 풀이.
슬롯: keywords[] (각 항목에 letter, label, description, color)
Figma 원본: 2-3_03 G-S-I-M 약어 설명, 2-2_04 개발 조건 키워드
-->
<div class="block-kw-circles">
{% for kw in keywords %}
<div class="kw-item">
<svg viewBox="0 0 80 80" width="70" height="70" xmlns="http://www.w3.org/2000/svg">
<defs>
<radialGradient id="kwGrad{{ loop.index }}" cx="40%" cy="35%" r="60%">
<stop offset="0%" stop-color="{{ kw.color_light | default('#93c5fd') }}" />
<stop offset="100%" stop-color="{{ kw.color | default('#2563eb') }}" />
</radialGradient>
</defs>
<circle cx="40" cy="40" r="38" fill="url(#kwGrad{{ loop.index }})" />
<text x="40" y="44" text-anchor="middle" dominant-baseline="central" fill="white" font-size="28" font-weight="900" font-family="Pretendard Variable, sans-serif">{{ kw.letter }}</text>
</svg>
<div class="kw-label">{{ kw.label }}</div>
{% if kw.description %}<div class="kw-desc">{{ kw.description }}</div>{% endif %}
</div>
{% endfor %}
</div>
<style>
.block-kw-circles {
display: flex;
gap: 24px;
justify-content: center;
flex-wrap: wrap;
padding: 10px 0;
}
.kw-item {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
width: 140px;
}
.kw-label {
font-size: 14px;
font-weight: 700;
color: #1e293b;
margin-top: 8px;
}
.kw-desc {
font-size: 12px;
color: #475569;
line-height: 1.5;
margin-top: 4px;
white-space: pre-line;
}
</style>

View File

@@ -0,0 +1,50 @@
<!-- 레이어 다이어그램: 겹쳐진 레이어 표현 (SVG) -->
<!--
📋 layer-diagram
─────────────────
용도: GIS/BIM/DT 레이어 구조, 기술 스택, 계층 구조 시각화
슬롯: layers[] (각 레이어에 label, color), title (선택)
Figma 원본: 1장_1-1_미래 "GIS+BIM+DT 레이어 시각화"
-->
<div class="block-layer-diag">
{% if title %}<div class="ld-title">{{ title }}</div>{% endif %}
<svg viewBox="0 0 400 {{ layers|length * 60 + 40 }}" width="100%" xmlns="http://www.w3.org/2000/svg" font-family="Pretendard Variable, sans-serif">
<defs>
{% for layer in layers %}
<linearGradient id="layerGrad{{ loop.index }}" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="{{ layer.color | default('#2563eb') }}" stop-opacity="0.85" />
<stop offset="100%" stop-color="{{ layer.color | default('#2563eb') }}" stop-opacity="0.6" />
</linearGradient>
{% endfor %}
<filter id="layerShadow">
<feDropShadow dx="0" dy="2" stdDeviation="3" flood-opacity="0.15" />
</filter>
</defs>
{% for layer in layers %}
{% set y = (layers|length - loop.index) * 55 + 20 %}
{% set offset = loop.index0 * 15 %}
<!-- 3D 효과: 사다리꼴 레이어 -->
<path d="M {{ 40 + offset }},{{ y }} L {{ 360 - offset }},{{ y }} L {{ 340 - offset }},{{ y + 40 }} L {{ 60 + offset }},{{ y + 40 }} Z"
fill="url(#layerGrad{{ loop.index }})" filter="url(#layerShadow)" />
<text x="200" y="{{ y + 25 }}" text-anchor="middle" fill="white" font-size="14" font-weight="700">{{ layer.label }}</text>
{% endfor %}
</svg>
</div>
<style>
.block-layer-diag {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
padding: 10px 0;
}
.ld-title {
font-size: 16px;
font-weight: 700;
color: #1e293b;
}
.block-layer-diag svg {
max-width: 400px;
}
</style>

View File

@@ -0,0 +1,61 @@
<!-- 프로세스 블록: 가로 단계 흐름 -->
<div class="block-process">
{% for step in steps %}
<div class="process-step">
<div class="process-number">{{ step.number | default(loop.index) }}</div>
<div class="process-title">{{ step.title }}</div>
{% if step.description %}<div class="process-desc">{{ step.description }}</div>{% endif %}
</div>
{% if not loop.last %}
<div class="process-arrow"></div>
{% endif %}
{% endfor %}
</div>
<style>
.block-process {
display: flex;
align-items: center;
justify-content: center;
gap: var(--spacing-small);
height: 100%;
}
.process-step {
flex: 1;
text-align: center;
padding: var(--spacing-inner);
background: var(--color-bg-subtle);
border: var(--border-width) solid var(--color-border);
border-radius: var(--radius);
}
.process-number {
width: 36px;
height: 36px;
border-radius: 50%;
background: var(--color-accent);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: var(--weight-bold);
font-size: var(--font-body);
margin: 0 auto var(--spacing-small);
}
.process-title {
font-size: var(--font-body);
font-weight: var(--weight-bold);
color: var(--color-primary);
margin-bottom: 4px;
}
.process-desc {
font-size: var(--font-caption);
color: var(--color-text-secondary);
line-height: var(--line-height-ko);
}
.process-arrow {
font-size: 1.5rem;
color: var(--color-accent);
font-weight: var(--weight-bold);
flex-shrink: 0;
}
</style>

View File

@@ -0,0 +1,40 @@
<!-- 피라미드 계층: 위에서 아래로 넓어지는 계층 구조 (SVG) -->
<!--
📋 pyramid-hierarchy
─────────────────
용도: 위계, 우선순위, 상위→하위 개념 (좁은→넓은)
슬롯: levels[] (상단부터, 각 레벨에 label, color)
-->
<div class="block-pyramid">
<svg viewBox="0 0 500 {{ levels|length * 70 + 20 }}" width="100%" xmlns="http://www.w3.org/2000/svg" font-family="Pretendard Variable, sans-serif">
<defs>
{% for level in levels %}
<linearGradient id="pyrGrad{{ loop.index }}" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="{{ level.color | default('#2563eb') }}" />
<stop offset="100%" stop-color="{{ level.color | default('#2563eb') }}" stop-opacity="0.7" />
</linearGradient>
{% endfor %}
<filter id="pyrShadow">
<feDropShadow dx="0" dy="2" stdDeviation="3" flood-opacity="0.12" />
</filter>
</defs>
{% for level in levels %}
{% set i = loop.index0 %}
{% set y = i * 65 + 10 %}
{% set w_half = 60 + i * 55 %}
<rect x="{{ 250 - w_half }}" y="{{ y }}" width="{{ w_half * 2 }}" height="50" rx="6" fill="url(#pyrGrad{{ loop.index }})" filter="url(#pyrShadow)" />
<text x="250" y="{{ y + 30 }}" text-anchor="middle" fill="white" font-size="14" font-weight="700">{{ level.label }}</text>
{% endfor %}
</svg>
</div>
<style>
.block-pyramid {
display: flex;
justify-content: center;
padding: 10px 0;
}
.block-pyramid svg {
max-width: 450px;
}
</style>

View File

@@ -0,0 +1,37 @@
<!-- 가로 타임라인: 좌→우 시간축 + 마커 + 라벨 (SVG) -->
<!--
📋 timeline-horizontal
─────────────────
용도: 연도별 로드맵, 짧은 일정, 마일스톤 (가로 배치)
슬롯: events[] (각 이벤트에 year, title, color)
timeline-vertical과 다른 점: 가로 방향, 공간 효율적
-->
<div class="block-timeline-h">
<svg viewBox="0 0 {{ events|length * 160 + 40 }} 100" width="100%" xmlns="http://www.w3.org/2000/svg" font-family="Pretendard Variable, sans-serif">
<!-- 가로 선 -->
<line x1="30" y1="40" x2="{{ events|length * 160 - 10 }}" y2="40" stroke="#cbd5e1" stroke-width="2" />
{% for event in events %}
{% set x = loop.index0 * 160 + 60 %}
<!-- 마커 -->
<circle cx="{{ x }}" cy="40" r="12" fill="{{ event.color | default('#2563eb') }}" />
<circle cx="{{ x }}" cy="40" r="5" fill="white" />
<!-- 연도 -->
<text x="{{ x }}" y="22" text-anchor="middle" fill="{{ event.color | default('#2563eb') }}" font-size="12" font-weight="800">{{ event.year }}</text>
<!-- 제목 -->
<text x="{{ x }}" y="65" text-anchor="middle" fill="#1e293b" font-size="12" font-weight="600">{{ event.title }}</text>
{% if event.sub %}
<text x="{{ x }}" y="80" text-anchor="middle" fill="#64748b" font-size="10">{{ event.sub }}</text>
{% endif %}
{% endfor %}
</svg>
</div>
<style>
.block-timeline-h {
padding: 10px 0;
overflow-x: auto;
}
.block-timeline-h svg {
min-width: 500px;
}
</style>

View File

@@ -0,0 +1,74 @@
<!-- 세로 타임라인: 좌측 선 + 원형 마커 + 우측 내용 (SVG 마커) -->
<!--
📋 timeline-vertical
─────────────────
용도: 연혁, 정책 시행 일정, 로드맵, 연도별 사건
슬롯: events[] (각 이벤트에 year, title, description, color)
Figma 참고: 정책 로드맵, 건설 정책 추진현황
-->
<div class="block-timeline-v">
{% for event in events %}
<div class="tv-event">
<div class="tv-marker-col">
<svg viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" fill="{{ event.color | default('#2563eb') }}" />
<circle cx="12" cy="12" r="5" fill="white" />
</svg>
{% if not loop.last %}<div class="tv-line" style="background: {{ event.color | default('#2563eb') }}"></div>{% endif %}
</div>
<div class="tv-content">
<div class="tv-year" style="color: {{ event.color | default('#2563eb') }}">{{ event.year }}</div>
<div class="tv-title">{{ event.title }}</div>
{% if event.description %}<div class="tv-desc">{{ event.description }}</div>{% endif %}
</div>
</div>
{% endfor %}
</div>
<style>
.block-timeline-v {
display: flex;
flex-direction: column;
gap: 0;
padding: 10px 0;
}
.tv-event {
display: flex;
gap: 14px;
}
.tv-marker-col {
display: flex;
flex-direction: column;
align-items: center;
flex-shrink: 0;
width: 24px;
}
.tv-line {
width: 2px;
flex: 1;
min-height: 20px;
opacity: 0.3;
border-radius: 1px;
}
.tv-content {
padding-bottom: 20px;
flex: 1;
}
.tv-year {
font-size: 13px;
font-weight: 800;
margin-bottom: 2px;
}
.tv-title {
font-size: 15px;
font-weight: 700;
color: #1e293b;
margin-bottom: 4px;
}
.tv-desc {
font-size: 13px;
color: #475569;
line-height: 1.7;
white-space: pre-line;
}
</style>

View File

@@ -0,0 +1,125 @@
<!-- 벤 다이어그램: SVG premium (N개 자동 배치) -->
<!--
📋 venn-diagram (P2-B: 동적 좌표 계산)
─────────────────
용도: 상위-하위 포함 관계, 기술 융합 구조
방식: renderer가 svg_calculator.prepare_venn_data()로 좌표 사전 계산
→ items[].cx, cy, r + outer_r, center_x, center_y 전달
Phase 1 fallback: outer_r이 없으면 3개 고정 좌표 사용
-->
<div class="block-venn-svg">
{% if outer_r is defined %}
{# ═══ P2-B: 동적 N개 배치 ═══ #}
<svg viewBox="0 0 {{ viewbox_width | default(600) }} {{ viewbox_height | default(550) }}" width="100%" xmlns="http://www.w3.org/2000/svg" font-family="Pretendard Variable, sans-serif">
<defs>
<linearGradient id="bgGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#dce3ea" />
<stop offset="100%" stop-color="#f0f2f5" />
</linearGradient>
<radialGradient id="blueOuter" cx="45%" cy="40%" r="55%">
<stop offset="0%" stop-color="#2979ff" />
<stop offset="40%" stop-color="#1565c0" />
<stop offset="100%" stop-color="#0d47a1" />
</radialGradient>
<radialGradient id="hlGrad" cx="35%" cy="25%" r="40%">
<stop offset="0%" stop-color="rgba(255,255,255,0.5)" />
<stop offset="100%" stop-color="rgba(255,255,255,0)" />
</radialGradient>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="8" result="blur" />
<feMerge><feMergeNode in="blur" /><feMergeNode in="SourceGraphic" /></feMerge>
</filter>
<filter id="shadow">
<feDropShadow dx="0" dy="4" stdDeviation="8" flood-color="rgba(0,0,0,0.3)" />
</filter>
{% for item in items %}
<radialGradient id="itemGrad{{ loop.index }}" cx="35%" cy="30%" r="65%">
<stop offset="0%" stop-color="{{ item.color_light | default('#93c5fd') }}" />
<stop offset="100%" stop-color="{{ item.color | default('#3b82f6') }}" />
</radialGradient>
{% endfor %}
</defs>
<rect width="{{ viewbox_width | default(600) }}" height="{{ viewbox_height | default(550) }}" fill="url(#bgGrad)" />
<circle cx="{{ center_x }}" cy="{{ center_y }}" r="{{ outer_r }}" fill="url(#blueOuter)" filter="url(#shadow)" />
<circle cx="{{ center_x }}" cy="{{ center_y }}" r="{{ outer_r - 10 }}" fill="none" stroke="#4a90d9" stroke-width="1.5" opacity="0.4" />
<circle cx="{{ center_x }}" cy="{{ center_y }}" r="{{ outer_r - 25 }}" fill="none" stroke="#5a9de0" stroke-width="1" opacity="0.3" />
<circle cx="{{ center_x }}" cy="{{ center_y }}" r="{{ outer_r - 40 }}" fill="none" stroke="#6aabe6" stroke-width="0.8" opacity="0.25" />
{% for item in items %}
<circle cx="{{ item.cx }}" cy="{{ item.cy }}" r="{{ item.r }}" fill="url(#itemGrad{{ loop.index }})" opacity="0.9" filter="url(#glow)" />
<circle cx="{{ item.cx }}" cy="{{ item.cy }}" r="{{ item.r }}" fill="url(#hlGrad)" />
<text x="{{ item.cx }}" y="{{ item.cy }}" text-anchor="middle" dominant-baseline="central" fill="white" font-size="{% if item.r > 60 %}18{% elif item.r > 40 %}15{% else %}12{% endif %}" font-weight="700">{{ item.label }}</text>
{% if item.sub %}
<text x="{{ item.cx }}" y="{{ item.cy + 18 }}" text-anchor="middle" fill="white" font-size="11" opacity="0.85">{{ item.sub }}</text>
{% endif %}
{% endfor %}
<text x="{{ center_x }}" y="{{ center_y - outer_r + 35 }}" text-anchor="middle" fill="#ffffff" font-size="13" font-weight="400" opacity="0.85">{{ center_sub | default('') }}</text>
<text x="{{ center_x }}" y="{{ center_y - outer_r + 60 }}" text-anchor="middle" fill="#ffffff" font-size="24" font-weight="900">{{ center_label }}</text>
</svg>
{% else %}
{# ═══ Phase 1 fallback: 3개 고정 ═══ #}
<svg viewBox="0 0 600 550" width="100%" xmlns="http://www.w3.org/2000/svg" font-family="Pretendard Variable, sans-serif">
<defs>
<linearGradient id="bgGrad" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#dce3ea" /><stop offset="100%" stop-color="#f0f2f5" /></linearGradient>
<radialGradient id="blueOuter" cx="45%" cy="40%" r="55%"><stop offset="0%" stop-color="#2979ff" /><stop offset="40%" stop-color="#1565c0" /><stop offset="100%" stop-color="#0d47a1" /></radialGradient>
<radialGradient id="orangeGrad" cx="35%" cy="35%" r="65%"><stop offset="0%" stop-color="#ffab40" /><stop offset="40%" stop-color="#ff6d00" /><stop offset="100%" stop-color="#c2185b" /></radialGradient>
<radialGradient id="mintGrad" cx="40%" cy="30%" r="65%"><stop offset="0%" stop-color="#80deea" /><stop offset="40%" stop-color="#26c6da" /><stop offset="100%" stop-color="#00897b" /></radialGradient>
<radialGradient id="goldGrad" cx="40%" cy="30%" r="65%"><stop offset="0%" stop-color="#ffd54f" /><stop offset="40%" stop-color="#ffb300" /><stop offset="100%" stop-color="#e65100" /></radialGradient>
<radialGradient id="hlGrad" cx="35%" cy="25%" r="40%"><stop offset="0%" stop-color="rgba(255,255,255,0.5)" /><stop offset="100%" stop-color="rgba(255,255,255,0)" /></radialGradient>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%"><feGaussianBlur stdDeviation="8" result="blur" /><feMerge><feMergeNode in="blur" /><feMergeNode in="SourceGraphic" /></feMerge></filter>
<filter id="shadow"><feDropShadow dx="0" dy="4" stdDeviation="8" flood-color="rgba(0,0,0,0.3)" /></filter>
</defs>
<rect width="600" height="550" fill="url(#bgGrad)" />
<circle cx="300" cy="270" r="230" fill="url(#blueOuter)" filter="url(#shadow)" />
<circle cx="300" cy="270" r="220" fill="none" stroke="#4a90d9" stroke-width="1.5" opacity="0.4" />
<circle cx="300" cy="270" r="205" fill="none" stroke="#5a9de0" stroke-width="1" opacity="0.3" />
<circle cx="300" cy="270" r="190" fill="none" stroke="#6aabe6" stroke-width="0.8" opacity="0.25" />
<circle cx="265" cy="300" r="120" fill="url(#orangeGrad)" opacity="0.92" filter="url(#glow)" />
<circle cx="265" cy="300" r="120" fill="url(#hlGrad)" />
<circle cx="370" cy="230" r="75" fill="url(#mintGrad)" opacity="0.9" filter="url(#glow)" />
<circle cx="370" cy="230" r="75" fill="url(#hlGrad)" />
<circle cx="365" cy="355" r="75" fill="url(#goldGrad)" opacity="0.9" filter="url(#glow)" />
<circle cx="365" cy="355" r="75" fill="url(#hlGrad)" />
<text x="300" y="95" text-anchor="middle" fill="#ffffff" font-size="13" font-weight="400" opacity="0.85">{{ center_sub | default('') }}</text>
<text x="300" y="125" text-anchor="middle" fill="#ffffff" font-size="26" font-weight="900">{{ center_label }}</text>
{% if items and items | length > 0 %}<text x="250" y="295" text-anchor="middle" fill="#ffffff" font-size="20" font-weight="800">{{ items[0].label }}</text>{% if items[0].sub %}<text x="250" y="318" text-anchor="middle" fill="#ffffff" font-size="13" opacity="0.85">{{ items[0].sub }}</text>{% endif %}{% endif %}
{% if items and items | length > 1 %}<text x="370" y="237" text-anchor="middle" fill="#ffffff" font-size="20" font-weight="800">{{ items[1].label }}</text>{% endif %}
{% if items and items | length > 2 %}<text x="365" y="362" text-anchor="middle" fill="#ffffff" font-size="20" font-weight="800">{{ items[2].label }}</text>{% endif %}
</svg>
{% endif %}
{% if description %}
<div class="venn-desc">{{ description }}</div>
{% endif %}
</div>
<style>
.block-venn-svg {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
padding: 10px 0;
flex-shrink: 1;
min-height: 0;
overflow: hidden;
}
.block-venn-svg svg {
max-width: 450px;
width: 100%;
height: auto;
flex-shrink: 1;
}
.venn-desc {
font-size: 14px;
color: #444;
text-align: center;
max-width: 450px;
line-height: 1.7;
word-break: keep-all;
}
</style>