Vendor templates and prefer local template assets

This commit is contained in:
2026-04-03 08:44:55 +09:00
parent 81b6289f80
commit adef735228
80 changed files with 5077 additions and 267 deletions

104
templates/blocks/INDEX.md Normal file
View File

@@ -0,0 +1,104 @@
# 블록 라이브러리 인덱스 (38개)
디자인 팀장이 콘텐츠에 맞는 블록을 선택할 때 참조하는 라이브러리.
각 카테고리 안에 변형이 여러 개 있으며, 콘텐츠 성격에 따라 적절한 변형을 선택한다.
**시각화(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/ (9개) — 카드 계열
| 파일 | 설명 | 언제 사용 |
|------|------|---------|
| `card-image-3col.html` | 이미지(160px) + 색상 제목 + 영문 + 불릿 (3열) | 단계별 설명에 이미지 핵심 |
| `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/ (6개) — 시각 요소 (**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) |
---
## 📁 emphasis/ (10개) — 강조, 인용, 결론
| 파일 | 설명 | 언제 사용 |
|------|------|---------|
| `quote-big-mark.html` | ❝❞ 큰따옴표 장식 + 인용+출처 | 임팩트 인용, 핵심 발언 |
| `quote-question.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` | 좌우 회색 선 + 중앙 텍스트 | 가벼운 섹션 구분, 휴식점 |
---
## 📁 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,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,52 @@
<!-- card-icon-desc variant: compact -->
<!--
📋 card-icon-desc--compact
─────────────────
용도: 높이가 부족할 때 아이콘 카드를 축소 렌더링
슬롯: cards[] (기존과 동일 — icon, title, description)
기존 card-icon-desc의 색상/구조 유지, 패딩/아이콘 축소
-->
<div class="block-card-icon-compact" style="--ci-count: {{ column_override | default(cards|length) }}">
{% for card in cards %}
<div class="cid-card-c">
{% if card.icon %}<div class="cid-icon-c">{{ card.icon }}</div>{% endif %}
<div class="cid-title-c">{{ card.title }}</div>
{% if card.description %}<div class="cid-desc-c">{{ card.description }}</div>{% endif %}
</div>
{% endfor %}
</div>
<style>
.block-card-icon-compact {
display: grid;
grid-template-columns: repeat(var(--ci-count, 3), 1fr);
gap: 8px;
}
.cid-card-c {
text-align: center;
padding: 8px 8px;
background: #f8fafc;
border-radius: 6px;
border: 1px solid #e2e8f0;
}
.cid-icon-c {
font-size: 1.4rem;
margin-bottom: 3px;
}
.cid-title-c {
font-size: 12px;
font-weight: 700;
color: #1e293b;
margin-bottom: 2px;
}
.cid-desc-c {
font-size: 10px;
color: #475569;
line-height: 1.5;
white-space: pre-line;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
</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: {{ column_override | default(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 variant: horizontal -->
<!--
📋 card-numbered--horizontal
─────────────────
용도: 같은 구조의 항목 2-3개를 가로로 나란히 비교
슬롯: items[] (기존과 동일 — title, description)
기존 card-numbered의 색상/스타일 유지, 배치만 가로
-->
<div class="block-card-num-h" style="--cn-count: {{ items|length }}">
{% for item in items %}
<div class="cn-item-h">
<div class="cn-number-h" style="background: {{ item.color | default('#2563eb') }}">{{ loop.index }}</div>
<div class="cn-body-h">
<div class="cn-title-h">{{ item.title }}</div>
<div class="cn-desc-h">{{ item.description }}</div>
</div>
</div>
{% endfor %}
</div>
<style>
.block-card-num-h {
display: grid;
grid-template-columns: repeat(var(--cn-count, 2), 1fr);
gap: 10px;
}
.cn-item-h {
display: flex;
gap: 10px;
align-items: flex-start;
padding: 10px 14px;
background: #f8fafc;
border-radius: 8px;
border: 1px solid #e2e8f0;
}
.cn-number-h {
width: 28px;
height: 28px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
font-size: 13px;
font-weight: 800;
flex-shrink: 0;
}
.cn-title-h {
font-size: 13px;
font-weight: 700;
color: #1e293b;
margin-bottom: 2px;
}
.cn-desc-h {
font-size: 11px;
color: #475569;
line-height: 1.6;
white-space: pre-line;
}
</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: {{ column_override | default(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,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,97 @@
<!-- comparison-2col variant: cards-in-container -->
<!--
📋 comparison-2col--cards-in-container
─────────────────
용도: 포함 관계 시각화 (A 안에 B, C, D가 포함됨)
슬롯: container_label, container_desc, cards[] (각 카드에 letter, label, description)
기존 comparison-2col의 색상 계열 활용
-->
<div class="block-container-cards">
<div class="cc-outer">
<div class="cc-badge">{{ container_label }}</div>
{% if container_desc %}<div class="cc-desc">{{ container_desc }}</div>{% endif %}
<div class="cc-grid" style="--cc-count: {{ cards|length }}">
{% for card in cards %}
<div class="cc-card">
{% if card.letter %}
<div class="cc-icon">{{ card.letter }}</div>
{% endif %}
<div class="cc-label">{{ card.label }}</div>
{% if card.description %}<div class="cc-text">{{ card.description }}</div>{% endif %}
</div>
{% endfor %}
</div>
</div>
</div>
<style>
.block-container-cards {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
.cc-outer {
width: 100%;
border: 3px solid #2563eb;
border-radius: 14px;
padding: 16px 14px 12px;
background: linear-gradient(180deg, #eff6ff, #dbeafe);
position: relative;
}
.cc-badge {
position: absolute;
top: -11px;
left: 50%;
transform: translateX(-50%);
background: #2563eb;
color: #ffffff;
font-size: 12px;
font-weight: 900;
padding: 3px 18px;
border-radius: 10px;
white-space: nowrap;
}
.cc-desc {
text-align: center;
font-size: 11px;
color: #1e40af;
margin-bottom: 10px;
}
.cc-grid {
display: grid;
grid-template-columns: repeat(var(--cc-count, 3), 1fr);
gap: 10px;
}
.cc-card {
background: #ffffff;
border: 2px solid #93c5fd;
border-radius: 8px;
padding: 10px;
text-align: center;
}
.cc-icon {
width: 36px;
height: 36px;
border-radius: 50%;
background: linear-gradient(135deg, #93c5fd, #2563eb);
color: #ffffff;
font-size: 16px;
font-weight: 900;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 6px;
}
.cc-label {
font-size: 13px;
font-weight: 700;
color: #1e293b;
margin-bottom: 3px;
}
.cc-text {
font-size: 10px;
color: #64748b;
line-height: 1.5;
}
</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,65 @@
<!-- dark-bullet-list variant: before-after -->
<!--
📋 dark-bullet-list--before-after
─────────────────
용도: 기존 방식 → 새 방식 전환/변화를 보여줄 때
슬롯: title (선택), changes[] (각 항목에 label, before, after)
기존 dark-bullet-list의 색상/배경/radius 그대로 사용
-->
<div class="block-dark-bullets">
{% if title %}<div class="db-title">{{ title }}</div>{% endif %}
<div class="db-changes">
{% for item in changes %}
<div class="db-change">
<div class="db-change-label">{{ item.label }}</div>
<div class="db-change-before">{{ item.before }}</div>
<div class="db-change-after">→ {{ item.after }}</div>
</div>
{% endfor %}
</div>
</div>
<style>
/* 기존 dark-bullet-list CSS 재사용 */
.block-dark-bullets {
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
border-radius: 8px;
padding: 14px 20px;
color: #ffffff;
}
.db-title {
font-size: 13px;
font-weight: 700;
margin-bottom: 8px;
color: #93c5fd;
}
/* variant: before-after 2열 구조 */
.db-changes {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 6px;
}
.db-change {
background: rgba(255,255,255,0.06);
border-radius: 6px;
padding: 8px 10px;
border-left: 3px solid #60a5fa;
}
.db-change-label {
font-size: 11px;
font-weight: 700;
color: #93c5fd;
margin-bottom: 3px;
}
.db-change-before {
font-size: 10px;
color: #94a3b8;
text-decoration: line-through;
}
.db-change-after {
font-size: 11px;
color: #e2e8f0;
font-weight: 500;
margin-top: 2px;
}
</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,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,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,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,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>

1844
templates/catalog.yaml Normal file

File diff suppressed because it is too large Load Diff

61
templates/slide-base.html Normal file
View File

@@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>{{ slide_title | default('슬라이드') }}</title>
<link rel="stylesheet" href="/static/base.css">
<style>
{% for page in pages %}
.slide-{{ page.page_number }} {
grid-template-areas: {{ page.grid_areas }};
grid-template-columns: {{ page.grid_columns | default('1fr') }};
grid-template-rows: {{ page.grid_rows | default('auto 1fr auto') }};
}
{% for block in page.blocks %}
.slide-{{ page.page_number }} .area-{{ block.area }} {
grid-area: {{ block.area }};
}
{% endfor %}
{% endfor %}
/* 다중 페이지: 페이지 간 간격 */
.slide + .slide {
margin-top: 40px;
}
/* 인쇄 시 페이지 분리 */
@media print {
.slide {
page-break-after: always;
}
.slide + .slide {
margin-top: 0;
}
}
</style>
</head>
<body>
{% for page in pages %}
<div class="slide slide-{{ page.page_number }}">
{% if loop.first and slide_title %}
<div class="slide-title" style="grid-area: header;">{{ slide_title }}</div>
{% endif %}
{% for block in page.blocks %}
<div class="area-{{ block.area }}" style="{{ block.style_override | default('') }}">
{{ block.html }}
</div>
{% endfor %}
</div>
{% endfor %}
<script>
/* 인쇄 시 <details> 자동 펼침, 인쇄 후 복원 */
window.onbeforeprint = function() {
document.querySelectorAll('details').forEach(function(d) { d.open = true; });
};
window.onafterprint = function() {
document.querySelectorAll('details').forEach(function(d) { d.open = false; });
};
</script>
</body>
</html>