WIP: hero-icon-cards_1 블록 + 오답노트 + figma 관련 파일

- hero-icon-cards_1.html: hero-icon-cards 변형 (icon → 소제목+불릿 계층)
- compare-detail-gradient.html: 하단 2열 비교 블록 (Figma Frame 4 기반)
- 오답노트.md: 절대 하지 말아야 하는 실수 목록
- figma_to_html.py: Figma→HTML 변환 스크립트
- static/figma-assets/: Figma export 이미지 (배지, 화살표)
- 주의: compare-detail-gradient CSS 폰트 크기가 임의 수정됨 — 원본 복원 필요

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-07 17:14:09 +09:00
parent 076aeb0403
commit 05703c8e72
37 changed files with 3536 additions and 0 deletions

112
FIGMA-DESIGN-LANGUAGE.md Normal file
View File

@@ -0,0 +1,112 @@
# Figma Design Language Analysis
> Phase 1 결과 문서 (2026-04-07)
> Figma Source: `9S6LsQyO6zlRxtiqZccOUM` / Page 1
## 1. 스코프
| 프레임 | 역할 | 판정 |
|--------|------|------|
| Frame 1 (1:3) | 3D 수렴 화살표 | 서브 컴포넌트 (장식 이미지) |
| Frame 2 (1:5) | Solution 제작 목표 | **블록화** → hero-icon-cards |
| Frame 3 (1:35) | 정책 달성 (Engn.Solution vs DfMA) | **블록화** → compare-2col-badge |
| Frame 4 (1:49) | 과정 vs 결과의 혁신 | **블록화** → compare-detail-gradient |
| Frame 5 (1:74) | 상세보기 버튼 | 서브 컴포넌트 (CTA) |
| Frame 6 (1:80) | 정책방향 (세로 문서) | **제외** (1280×720 부적합) |
## 2. 스케일 변환
Figma 캔버스 → 슬라이드(1280px) 변환 비율:
- Frame 2, 3: ×0.71 (1808px → 1280px)
- Frame 4: ×0.33 (3848px, 양쪽 합쳐서 2패널)
| Figma | 슬라이드 환산 | 역할 |
|-------|-------------|------|
| 70px | 28-35px | 대섹션 헤더 |
| 60px | 24-28px | Hero 메시지 |
| 50px | 22-26px | 섹션 제목, 배지 |
| 45px | 20-22px | 카드 타이틀 (EN) |
| 40px | 16-20px | 본문 |
| 35px | 14-18px | 부제, 한국어 서브 |
| 32px | 12-14px | 버튼 |
## 3. 색상 팔레트 (Warm Theme)
기존 블루/슬레이트 테마와 **병존**하는 새 팔레트:
| 토큰 | Hex | Figma 원본 | 용도 |
|------|-----|-----------|------|
| `--color-warm-brown` | `#5C3714` | rgba(92,55,20) | 과정/프로세스 섹션 제목 |
| `--color-dark-teal` | `#084C56` | rgba(8,76,86) | 결과/디지털 섹션 제목 |
| `--color-teal` | `#227582` | rgba(34,117,130) | 설명 텍스트 |
| `--color-forest` | `#548235` | rgba(84,130,53) | 배경 그라디언트 |
| `--color-beige` | `#E4D9C0` | rgba(228,217,192) | 서브틀 배경/버튼 |
| `--color-warm-yellow` | `#FAEDCB` | rgba(250,237,203) | 하이라이트 바 |
### 그라디언트 패턴
- 왼쪽(과정): `rgba(165,161,150,0.10) → rgba(57,50,30,1.00)` (베이지→브라운)
- 오른쪽(결과): `rgba(41,107,85,0.10) → rgba(3,33,24,1.00)` (틸→다크)
- 버튼: `rgba(255,255,255,0.00) → rgba(228,217,192,1.00)` (투명→베이지)
- 배경: `rgba(84,130,53,1.00) → rgba(37,62,31,0.00)` (그린→투명)
## 4. 타이포그래피
- **폰트**: Pretendard Variable 유지 (Noto Sans KR은 이미 fallback)
- **핵심은 크기/굵기 위계**
| 레벨 | 크기 (슬라이드) | Weight | 스트로크 | 정렬 |
|------|---------------|--------|---------|------|
| Hero Statement | 24-28px | 700 | white 1.5px | center |
| Section Header | 28-35px | 900 | white 5px | center/left |
| Badge Title | 22-26px | 700 | 없음 | center |
| Card Title (EN) | 20-22px | 900 | white 5px | center |
| Card Subtitle (KR) | 14-18px | 500 | white 1.5px | center |
| Body Text | 16-20px | 700 | white 1px | left |
| Section Sub-title | 22-26px | 900 | 없음 | left |
### 텍스트 스트로크 기법
Figma 디자인의 특징: 다양한 배경 위에서 가독성 확보를 위해 **흰색 스트로크** 사용
```css
-webkit-text-stroke: 1.5px white; /* 일반 텍스트 */
-webkit-text-stroke: 5px white; /* 강조 텍스트 */
paint-order: stroke fill; /* 스트로크가 텍스트 뒤로 */
```
## 5. 레이아웃 패턴
### A. Badge Header
- 이미지/그라디언트 배경 위 `border-radius: 20px`
- 중앙 흰색 텍스트 (50px/700 → 22-26px/700)
- 높이: ~88px (Figma) → ~44-50px (슬라이드)
### B. Hero Statement
- 전체 폭 중앙 정렬
- 큰 텍스트 (60px/700) + 흰색 스트로크
- 키워드 **굵은 강조** 가능
### C. Icon Card Row
- N개 카드 수평 배치, 세로 구분선
- 각 카드: 아이콘 이미지 + 영문 제목(900) + 한국어 부제(500)
- 흰색 둥근 컨테이너 (borderRadius: 20)
### D. Two-Col Comparison
- 좌/우 그라디언트 배경
- 각 열: 헤더 바 + (섹션 제목 + 본문) × N개
- 색상으로 좌/우 구분 (브라운 vs 틸)
### E. CTA Button
- 그라디언트 바 (투명→베이지) + 둥근 버튼 (r:7)
- 흰색 텍스트
## 6. 디자인 시스템 vs 콘텐츠 전용 경계
### 디자인 시스템 (블록에 포함)
- 색상 팔레트, 그라디언트 패턴
- 타이포그래피 위계, 텍스트 스트로크
- 둥근 모서리 컨테이너 (r:20)
- Badge Header, 2열 비교, N열 카드 레이아웃 구조
### 콘텐츠 전용 (블록에 포함하지 않음)
- 3D 화살표 이미지 (Frame 1) → 콘텐츠가 제공
- 특정 아이콘 이미지들 (brain, thunder 등) → 콘텐츠가 제공
- 도메인 텍스트 → 슬롯으로 처리

441
FIGMA-EXTRACTION.md Normal file
View File

@@ -0,0 +1,441 @@
# Figma → HTML 블록 변환 프로세스
> 2026-04-07 확립. Figma 디자인을 design_agent 블록으로 변환하는 정확한 방법론.
---
## 1. 전체 워크플로우
```
[Step 1] Figma API로 파일 구조 추출
[Step 2] 프레임별 렌더링 이미지(PNG) 다운로드
[Step 3] 노드별 상세 데이터 추출 (좌표, 색상, 폰트, 크기)
[Step 4] 디자인 언어 분석 (공통 패턴 vs 콘텐츠 전용 구분)
[Step 5] 블록 설계 (슬롯, 동적 규칙, schema)
[Step 6] 수학적 계산 (Figma 좌표 → 스케일 → CSS값)
[Step 7] HTML/CSS 구현
[Step 8] 비교 리뷰 (Figma PNG vs HTML, 같은 폭으로 위/아래 배치)
[Step 9] 피드백 반영 → Step 6~8 반복
[Step 10] Jinja2 템플릿화 + catalog.yaml 등록
```
---
## 2. Figma API 사용법
### 2.1 파일 구조 가져오기
```bash
curl -s -H "X-Figma-Token: {TOKEN}" \
"https://api.figma.com/v1/files/{FILE_KEY}" \
| python -m json.tool
```
### 2.2 특정 노드 상세 데이터
```bash
curl -s -H "X-Figma-Token: {TOKEN}" \
"https://api.figma.com/v1/files/{FILE_KEY}/nodes?ids={NODE_IDS}&geometry=paths"
```
### 2.3 노드 이미지 렌더링 (PNG)
```bash
curl -s -H "X-Figma-Token: {TOKEN}" \
"https://api.figma.com/v1/images/{FILE_KEY}?ids={NODE_IDS}&format=png&scale=2"
```
- `scale=2`: 2배 해상도로 다운로드 (선명도 확보)
- 응답의 `images` 객체에 각 노드 ID별 S3 URL 제공
### 2.4 추출해야 하는 핵심 데이터
| 데이터 | API 필드 | 용도 |
|-------|---------|------|
| 위치 | `absoluteBoundingBox.x, .y` | 요소 간 관계 계산 |
| 크기 | `absoluteBoundingBox.width, .height` | 스케일 계산 |
| 텍스트 | `characters` | 콘텐츠 확인 |
| 폰트 | `style.fontFamily, .fontSize, .fontWeight` | 타이포그래피 |
| 색상 | `fills[].color` | 색상 팔레트 |
| 테두리 | `strokes[], strokeWeight` | 박스 스타일 |
| 라운드 | `cornerRadius` | border-radius |
| 이미지 | `fills[].imageRef` | 이미지 자산 식별 |
---
## 3. 수학적 계산 (핵심)
### 3.1 스케일 팩터
```
슬라이드 콘텐츠 폭 = 1280px - padding(40px × 2) = 1200px
scale = 1200 / figma_frame_width
```
| Figma 프레임 | 폭 | 스케일 |
|-------------|-----|--------|
| Frame 2 | 1808px | 0.6637 |
| Frame 3 | 1807px | 0.6641 |
| Frame 4 | 3848px | 0.3118 |
### 3.2 요소 간 정렬 계산
**절대 원칙: Figma 좌표 차이값 → 스케일 적용 → CSS값**
```python
# 예: 리본 접힘선과 박스 테두리 정렬
badge_y = 1431 # Figma에서 badge 이미지 top Y
box_y = 1449 # Figma에서 box top Y
fold_offset = box_y - badge_y # = 18px (Figma 기준)
# 스케일 적용
fold_offset_css = round(fold_offset * scale) # = 12px (CSS)
```
**금지: "좀 더 올려볼게요" 식의 시행착오 px 조정**
### 3.3 이미지 자산 크기 계산
```python
# Figma 원본 크기에 스케일 적용
ribbon_width_css = round(badge_img_width * scale)
ribbon_height_css = round(badge_img_height * scale)
# 비율 계산 (CSS에서 width만 지정하면 height는 자동)
aspect_ratio = badge_img_width / badge_img_height
```
### 3.4 패딩/여백 계산
```python
# 리본이 박스 안에 들어오는 높이 = 리본 전체 높이 - 접힘선 오프셋
ribbon_inside_box = ribbon_height_css - fold_offset_css
# 박스 상단 패딩 = 리본 침입 높이 + 여유
box_padding_top = ribbon_inside_box + 6 # 6px 여유
```
### 3.5 실제 계산 예시 (Frame 2)
```
입력 (Figma 원본):
badge 이미지: 508×94px, y=1431
box: y=1449
frame width: 1808px
계산:
scale = 1200/1808 = 0.6637
ribbon_w = 508 × 0.6637 = 337px
ribbon_h = 94 × 0.6637 = 62px
fold_offset = (1449-1431) × 0.6637 = 12px
ribbon_below_fold = 62 - 12 = 50px
box_padding_top = 50 + 6 = 56px
CSS 출력:
.ribbon { width: 337px; top: -12px; }
.box { padding-top: 56px; }
```
---
## 4. 이미지 자산 처리
### 4.1 CSS로 만들면 안 되는 것
| 요소 | 이유 | 처리 |
|------|------|------|
| 3D 리본/두루마리 | 입체감, 그림자, 곡면 → CSS 불가 | Figma에서 PNG 추출 |
| 복잡한 그라디언트 배경 | 다중 정지점, 비선형 → CSS 근사 불가 | 이미지 사용 |
| 아이콘 이미지 | 디자이너가 만든 고유 자산 | 원본 이미지 사용 |
### 4.2 CSS로 만들 수 있는 것
| 요소 | CSS 구현 |
|------|---------|
| 단색/2색 그라디언트 배경 | `linear-gradient()` |
| 둥근 모서리 테두리 박스 | `border + border-radius` |
| 텍스트 스타일 | `font-size, font-weight, color` |
| 그리드/플렉스 레이아웃 | `display: grid / flex` |
| 구분선 | `border` or `background` |
### 4.3 이미지 추출 및 저장
```bash
# Figma API로 특정 노드 이미지 추출
curl -s -H "X-Figma-Token: {TOKEN}" \
"https://api.figma.com/v1/images/{FILE_KEY}?ids={NODE_ID}&format=png&scale=2"
# 다운로드 → static/figma-assets/ 에 저장
curl -s -o static/figma-assets/{name}.png "{S3_URL}"
```
저장 위치: `static/figma-assets/`
---
## 5. 비교 리뷰 페이지 작성법
### 5.1 레이아웃
```
같은 폭으로 위/아래 배치 (좌/우 아님 — 크기 차이 문제)
┌─ 빨간 테두리 ──────────────┐
│ Figma Original (PNG) │
└─────────────────────────────┘
─ 구분선 ─
┌─ 초록 테두리 ──────────────┐
│ HTML Block │
└─────────────────────────────┘
```
### 5.2 HTML 스케일링
```css
.html-inner {
width: 1280px; /* 슬라이드 원본 크기 */
transform-origin: top left;
transform: scale(0.74); /* 960px 컨테이너에 맞춤: 960/1280 */
}
```
### 5.3 비교 리뷰 파일 위치
`data/figma_ref/comparison.html`
---
## 6. Jinja2 템플릿 변환 규칙
### 6.1 고정값 → 변수
```html
<!-- Figma 원본의 텍스트 → Jinja2 변수 -->
<span>정책 달성</span><span>{{ badge_title }}</span>
<span>Engn. Solution</span><span>{{ left_title }}</span>
```
### 6.2 반복 요소 → 루프
```html
<!-- N개 카드 → for loop -->
{% for card in cards %}
<div class="card">{{ card.title }}</div>
{% endfor %}
```
### 6.3 이미지 자산 → 슬롯
```html
<!-- 리본 이미지: 색상에 따라 다른 자산 사용 가능 -->
<img src="{{ ribbon_image | default('figma-assets/badge_solution.png') }}">
```
### 6.4 계산된 CSS → CSS 변수
```html
<!-- 수학적 계산 결과를 CSS 변수로 -->
<div style="--ribbon-width: {{ ribbon_width }}px; --fold-offset: {{ fold_offset }}px;">
```
---
## 7. 디자인 언어 vs 콘텐츠 전용 구분
### 디자인 언어 (블록에 포함, 재사용 가능)
- 색상 팔레트 (warm 테마: 브라운, 틸, 베이지)
- 타이포그래피 위계 (크기, 굵기 단계)
- 레이아웃 구조 (2열 비교, N열 카드 등)
- 장식 요소 (3D 리본, 둥근 컨테이너)
### 콘텐츠 전용 (블록에 포함하지 않음)
- 특정 텍스트 ("디지털전환은 사용자...")
- 특정 아이콘 이미지 (brain, thunder 등)
- 도메인 전문 용어 (DfMA, Engn. Solution)
---
## 8. 파일 구조
```
design_agent/
├── static/figma-assets/ ← Figma에서 추출한 이미지 자산
│ ├── badge_policy.png (틸 3D 리본)
│ ├── badge_solution.png (빨간 3D 리본)
│ ├── box_policy_container.png
│ └── box_solution_cards.png
├── data/figma_ref/ ← 비교 리뷰용
│ ├── comparison.html (Figma vs HTML 비교 페이지)
│ ├── frame2_1-5.png (Figma 원본 PNG)
│ ├── frame3_1-35.png
│ └── frame4_1-49.png
├── templates/blocks/cards/ ← 블록 템플릿
│ ├── hero-icon-cards.html
│ ├── compare-2col-badge.html
│ └── compare-detail-gradient.html
├── FIGMA-DESIGN-LANGUAGE.md ← 디자인 언어 분석 결과
├── FIGMA-EXTRACTION.md ← 이 문서
└── PHASE-FIGMA-BLOCKS.md ← 블록 설계 명세
```
---
## 9. 고급 레이아웃 패턴
### 9.1 좌/우 열 섹션 Y선 정렬 (CSS Grid 행 공유)
2열 비교에서 좌/우 섹션 제목이 같은 Y선에 있어야 할 때:
**문제**: 각 열을 독립 flex-column으로 만들면, 좌측 섹션 본문이 길면 우측 다음 섹션이 밀림.
```
flex-column (잘못):
좌: [제목1] [긴본문] [제목2]
우: [제목1] [짧은본문] [제목2] ← 제목2가 좌측과 Y가 다름
```
**해결**: CSS Grid 2열 × N행으로 행을 공유하면 자동 정렬.
```css
.block {
display: grid;
grid-template-columns: 1fr 1fr; /* 2열 */
grid-template-rows: auto auto auto auto; /* 헤더 + N행 */
}
```
```
Grid (올바름):
[좌 헤더] [우 헤더] ← Row 0
[좌 섹션1] [우 섹션1] ← Row 1 (행 높이 = max(좌,우))
[좌 섹션2] [우 섹션2] ← Row 2 (Y선 자동 정렬!)
```
**실제 계산 (Frame 4)**:
```
Figma Y좌표:
Row 1: 좌 1166, 우 1166 → 0px 차이 (이미 정렬)
Row 2: 좌 1529, 우 1467 → 62px 차이 (Grid가 해결)
Row 3: 좌 1845, 우 1845 → 0px 차이 (이미 정렬)
원인: Row 1 좌측에 As-Is→To-Be 구조가 있어서 본문이 62px 더 높음
```
### 9.2 As-Is → To-Be 수평 서브 레이아웃
한 섹션 안에서 변환 전/후를 수평 배치할 때:
```html
<div class="asis-tobe">
<div class="asis">
<div class="bullet">이전 상태 1</div>
<div class="bullet">이전 상태 2</div>
</div>
<img src="arrow.png" class="arrow" alt="→">
<div class="tobe">
<div class="bullet">변환 후 1</div>
<div class="bullet">변환 후 2</div>
</div>
</div>
```
```css
.asis-tobe { display: flex; align-items: center; gap: 8px; }
.asis, .tobe { flex: 1; }
.arrow { width: 60px; height: auto; flex-shrink: 0; }
```
**Figma 좌표로 검증**:
```
As-Is: x=2737, w=539
Arrow: x=3375, w=252
To-Be: x=3687, w=672
→ 세 요소가 같은 Y(1269)에 수평 배치됨을 좌표로 확인
```
### 9.3 3D 리본/두루마리 배지 정렬 공식
리본 이미지의 접힘선(fold-back)이 박스 테두리와 정확히 일치해야 할 때:
```
┌── 리본 이미지 ──────────────┐
│ 접힘 삼각형 (fold) │ ← fold_offset (이미지 top에서)
│ 리본 본체 │
│ │
└──────────────────────────────┘
════════════════════════════════ ← 박스 top border (여기에 fold가 일치해야 함)
┌── 박스 ──────────────────────┐
│ padding-top = ribbon_below │
│ 콘텐츠 시작 │
계산:
fold_offset = (box_y - badge_y) × scale → CSS: top 값
ribbon_below = ribbon_height - fold_offset → 박스 안 침입 높이
box_padding_top = ribbon_below + 여유(6px) → 콘텐츠 겹침 방지
```
**핵심**: 리본을 올리거나 내리는 게 아니라, **박스의 위치를 계산**하는 것.
- `top: -fold_offset` → 리본 접힘선 = 박스 top border
- 리본은 그대로, 박스와의 관계만 수학적으로 결정
---
## 10. 실수 방지 (Anti-patterns)
### 10.1 절대 하면 안 되는 것
| Anti-pattern | 왜 안 되는지 | 올바른 방법 |
|-------------|------------|-----------|
| px 시행착오 조정 ("좀 더 올려볼게") | 3번 이상 실패, 시간 낭비 | Figma 좌표에서 수학적 계산 |
| 3D 효과를 CSS로 재현 | 평면적이라 품질 차이 심각 | Figma에서 PNG 추출 |
| 비교 리뷰를 좌/우 배치 | 크기 차이로 비교 불가 | 위/아래 같은 폭으로 배치 |
| Jinja2 템플릿을 브라우저에서 직접 열기 | 변수 미렌더, 이미지 경로 깨짐 | comparison.html 또는 FastAPI로 확인 |
| 독립 flex-column으로 2열 비교 | 행 정렬 안 됨 | CSS Grid 행 공유 |
| 느낌으로 폰트/색상 설정 | Figma와 다른 결과물 | Figma API에서 정확한 값 추출 |
### 10.2 반드시 해야 하는 것
| 원칙 | 이유 |
|------|------|
| CSS 주석에 계산 근거 기록 | 나중에 왜 이 값인지 추적 가능 |
| 비교 리뷰 후 진행 | 디자인 차이를 사전에 발견 |
| 이미지 자산은 `static/figma-assets/`에 저장 | FastAPI가 서빙, 경로 일관성 |
| `comparison.html`에 모든 프레임 포함 | 한 페이지에서 전체 리뷰 가능 |
| Figma 노드 ID 기록 | 나중에 업데이트된 디자인 재추출 가능 |
---
## 11. Figma 소스 정보
### 현재 등록된 Figma 파일
| 항목 | 값 |
|------|---|
| File Key | `9S6LsQyO6zlRxtiqZccOUM` |
| Page | Page 1 (0:1) |
| Frame 2 (hero-icon-cards) | Node `1:5` |
| Frame 3 (compare-2col-badge) | Node `1:35` |
| Frame 4 (compare-detail-gradient) | Node `1:49` |
| Badge (빨간 리본) | Node `1:33` (image 4019) |
| Badge (틸 리본) | Node `1:43` (image 2197) |
| Arrow (As-Is→To-Be) | Node `1:67` (image 2645) |
| Box (빨간 테두리) | Node `1:12` (Rectangle 42894) |
| Box (틸 테두리) | Node `1:37` (Rectangle 42598) |
---
## 12. 체크리스트
새 Figma 프레임을 블록으로 변환할 때:
- [ ] Figma API로 노드 데이터 추출 (좌표, 크기, 색상, 폰트)
- [ ] PNG 렌더링 다운로드 (scale=2)
- [ ] 복잡한 비주얼 요소 식별 → 이미지로 추출 (CSS로 만들지 않음)
- [ ] 스케일 팩터 계산 (1200 / frame_width)
- [ ] 핵심 정렬 포인트 수학적 계산 (좌표 차이 × 스케일)
- [ ] CSS 값 도출 (계산 근거를 주석으로 기록)
- [ ] 비교 리뷰 페이지에 추가 (위/아래 같은 폭)
- [ ] 사용자 피드백 확인
- [ ] Jinja2 템플릿 변환 (고정값→변수, 반복→루프)
- [ ] catalog.yaml 등록

463
PHASE-FIGMA-BLOCKS.md Normal file
View File

@@ -0,0 +1,463 @@
# Phase 2: Figma Block Design Specification
> 3개 블록 + 2개 서브 컴포넌트 상세 설계
> 기준: FIGMA-DESIGN-LANGUAGE.md 분석 결과
---
## Block 1: `hero-icon-cards`
### 1.1 시각적 구조
```
┌──────────────────────────────────────────────┐
│ [Hero Statement - 큰 텍스트, 중앙] │ ← zone: header or full-width
│ │
│ ┌─[Badge Title]─┐ │
│──────────┤ ├───────────────────│
│ ┌─────┐ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │icon │ │ │icon │ │icon │ │icon │ │icon │ │ ← N개 카드 (2~6)
│ │ │ │ │ │ │ │ │ │ │ │ │
│ │Title│ ╎ │Title│ │Title│ │Title│ │Title│ │ ← 세로 구분선
│ │(sub)│ │ │(sub)│ │(sub)│ │(sub)│ │(sub)│ │
│ └─────┘ │ └─────┘ └─────┘ └─────┘ └─────┘ │
│ └───────────────────────────────────│
└──────────────────────────────────────────────┘
```
### 1.2 슬롯 정의
| 슬롯 | 필수 | 타입 | 설명 |
|------|------|------|------|
| `statement` | O | string | Hero 메시지 (1-2줄) |
| `badge_title` | X | string | 배지 바 텍스트 |
| `cards[]` | O | array | 카드 배열 |
| `cards[].icon` | X | string | 아이콘 이미지 URL 또는 이모지 |
| `cards[].title` | O | string | 영문 또는 주제목 |
| `cards[].subtitle` | X | string | 한국어 부제 |
| `cards[].color` | X | string | 개별 카드 강조색 |
### 1.3 동적 재구성 규칙
#### 그리드 계산
```
입력: N = cards.length, W = container_width_px
N ≤ 5: 1행 N열
col_count = N
card_width = (W - padding*2 - gap*(N-1)) / N
N = 6: 1행 6열 (gap 축소)
col_count = 6
gap = 8px (기본 16px에서 축소)
N > 6: 2행
col_count = ceil(N / 2)
row_count = 2
```
#### 폰트 스케일링
```
card_width ≥ 200px → title: 20px, subtitle: 14px
card_width ≥ 150px → title: 16px, subtitle: 12px
card_width < 150px → title: 14px, subtitle: 11px
```
#### 높이 계산
```
hero_height = statement_lines * line_height + padding
badge_height = 44px (고정)
card_area_height = icon_height + title_lines * title_lh + subtitle_lh + padding
- 1행: card_area_height
- 2행: card_area_height * 2 + gap
total_min_height = hero_height + badge_height + card_area_height + gaps
```
### 1.4 catalog.yaml schema
```yaml
- id: hero-icon-cards
name: 히어로 문구 + 아이콘 카드
category: cards
template: blocks/cards/hero-icon-cards.html
height_cost: xlarge
min_height_px: 280
relation_types: [definition, flow]
min_items: 2
max_items: 6
visual: >
상단에 큰 Hero 메시지(24px bold, 중앙) + 배지 바 +
하단에 N열 아이콘 카드(둥근 흰색 컨테이너, 세로 구분선).
각 카드는 아이콘 이미지 + 영문 제목(20px/900) + 한국어 부제(14px/500).
when: >
핵심 목표나 가치를 N개 키워드로 선언할 때.
각 키워드에 아이콘이나 이미지가 있을 때.
"우리가 추구하는 5가지 가치" 같은 구조.
not_for: >
비교/대조 구조 → compare-2col-badge.
상세 설명이 길 때 → card-icon-desc.
순서/단계 → card-step-vertical 또는 process-horizontal.
purpose_fit: [핵심전달, 가치선언]
zone: full-width-only
slots:
required: [statement, cards[]]
optional: [badge_title, cards[].icon, cards[].subtitle, cards[].color]
schema:
statement:
max_lines: 2
font_size: 24
ref_chars:
body: 60
note: "24px bold, 중앙정렬, 흰색 스트로크"
badge_title:
max_lines: 1
font_size: 18
ref_chars:
body: 20
note: "18px bold white, 배지 바 위"
card_title:
max_lines: 2
font_size: 20
ref_chars:
body: 15
note: "20px black/900, 중앙정렬"
card_subtitle:
max_lines: 1
font_size: 14
ref_chars:
body: 10
note: "14px medium, 한국어 부제"
padding_overhead_px: 60
padding_h_px: 32
```
---
## Block 2: `compare-2col-badge`
### 2.1 시각적 구조
```
┌──────────────────────────────────────────────┐
│ ┌─[Badge Title]─┐ │
│────────────┤ ├─────────────────│
│ │
│ ┌── Left Column ──┐ ╎ ┌── Right Column ──┐ │
│ │ │ ╎ │ │ │
│ │ [Big Title] │ ╎ │ [Big Title] │ │
│ │ │ ╎ │ │ │
│ │ body text... │ ╎ │ body text... │ │
│ │ body text... │ ╎ │ body text... │ │
│ │ │ ╎ │ │ │
│ └──────────────────┘ ╎ └──────────────────┘ │
│ │
│ [Optional: Hero Statement] │
└──────────────────────────────────────────────┘
```
### 2.2 슬롯 정의
| 슬롯 | 필수 | 타입 | 설명 |
|------|------|------|------|
| `badge_title` | O | string | 배지 바 텍스트 |
| `left_title` | O | string | 좌측 열 대제목 |
| `left_body` | O | string | 좌측 열 본문 |
| `right_title` | O | string | 우측 열 대제목 |
| `right_body` | O | string | 우측 열 본문 |
| `statement` | X | string | 하단 Hero 메시지 |
| `left_color` | X | string | 좌측 강조색 (기본: --color-teal) |
| `right_color` | X | string | 우측 강조색 (기본: --color-teal) |
### 2.3 동적 재구성 규칙
#### 레이아웃 계산
```
container_width = 컨테이너 전체 폭
padding_h = 32px * 2
2열 모드 (기본):
col_width = (container_width - padding_h - divider_gap) / 2
divider_gap = 32px
1열 모드 (sidebar zone, 폭 < 500px):
좌/우가 세로 스택
col_width = container_width - padding_h
```
#### 높이 계산
```
badge_height = 44px
left_height = title_height + body_lines * line_height + padding
right_height = title_height + body_lines * line_height + padding
content_height = max(left_height, right_height)
statement_height = statement ? (statement_lines * 28 + 16) : 0
total = badge_height + content_height + statement_height + gaps
```
#### 텍스트 피팅
```
col_width에 따른 body 글자수 제한:
col_width ≥ 500px → ~40자/줄, font: 16px
col_width ≥ 350px → ~28자/줄, font: 14px
col_width < 350px → ~20자/줄, font: 13px
```
### 2.4 catalog.yaml schema
```yaml
- id: compare-2col-badge
name: 배지 헤더 2열 비교
category: cards
template: blocks/cards/compare-2col-badge.html
height_cost: large
min_height_px: 200
relation_types: [comparison, contrast]
visual: >
상단 배지 바(이미지/그라디언트 배경 + 흰색 텍스트) 아래
2열 비교 레이아웃. 좌/우 각각 대제목(24px/900) + 본문(16px/700).
중앙 세로 구분선. 둥근 흰색 컨테이너(r:20).
선택적 하단 Hero 메시지.
when: >
두 개념/방법/전략을 나란히 비교할 때.
배지 헤더로 상위 주제를 명시.
예: "Engn. Solution vs DfMA", "현재 vs 미래"
not_for: >
3개 이상 항목 비교 → compare-3col-badge.
장/단점 목록 → comparison-2col.
상세 내용이 길고 섹션이 많을 때 → compare-detail-gradient.
purpose_fit: [비교대조, 개념정의]
zone: full-width-only
slots:
required: [badge_title, left_title, left_body, right_title, right_body]
optional: [statement, left_color, right_color]
schema:
badge_title:
max_lines: 1
font_size: 18
ref_chars:
body: 15
note: "18px bold white, 배지 바"
left_title:
max_lines: 1
font_size: 24
ref_chars:
body: 15
note: "24px black/900, 흰색 스트로크"
left_body:
max_lines: 6
font_size: 16
ref_chars:
body: 200
note: "16px/700, 틸 색상"
right_title:
max_lines: 1
font_size: 24
ref_chars:
body: 15
note: "24px black/900, 흰색 스트로크"
right_body:
max_lines: 6
font_size: 16
ref_chars:
body: 200
note: "16px/700, 틸 색상"
statement:
max_lines: 2
font_size: 20
ref_chars:
body: 50
note: "20px bold, 중앙정렬"
padding_overhead_px: 56
padding_h_px: 32
```
---
## Block 3: `compare-detail-gradient`
### 3.1 시각적 구조
```
┌──────────────────────────────────────────────────────────┐
│ ┌───── Left Header Bar (gradient) ─────┐┌── Right ─────┐│
│ │ [Left Column Title] ││ [Right Title] ││
│ └──────────────────────────────────────┘└───────────────┘│
│ ┌─────── Left BG (warm) ──────┐┌──── Right BG (teal) ──┐│
│ │ ││ ││
│ │ [Section 1 Title] ││ [Section 1 Title] ││
│ │ • body text ││ • body text ││
│ │ • body text ││ • body text ││
│ │ ││ ││
│ │ [Section 2 Title] ││ [Section 2 Title] ││
│ │ • body text ││ • body text ││
│ │ • body text ││ • body text ││
│ │ ││ ││
│ │ [Section N Title] ││ [Section M Title] ││
│ │ • body text ││ • body text ││
│ └──────────────────────────────┘└───────────────────────┘│
└──────────────────────────────────────────────────────────┘
```
### 3.2 슬롯 정의
| 슬롯 | 필수 | 타입 | 설명 |
|------|------|------|------|
| `left_header` | O | string | 좌측 열 헤더 타이틀 |
| `right_header` | O | string | 우측 열 헤더 타이틀 |
| `left_sections[]` | O | array | 좌측 섹션 배열 |
| `left_sections[].title` | O | string | 섹션 소제목 |
| `left_sections[].body` | O | string | 섹션 본문 (줄바꿈 허용) |
| `right_sections[]` | O | array | 우측 섹션 배열 |
| `right_sections[].title` | O | string | 섹션 소제목 |
| `right_sections[].body` | O | string | 섹션 본문 |
| `left_color_theme` | X | string | 좌측 테마 (기본: warm) |
| `right_color_theme` | X | string | 우측 테마 (기본: teal) |
### 3.3 동적 재구성 규칙 (★ 가장 수학적으로 복잡)
#### 그리드 계산
```
container_width에서 2열 분할:
col_width = (container_width - gap) / 2
gap = 0px (그라디언트가 맞닿음)
```
#### 섹션 높이 계산 (핵심)
```
header_bar_height = 48px (고정)
각 섹션의 높이:
section_height(s) =
title_height(s.title, title_font_size, col_width) +
body_height(s.body, body_font_size, col_width) +
section_padding
title_height = ceil(char_count / chars_per_line) * title_line_height
body_height = line_count * body_line_height
chars_per_line = floor(col_width / (font_size * 0.55)) // 한글 평균 0.55em
좌측 전체:
left_total = header_bar + sum(section_height for s in left_sections) + gaps
우측 전체:
right_total = header_bar + sum(section_height for s in right_sections) + gaps
content_height = max(left_total, right_total)
```
#### 오버플로 방지 — Fit 검증
```
if content_height > container_available_height:
전략 1: 폰트 축소
body_font_size -= 1px (최소 12px)
재계산
전략 2: 섹션 본문 줄 수 제한
max_body_lines = floor(
(available_per_section - title_height) / body_line_height
)
available_per_section = (container_height - header*2 - gaps) / max(N_left, N_right)
전략 3: Kei 에스컬레이션 (기존 파이프라인)
content 요약 요청
```
#### 색상 테마 매핑
```
warm (좌측 기본):
header_gradient: rgba(165,161,150,0.10) → rgba(57,50,30,1.00)
section_title_color: var(--color-warm-brown)
bg: rgba(255,255,255,0.30) → rgba(57,50,30,0.30)
teal (우측 기본):
header_gradient: rgba(41,107,85,0.10) → rgba(3,33,24,1.00)
section_title_color: var(--color-dark-teal)
bg: rgba(41,107,85,0.30) → rgba(255,255,255,0.30)
```
### 3.4 catalog.yaml schema
```yaml
- id: compare-detail-gradient
name: 그라디언트 상세 2열 비교
category: cards
template: blocks/cards/compare-detail-gradient.html
height_cost: xlarge
min_height_px: 300
relation_types: [comparison, contrast, process]
min_items: 2 # 좌/우 최소 1섹션씩
max_items: 10 # 좌+우 합계
visual: >
좌우 그라디언트 배경(워 브라운 vs 다크틸)으로 나뉜 2열 비교.
각 열 상단에 그라디언트 헤더 바 + 큰 제목(28px/900).
하단에 N개 섹션(소제목 22px/900 + 본문 16px/700) 반복.
좌측은 따뜻한 톤(과정/As-Is), 우측은 차가운 톤(결과/To-Be).
when: >
두 카테고리를 상세하게 비교할 때.
각 카테고리에 여러 하위 항목이 있을 때.
과정 vs 결과, As-Is vs To-Be, 문제 vs 해결 구조.
not_for: >
간단한 2항목 비교(본문 짧을 때) → compare-2col-badge.
3열 비교 → compare-3col-badge.
비교가 아닌 단독 리스트 → dark-bullet-list.
purpose_fit: [비교대조, 구조시각화, 근거사례]
zone: full-width-only
slots:
required: [left_header, right_header, left_sections[], right_sections[]]
optional: [left_color_theme, right_color_theme]
schema:
left_header:
max_lines: 1
font_size: 28
ref_chars:
body: 20
note: "28px black/900, 그라디언트 바 위"
right_header:
max_lines: 1
font_size: 28
ref_chars:
body: 20
note: "28px black/900, 그라디언트 바 위"
section_title:
max_lines: 2
font_size: 22
ref_chars:
body: 30
note: "22px/900, 색상 테마별 (브라운 or 틸)"
section_body:
max_lines: 4
font_size: 16
ref_chars:
body: 120
note: "16px/700, black"
padding_overhead_px: 48
padding_h_px: 0
```
---
## 서브 컴포넌트
### S1. 장식 이미지 (3D 화살표 등)
- 블록이 아닌 **콘텐츠 이미지**로 처리
- `cards[].icon` 또는 별도 `decoration_image` 슬롯으로 전달
- 블록은 `<img>` 태그로 렌더링, 크기는 CSS로 컨테이너에 맞춤
### S2. CTA 버튼
- 독립 블록이 아닌 **다른 블록 내 선택적 요소**
- `cta_text` 슬롯으로 전달 (없으면 미표시)
- CSS: 그라디언트 바 + 둥근 버튼 (r:7)
---
## 구현 우선순위
| 순서 | 블록 | 이유 |
|------|------|------|
| 1 (파일럿) | `compare-2col-badge` | 중간 복잡도, 기존 compare-2col-split과 비교 검증 가능 |
| 2 | `hero-icon-cards` | N개 카드 그리드 계산 필요, 파일럿 경험 활용 |
| 3 | `compare-detail-gradient` | 가장 복잡 (N개 섹션 × 2열, 높이 균형, 오버플로 방지) |

261
scripts/figma_to_html.py Normal file
View File

@@ -0,0 +1,261 @@
"""Figma 프레임을 HTML/CSS로 변환.
기존 블록 템플릿(templates/blocks/)과 동일한 방식으로
Figma API 데이터에서 정확한 HTML을 생성한다.
Usage:
python scripts/figma_to_html.py data/runs/figma_beps_full.json templates/blocks/BEPs/
"""
from __future__ import annotations
import json
import math
import sys
from pathlib import Path
def get_fill_css(fills: list[dict]) -> tuple[str, str]:
"""fills 배열에서 CSS background와 color를 추출.
Returns: (background_css, color_css)
"""
bg = ""
color = ""
for f in fills:
if not f.get("visible", True):
continue
ftype = f.get("type", "")
if ftype == "SOLID":
c = f["color"]
r, g, b = int(c["r"] * 255), int(c["g"] * 255), int(c["b"] * 255)
a = f.get("opacity", c.get("a", 1))
if a < 0.99:
val = f"rgba({r},{g},{b},{a:.2f})"
else:
val = f"#{r:02x}{g:02x}{b:02x}"
bg = val
color = val
elif "GRADIENT" in ftype:
stops = f.get("gradientStops", [])
handles = f.get("gradientHandlePositions", [])
if len(stops) < 2:
continue
# 각도 계산
if len(handles) >= 2:
dx = handles[1]["x"] - handles[0]["x"]
dy = handles[1]["y"] - handles[0]["y"]
angle = math.degrees(math.atan2(dy, dx)) + 90
else:
angle = 180
stop_str = ",".join(
f"#{int(s['color']['r']*255):02x}{int(s['color']['g']*255):02x}{int(s['color']['b']*255):02x} {s['position']*100:.0f}%"
for s in stops
)
bg = f"linear-gradient({angle:.0f}deg,{stop_str})"
color = bg
elif ftype == "IMAGE":
bg = "__IMAGE__"
return bg, color
def node_to_html(node: dict, ox: float, oy: float, scale: float) -> str:
"""단일 노드를 HTML div로 변환."""
ntype = node.get("type", "")
name = node.get("name", "")
bb = node.get("absoluteBoundingBox")
if not bb or not bb.get("width"):
return ""
x = (bb["x"] - ox) * scale
y = (bb["y"] - oy) * scale
w = bb["width"] * scale
h = bb["height"] * scale
fills = node.get("fills", [])
visible_fills = [f for f in fills if f.get("visible", True)]
if ntype == "TEXT":
chars = node.get("characters", "")
if not chars:
return ""
style = node.get("style", {})
fs = style.get("fontSize", 12) * scale
fw = style.get("fontWeight", 400)
align_h = style.get("textAlignHorizontal", "LEFT").lower()
align_v = style.get("textAlignVertical", "TOP")
lh_px = style.get("lineHeightPx", 0)
lh = lh_px * scale if lh_px else fs * 1.5
ls = style.get("letterSpacing", 0) * scale
# 텍스트 fill 처리
has_gradient = any(
"GRADIENT" in f.get("type", "") for f in visible_fills
)
if has_gradient:
bg, _ = get_fill_css(
[f for f in visible_fills if "GRADIENT" in f.get("type", "")]
)
text_style = (
f"background:{bg};"
f"-webkit-background-clip:text;"
f"-webkit-text-fill-color:transparent;"
)
else:
_, c = get_fill_css(visible_fills)
text_style = f"color:{c or '#000'};"
lh_css = f"line-height:{lh:.1f}px;"
ls_css = f"letter-spacing:{ls:.1f}px;" if ls > 0.1 else ""
align_css = f"text-align:{align_h};" if align_h != "left" else ""
valign_css = (
"display:flex;align-items:center;" if align_v == "CENTER" else ""
)
text_html = chars.replace("\n", "<br>")
return (
f'<div style="position:absolute;left:{x:.1f}px;top:{y:.1f}px;'
f"width:{w:.1f}px;height:{h:.1f}px;"
f"font-size:{fs:.1f}px;font-weight:{fw};"
f"{text_style}{lh_css}{ls_css}{align_css}{valign_css}"
f'overflow:hidden;">{text_html}</div>'
)
elif ntype in ("RECTANGLE", "VECTOR"):
bg, _ = get_fill_css(visible_fills)
if not bg:
return ""
if bg == "__IMAGE__":
# 이미지 placeholder
return (
f'<div style="position:absolute;left:{x:.1f}px;top:{y:.1f}px;'
f"width:{w:.1f}px;height:{h:.1f}px;"
f'background:#ddd;border:1px solid #ccc;"></div>'
)
cr = node.get("cornerRadius", 0)
cr_css = f"border-radius:{cr * scale:.1f}px;" if cr > 0 else ""
strokes = node.get("strokes", [])
stroke_css = ""
if strokes:
for s in strokes:
if not s.get("visible", True):
continue
sc = s.get("color", {})
sr = int(sc.get("r", 0) * 255)
sg = int(sc.get("g", 0) * 255)
sb = int(sc.get("b", 0) * 255)
sw = node.get("strokeWeight", 1) * scale
stroke_css = (
f"border:{sw:.1f}px solid #{sr:02x}{sg:02x}{sb:02x};"
)
break
return (
f'<div style="position:absolute;left:{x:.1f}px;top:{y:.1f}px;'
f"width:{w:.1f}px;height:{h:.1f}px;"
f"background:{bg};{cr_css}{stroke_css}"
f'"></div>'
)
return ""
def frame_to_html(frame: dict, target_width: int = 1280) -> str:
"""프레임 전체를 HTML 문서로 변환."""
bb = frame.get("absoluteBoundingBox", {})
ox, oy = bb["x"], bb["y"]
fw, fh = bb["width"], bb["height"]
scale = target_width / fw
out_h = int(fh * scale)
# 모든 리프 노드 수집 (재귀)
elements: list[tuple[int, str]] = [] # (z-order, html)
def collect(node: dict, z: int = 0):
ntype = node.get("type", "")
children = node.get("children", [])
if ntype in ("GROUP", "FRAME", "CANVAS", "COMPONENT", "INSTANCE"):
# 컨테이너는 자식만 순회
for i, child in enumerate(children):
collect(child, z + i)
else:
html = node_to_html(node, ox, oy, scale)
if html:
elements.append((z, html))
for i, child in enumerate(children):
collect(child, z + i)
collect(frame, 0)
# 프레임 배경
frame_fills = frame.get("fills", [])
frame_bg, _ = get_fill_css(
[f for f in frame_fills if f.get("visible", True)]
)
frame_bg_css = f"background:{frame_bg};" if frame_bg else "background:#fff;"
# z-order 순으로 정렬 (rect 먼저, text 나중)
rects = [(z, h) for z, h in elements if "font-size" not in h]
texts = [(z, h) for z, h in elements if "font-size" in h]
parts = [
f"""<!DOCTYPE html><html><head><meta charset="UTF-8">
<style>
*{{margin:0;padding:0;box-sizing:border-box;}}
body{{background:#e5e5e5;padding:10px;font-family:'Pretendard Variable','Noto Sans KR',sans-serif;word-break:keep-all;}}
</style></head><body>
<div style="width:{target_width}px;height:{out_h}px;position:relative;{frame_bg_css}overflow:hidden;">"""
]
# rect를 먼저 (배경), text를 나중 (전경)
for _, h in sorted(rects, key=lambda x: x[0]):
parts.append(h)
for _, h in sorted(texts, key=lambda x: x[0]):
parts.append(h)
parts.append("</div></body></html>")
return "\n".join(parts)
def main():
if len(sys.argv) < 3:
print(
"Usage: python scripts/figma_to_html.py <figma_json> <output_dir>"
)
sys.exit(1)
figma_json = Path(sys.argv[1])
output_dir = Path(sys.argv[2])
output_dir.mkdir(parents=True, exist_ok=True)
data = json.loads(figma_json.read_text(encoding="utf-8"))
doc = data.get("document", {})
pages = doc.get("children", [])
for page in pages:
for i, frame in enumerate(page.get("children", [])):
if frame.get("type") not in ("FRAME", "COMPONENT"):
continue
name = frame.get("name", f"frame_{i}")
# 파일명 정리
safe_name = (
name.replace(" ", "_")
.replace("/", "_")
.replace("\\", "_")
)
html = frame_to_html(frame)
out_path = output_dir / f"{safe_name}.html"
out_path.write_text(html, encoding="utf-8")
bb = frame.get("absoluteBoundingBox", {})
print(
f" {safe_name}.html"
f" ({bb.get('width', 0):.0f}x{bb.get('height', 0):.0f}"
f" -> 1280px)"
)
print(f"\n완료: {output_dir}")
if __name__ == "__main__":
main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -34,6 +34,14 @@
--spacing-inner: 16px;
--spacing-small: 8px;
/* Warm 테마 (Figma 2026-04) */
--color-warm-brown: #5C3714;
--color-dark-teal: #084C56;
--color-teal: #227582;
--color-forest: #548235;
--color-beige: #E4D9C0;
--color-warm-yellow: #FAEDCB;
/* 기타 */
--radius: 6px;
--border-width: 1px;

View File

@@ -0,0 +1,29 @@
<!-- 상세보기 버튼: 그라데이션 배경 + 둥근 모서리 -->
<!--
📋 detail-button
─────────────────
용도: 팝업 또는 상세 페이지로 이동하는 버튼
슬롯: label (기본: "상세보기")
Figma 원본: Frame 1171276068
색상: 배경 그라데이션 #fff → #e3d8bf, 텍스트 #ffffff
-->
<div class="block-detail-btn">
<span class="db-label">{{ label | default('상세보기') }}</span>
</div>
<style>
.block-detail-btn {
display: inline-flex;
align-items: center;
justify-content: center;
background: linear-gradient(90deg, #ffffff, #e3d8bf);
border-radius: 7px;
padding: 4px 16px;
cursor: pointer;
}
.db-label {
font-size: var(--font-caption, 11px);
font-weight: 700;
color: #ffffff;
}
</style>

View File

@@ -0,0 +1,73 @@
<!-- Engn. Solution / DfMA 설명 + 정책 달성 -->
<!--
📋 engn-solution-dfma
─────────────────
용도: Engn. Solution과 DfMA 개념 설명 + 정책 달성 연결
슬롯:
items[]: 항목 목록
- title: 제목 (예: "Engn. Solution", "DfMA")
- description: 설명 텍스트
- color: 제목 색상 (예: "#217581")
main_text: 하단 메인 텍스트
policy_label: 정책 달성 라벨
Figma 원본: Frame 1171276070
-->
<div class="block-engn">
<div class="engn-items">
{% for item in items %}
<div class="engn-item">
<div class="engn-title" style="color: {{ item.color | default('#217581') }}">{{ item.title }}</div>
<div class="engn-desc">{{ item.description }}</div>
</div>
{% endfor %}
</div>
{% if main_text %}
<div class="engn-main">{{ main_text }}</div>
{% endif %}
{% if policy_label %}
<div class="engn-policy">{{ policy_label }}</div>
{% endif %}
</div>
<style>
.block-engn {
padding: 16px 20px;
}
.engn-items {
display: flex;
gap: 20px;
margin-bottom: 16px;
}
.engn-item {
flex: 1;
}
.engn-title {
font-size: var(--font-subtitle, 16px);
font-weight: 900;
margin-bottom: 6px;
}
.engn-desc {
font-size: var(--font-caption, 12px);
font-weight: 700;
color: #217581;
line-height: var(--line-height-ko, 1.7);
}
.engn-main {
font-size: var(--font-body, 14px);
font-weight: 700;
color: #000;
line-height: var(--line-height-ko, 1.7);
text-align: center;
margin-top: 16px;
}
.engn-policy {
background: #1a365d;
color: #fff;
font-size: var(--font-body, 14px);
font-weight: 700;
padding: 8px 20px;
text-align: center;
border-radius: 6px;
margin-top: 12px;
}
</style>

View File

@@ -0,0 +1,25 @@
<!-- 이미지 플레이스홀더: 이미지 슬롯만 있는 프레임 -->
<!--
📋 image-placeholder
─────────────────
용도: 이미지만 배치되는 프레임 (텍스트 없음)
슬롯: src (이미지 경로), alt (대체 텍스트), width, height
Figma 원본: Frame 1171276071 (949x275, 이미지만)
-->
<div class="block-img-ph">
<img src="{{ src }}" alt="{{ alt | default('') }}" style="width:100%;height:100%;object-fit:contain;">
</div>
<style>
.block-img-ph {
width: 100%;
height: 100%;
background: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
}
.block-img-ph img {
border-radius: var(--radius, 6px);
}
</style>

View File

@@ -0,0 +1,87 @@
<!-- 정책방향: 좌측 설명 + 우측 항목/이미지 -->
<!--
📋 policy-direction
─────────────────
용도: 인프라 건설산업의 정책방향 설명 (좌측 텍스트 + 우측 구조도)
슬롯:
title: 대제목 (예: "인프라 건설산업의 정책방향")
description: 설명 텍스트
sub_sections[]: 하위 섹션 목록
- title: 섹션 제목
- bullets[]: 불릿
questions[]: 질문 목록 (좌하단)
- text: 질문 텍스트
Figma 원본: Frame 1171276067
-->
<div class="block-policy">
<div class="pol-header">
<div class="pol-title">{{ title }}</div>
{% if description %}
<div class="pol-desc">{{ description }}</div>
{% endif %}
</div>
<div class="pol-content">
{% for section in sub_sections %}
<div class="pol-section">
<div class="pol-section-title">{{ section.title }}</div>
{% for bullet in section.bullets %}
<div class="pol-bullet">• {{ bullet }}</div>
{% endfor %}
</div>
{% endfor %}
</div>
{% if questions %}
<div class="pol-questions">
{% for q in questions %}
<div class="pol-q">{{ q.text }}</div>
{% endfor %}
</div>
{% endif %}
</div>
<style>
.block-policy {
padding: 16px 20px;
}
.pol-header {
margin-bottom: 14px;
}
.pol-title {
font-size: var(--font-subtitle, 16px);
font-weight: 700;
color: #000;
margin-bottom: 6px;
}
.pol-desc {
font-size: var(--font-caption, 12px);
font-weight: 500;
color: #000;
line-height: var(--line-height-ko, 1.7);
}
.pol-section {
margin-bottom: 12px;
}
.pol-section-title {
font-size: var(--font-body, 13px);
font-weight: 700;
color: #000;
margin-bottom: 4px;
}
.pol-bullet {
font-size: var(--font-caption, 11px);
color: #333;
line-height: var(--line-height-ko, 1.7);
padding-left: 8px;
}
.pol-questions {
margin-top: 16px;
border-top: 1px solid #ccc;
padding-top: 12px;
}
.pol-q {
font-size: var(--font-body, 13px);
font-weight: 700;
color: #c41e3a;
margin-bottom: 4px;
}
</style>

View File

@@ -0,0 +1,187 @@
<!-- Process/Product 2단 비교: 좌측 과정의 혁신 + 우측 결과의 혁신 -->
<!--
📋 process-product-2col
─────────────────
용도: Process 혁신과 Product 변화를 좌우 2단으로 비교
슬롯:
left_title, right_title: 좌/우 제목
left_sections[], right_sections[]: 섹션 목록 (title + bullets[])
left_compare: As-is → To-be 비교 (선택, title + left_items[] + right_items[])
Figma 원본: Frame 1171276073 (3848x1487 → 1280x495)
색상 (Figma에서 추출):
좌 배경: linear-gradient(180deg, #ffffff 46%, #39311e 100%)
우 배경: linear-gradient(0deg, #296b55 0%, #ffffff 56%)
좌 제목바: linear-gradient(270deg, #a4a096, #39311e)
우 제목바: linear-gradient(0deg, #296b55, #022017)
좌 제목텍스트: gradient(#296b55→#123328) + solid #3e3523
우 제목텍스트: gradient(#296b55→#123328) + solid #225e4a
좌 중제목: #5c3614, 16.6px/900
우 중제목: #084c56, 16.6px/900
본문: #000000, 13.3px/700, lineH=23.3px
-->
<div class="block-pp2">
<div class="pp2-col pp2-left">
<div class="pp2-header-bar pp2-header-bar--left">
<span class="pp2-header-text pp2-header-text--left">{{ left_title }}</span>
</div>
<div class="pp2-body">
{% if left_compare %}
<div class="pp2-mid-title pp2-mid-title--left">{{ left_compare.title }}</div>
<div class="pp2-compare">
<div class="pp2-compare-col">
{% for item in left_compare.left_items %}
<div class="pp2-body-text">• {{ item }}</div>
{% endfor %}
</div>
<div class="pp2-compare-arrow">
<img src="{{ arrow_image | default('') }}" alt="→" class="pp2-arrow-img">
</div>
<div class="pp2-compare-col">
{% for item in left_compare.right_items %}
<div class="pp2-body-text">• {{ item }}</div>
{% endfor %}
</div>
</div>
{% endif %}
{% for section in left_sections %}
<div class="pp2-mid-title pp2-mid-title--left">{{ section.title }}</div>
{% for bullet in section.bullets %}
<div class="pp2-body-text">• {{ bullet }}</div>
{% endfor %}
{% endfor %}
</div>
</div>
<div class="pp2-col pp2-right">
<div class="pp2-header-bar pp2-header-bar--right">
<span class="pp2-header-text pp2-header-text--right">{{ right_title }}</span>
</div>
<div class="pp2-body">
{% for section in right_sections %}
<div class="pp2-mid-title pp2-mid-title--right">{{ section.title }}</div>
{% for bullet in section.bullets %}
<div class="pp2-body-text">• {{ bullet }}</div>
{% endfor %}
{% endfor %}
</div>
</div>
</div>
<style>
.block-pp2 {
display: flex;
gap: 0;
width: 100%;
height: 100%;
}
.pp2-col {
flex: 1;
display: flex;
flex-direction: column;
min-height: 495px;
}
/* 좌측 배경: Figma gradient 180deg #fff 46% → #39311e 100% */
.pp2-left {
background: linear-gradient(180deg, #ffffff 46%, #39311e 100%);
}
/* 우측 배경: Figma gradient 0deg #296b55 0% → #fff 56% */
.pp2-right {
background: linear-gradient(0deg, #296b55 0%, #ffffff 56%);
}
/* 제목 바: height=47.2px, top=20.6px → padding-top으로 처리 */
.pp2-header-bar {
height: 47px;
margin-top: 21px;
display: flex;
align-items: center;
}
/* 좌 제목바: 우측 둥글게, 그라데이션 우→좌 */
.pp2-header-bar--left {
background: linear-gradient(270deg, #a4a096 0%, #39311e 100%);
border-radius: 0 24px 24px 0;
justify-content: center;
}
/* 우 제목바: 좌측 둥글게, 그라데이션 좌→우 */
.pp2-header-bar--right {
background: linear-gradient(90deg, #296b55 0%, #022017 100%);
border-radius: 24px 0 0 24px;
padding-left: 71px;
}
/* 제목 텍스트: 23.3px/900, letterSpacing=1.2px */
.pp2-header-text {
font-size: 23.3px;
font-weight: 900;
letter-spacing: 1.2px;
line-height: 33.7px;
}
/* 좌 제목: Figma solid fill #3e3523 */
.pp2-header-text--left {
color: #3e3523;
}
/* 우 제목: Figma solid fill #225e4a */
.pp2-header-text--right {
color: #225e4a;
}
/* 본문 영역 */
.pp2-body {
padding: 10px 27px;
flex: 1;
}
/* 중제목: 16.6px/900, lineH=31.6px */
.pp2-mid-title {
font-size: 16.6px;
font-weight: 900;
line-height: 31.6px;
margin-top: 8px;
}
.pp2-mid-title:first-child {
margin-top: 0;
}
.pp2-mid-title--left {
color: #5c3614;
}
.pp2-mid-title--right {
color: #084c56;
}
/* 본문: 13.3px/700, lineH=23.3px */
.pp2-body-text {
font-size: 13.3px;
font-weight: 700;
color: #000000;
line-height: 23.3px;
}
/* As-is → To-be 비교 */
.pp2-compare {
display: flex;
align-items: center;
gap: 4px;
margin-bottom: 10px;
}
.pp2-compare-col {
flex: 1;
}
.pp2-compare-arrow {
flex-shrink: 0;
width: 84px;
display: flex;
align-items: center;
justify-content: center;
}
.pp2-arrow-img {
width: 84px;
height: 30px;
object-fit: contain;
}
</style>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.0 MiB

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,141 @@
<!DOCTYPE html><html><head><meta charset="UTF-8">
<style>
*{margin:0;padding:0;box-sizing:border-box;}
body{background:#e5e5e5;padding:10px;font-family:'Pretendard Variable','Noto Sans KR',sans-serif;word-break:keep-all;}
.block-pp2 {
display: flex;
gap: 0;
width: 1280px;
height: 495px;
}
.pp2-col {
flex: 1;
display: flex;
flex-direction: column;
}
.pp2-left {
background: linear-gradient(180deg, #ffffff 46%, #39311e 100%);
}
.pp2-right {
background: linear-gradient(0deg, #296b55 0%, #ffffff 56%);
}
.pp2-header-bar {
height: 47px;
margin-top: 21px;
display: flex;
align-items: center;
}
.pp2-header-bar--left {
background: linear-gradient(270deg, #a4a096 0%, #39311e 100%);
border-radius: 0 24px 24px 0;
justify-content: center;
}
.pp2-header-bar--right {
background: linear-gradient(90deg, #296b55 0%, #022017 100%);
border-radius: 24px 0 0 24px;
padding-left: 71px;
}
.pp2-header-text {
font-size: 23.3px;
font-weight: 900;
letter-spacing: 1.2px;
line-height: 33.7px;
}
.pp2-header-text--left {
color: #3e3523;
}
.pp2-header-text--right {
color: #225e4a;
}
.pp2-body {
padding: 10px 27px;
flex: 1;
}
.pp2-mid-title {
font-size: 16.6px;
font-weight: 900;
line-height: 31.6px;
margin-top: 8px;
}
.pp2-mid-title:first-child {
margin-top: 0;
}
.pp2-mid-title--left {
color: #5c3614;
}
.pp2-mid-title--right {
color: #084c56;
}
.pp2-body-text {
font-size: 13.3px;
font-weight: 700;
color: #000000;
line-height: 23.3px;
}
.pp2-compare {
display: flex;
align-items: center;
gap: 4px;
margin-bottom: 10px;
}
.pp2-compare-col {
flex: 1;
}
.pp2-compare-arrow {
flex-shrink: 0;
width: 84px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
color: #5c3614;
}
</style></head><body>
<div class="block-pp2">
<div class="pp2-col pp2-left">
<div class="pp2-header-bar pp2-header-bar--left">
<span class="pp2-header-text pp2-header-text--left">과정 (Process)의 혁신</span>
</div>
<div class="pp2-body">
<div class="pp2-mid-title pp2-mid-title--left">Analogue 개념 기반 업무의 Digital Transformation</div>
<div class="pp2-compare">
<div class="pp2-compare-col">
<div class="pp2-body-text">• 개념, 도서, 행정 절차 중심</div>
<div class="pp2-body-text">• 2D 도면, 전문가, 규정</div>
<div class="pp2-body-text">• 업무 구분(단절), 책임</div>
</div>
<div class="pp2-compare-arrow"></div>
<div class="pp2-compare-col">
<div class="pp2-body-text">• 시각화된 목적물, 소통, 투명성 중심</div>
<div class="pp2-body-text">• 3D 모델, 참여자, 실체</div>
<div class="pp2-body-text">• 협업(융·복합), 창의성</div>
</div>
</div>
<div class="pp2-mid-title pp2-mid-title--left">위치기반의 3D 모델을 사용하는 Process 혁신</div>
<div class="pp2-body-text">• 위치기반(지리적, 지형, 지반상태 포함)의 GIS와 3D 모델(형상, 내용속성) 기반의 건설 정보를 포함하는 BIM의 연계를 통한 업무 프로세스의 혁신</div>
<div class="pp2-mid-title pp2-mid-title--left">사용자 중심의 Solution(S/W) 제공</div>
<div class="pp2-body-text">• 인프라 건설산업의 1차적인 Process 혁신은 등고선 중심의 지형도가 아닌 속성이 포함된 수치지형도와 본태 측량에서 획득한 3D 지반모델 구축 필수</div>
<div class="pp2-body-text">• 설계와 시공에 관련된 기술을 정리하고 디지털화하여 S/W로 기술 축적</div>
<div class="pp2-body-text">• 서로 다른 S/W로 작성되어 분절화된 Analogue 방식의 성과물과 정보물을 연계가 가능하도록 설계, 시공 Solution 제공</div>
</div>
</div>
<div class="pp2-col pp2-right">
<div class="pp2-header-bar pp2-header-bar--right">
<span class="pp2-header-text pp2-header-text--right">결과 (Products)의 혁신</span>
</div>
<div class="pp2-body">
<div class="pp2-mid-title pp2-mid-title--right">Copy & Paste로 인한 하향 평준화된 기존 성과품의 품질 향상</div>
<div class="pp2-body-text">• 과거 수작업으로 시행하면서 발생하던 오류 등의 최소화</div>
<div class="pp2-body-text">• 정확한 Data에 기반한 계획과 개선된 높은 품질의 성과물</div>
<div class="pp2-mid-title pp2-mid-title--right">Analogue 기반 도서 외 Digital 기반 정보물 추가</div>
<div class="pp2-body-text">• 규정에만 의존한 도면, 수량, 계산서, 시방서 등의 성과물에 3D 모델, 시뮬레이션 등의 Digital 기반 정보물(Information Data and Products)이 추가</div>
<div class="pp2-mid-title pp2-mid-title--right">Solution을 이용한 업무효율화(사용자 편의, 협업 및 의사소통 강화 등)</div>
<div class="pp2-body-text">• 디지털 기반 성과물인 Graphic 중심의 3D 모델, 시뮬레이션을 제대로 활용하기 위해서는 기존의 낮은 수준이 아니라 공학용 사이니지(H/W) 시스템이 필수로 갖춰야만 함</div>
<div class="pp2-body-text">• Engn. Solution을 통해 프로젝트에 관한 이슈를 함께 검토하고 논의하고 다양한 건설단계별 정보를 디지털 데이터로 저장하여 건설의 전 과정을 통합관리</div>
</div>
</div>
</div>
</body></html>

View File

@@ -0,0 +1,76 @@
<!-- Solution 제작 목표: 5개 목표 카드 + 중앙 텍스트 -->
<!--
📋 solution-goals
─────────────────
용도: Solution 제작의 5가지 핵심 목표 (Easy, Convenient, Quality, Cost, Speed)
슬롯:
main_text: 중앙 메인 텍스트
goals[]: 목표 카드 목록
- en: 영문 라벨 (예: "Easy Like Breath")
- ko: 한글 라벨 (예: "(쉬운 이해)")
- icon: 아이콘 이미지 경로 (선택)
Figma 원본: Frame 1171276072
색상: 제목 #ffffff on dark, 본문 #000000, 영문 gradient text
-->
<div class="block-sol-goals">
<div class="sg-title">Solution 제작 목표</div>
<div class="sg-main">{{ main_text }}</div>
<div class="sg-grid" style="--sg-count: {{ goals|length }}">
{% for g in goals %}
<div class="sg-card">
{% if g.icon %}<img class="sg-icon" src="{{ g.icon }}" alt="{{ g.en }}">{% endif %}
<div class="sg-en">{{ g.en }}</div>
<div class="sg-ko">{{ g.ko }}</div>
</div>
{% endfor %}
</div>
</div>
<style>
.block-sol-goals {
text-align: center;
padding: 20px;
}
.sg-title {
font-size: var(--font-subtitle, 16px);
font-weight: 700;
color: #ffffff;
background: #1a365d;
display: inline-block;
padding: 6px 20px;
border-radius: 6px;
margin-bottom: 16px;
}
.sg-main {
font-size: var(--font-body, 14px);
font-weight: 700;
color: #000;
line-height: var(--line-height-ko, 1.7);
margin-bottom: 20px;
}
.sg-grid {
display: grid;
grid-template-columns: repeat(var(--sg-count, 5), 1fr);
gap: 14px;
}
.sg-card {
text-align: center;
}
.sg-icon {
width: 48px;
height: 48px;
object-fit: contain;
margin-bottom: 6px;
}
.sg-en {
font-size: 13px;
font-weight: 900;
color: #1a365d;
margin-bottom: 2px;
}
.sg-ko {
font-size: 11px;
font-weight: 500;
color: #000;
}
</style>

View File

@@ -0,0 +1,106 @@
<!-- 배지 헤더 2열 비교: 3D 리본 탭 + 좌/우 비교 + 선택적 하단 문구 -->
<!--
📋 compare-2col-badge
─────────────────
용도: 두 개념/전략/기술을 나란히 비교. 3D 리본 탭이 컨테이너 위에 걸침.
슬롯: badge_title, left_title, left_body, right_title, right_body, statement(선택)
Figma 원본: Frame 3 (정책 달성 — 틸 리본 + 흰 둥근 컨테이너 + 2열 비교)
수학적 계산:
badge 517x88 at y=929, box at y=947, frame_w=1807
scale = 1200/1807 = 0.6641
ribbon: 343x58px, fold_offset: 12px, ribbon_below_fold: 46px
box padding-top: 52px
-->
<div class="block-c2b">
<div class="c2b-main">
{% if badge_title %}
<div class="c2b-ribbon">
<img src="{{ ribbon_image | default('/static/figma-assets/badge_policy.png') }}" class="c2b-ribbon-img" alt="">
<span class="c2b-ribbon-text">{{ badge_title }}</span>
</div>
{% endif %}
<div class="c2b-container">
<div class="c2b-col c2b-left">
<div class="c2b-title" style="color: {{ left_color | default('var(--color-teal, #227582)') }}">
{{ left_title }}
</div>
<div class="c2b-body" style="color: {{ left_color | default('var(--color-teal, #227582)') }}">
{{ left_body }}
</div>
</div>
<div class="c2b-divider"></div>
<div class="c2b-col c2b-right">
<div class="c2b-title" style="color: {{ right_color | default('var(--color-teal, #227582)') }}">
{{ right_title }}
</div>
<div class="c2b-body" style="color: {{ right_color | default('var(--color-teal, #227582)') }}">
{{ right_body }}
</div>
</div>
</div>
</div>
{% if statement %}
<div class="c2b-statement">{{ statement }}</div>
{% endif %}
</div>
<style>
.block-c2b { display: flex; flex-direction: column; width: 100%; }
/*
리본+박스 수학적 계산 (Figma 원본):
badge 517x88 at y=929, box at y=947
접힘선 = 18px = 이미지 높이의 20.5%
scale(1200/1807) → ribbon 343x58, fold top에서 12px, 아래 46px
*/
.c2b-main { position: relative; }
/* ── 3D 리본 배지 ── */
.c2b-ribbon {
position: absolute;
top: -12px; /* 접힘선이 박스 top border와 정확히 일치 */
left: 50%; transform: translateX(-50%);
z-index: 2; width: 343px; text-align: center;
}
.c2b-ribbon-img { width: 100%; height: auto; display: block; }
.c2b-ribbon-text {
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
font-size: 22px; font-weight: 700; color: #fff;
letter-spacing: 0.03em; white-space: nowrap;
text-shadow: 0 1px 3px rgba(0,0,0,0.3);
}
/* ── 틸 테두리 흰 박스 ── */
.c2b-container {
display: grid; grid-template-columns: 1fr auto 1fr;
background: #fff;
border: 2px solid #5a9ea8;
border-radius: 20px;
padding: 52px 40px 36px; /* top: ribbon_below(46) + 6px 여유 */
}
.c2b-divider { width: 1px; background: var(--color-border, #e2e8f0); margin: 0 32px; align-self: stretch; }
.c2b-col { display: flex; flex-direction: column; gap: 20px; min-width: 0; }
.c2b-title { font-size: 36px; font-weight: var(--weight-black, 900); line-height: 1.2; word-break: keep-all; }
.c2b-body { font-size: 22px; font-weight: var(--weight-bold, 700); line-height: 1.9; white-space: pre-line; word-break: keep-all; }
/* ── Statement ── */
.c2b-statement {
margin-top: 24px; text-align: center; font-size: 28px; font-weight: var(--weight-bold, 700);
color: var(--color-primary, #1e293b); line-height: 1.6; word-break: keep-all; padding: 0 20px;
}
.c2b-statement strong { font-weight: 900; font-size: 1.15em; }
.c2b-statement em { color: #dc2626; font-style: normal; font-weight: 900; font-size: 1.2em; }
/* ── Responsive: 좁은 컨테이너 ── */
@container (max-width: 500px) {
.c2b-container { grid-template-columns: 1fr; gap: 20px; }
.c2b-divider { width: 100%; height: 1px; margin: 0; }
.c2b-title { font-size: 24px; }
.c2b-body { font-size: 16px; }
.c2b-statement { font-size: 20px; }
}
</style>

View File

@@ -0,0 +1,113 @@
<!-- 그라디언트 상세 2열 비교: 비대칭 라운드 헤더 + 행 공유 + As-Is/To-Be -->
<!--
📋 compare-detail-gradient
─────────────────
용도: 두 카테고리의 상세 비교 (각각 N개 하위 섹션, 행 공유 정렬)
슬롯: left_header, right_header, sections[] (각 row에 left + right)
Figma 원본: Frame 4 (과정의 혁신 vs 결과의 혁신)
디자인:
- 좌측 헤더: 우측 라운드 + 텍스트 우정렬 (warm 그라디언트)
- 우측 헤더: 좌측 라운드 + 텍스트 좌정렬 (teal 그라디언트)
- CSS Grid 2열×N행: 좌/우 섹션 제목이 같은 Y선에 정렬
- 첫 번째 섹션: As-Is → 화살표 → To-Be 수평 배치 가능
수학적 계산:
좌/우 섹션 Y좌표 — Row1: 1166/1166(정렬), Row2: 1529/1467(62px차→Grid해결), Row3: 1845/1845(정렬)
-->
<div class="block-cdg">
<!-- Row 0: Headers -->
<div class="cdg-header cdg-header-warm">
<span class="cdg-header-text">{{ left_header }}</span>
</div>
<div class="cdg-header cdg-header-teal">
<span class="cdg-header-text">{{ right_header }}</span>
</div>
<!-- Rows: sections (행 공유로 좌/우 Y선 자동 정렬) -->
{% for row in sections %}
<div class="cdg-cell cdg-cell-warm">
<div class="cdg-sec-title cdg-title-warm">{{ row.left.title }}</div>
{% if row.left.asis is defined %}
<div class="cdg-asis-tobe">
<div class="cdg-asis">
{% for b in row.left.asis %}<div class="cdg-bullet">{{ b }}</div>{% endfor %}
</div>
<img src="{{ arrow_image | default('../../../static/figma-assets/arrow_asis_tobe.png') }}" class="cdg-arrow" alt="→">
<div class="cdg-tobe">
{% for b in row.left.tobe %}<div class="cdg-bullet">{{ b }}</div>{% endfor %}
</div>
</div>
{% elif row.left.bullets is defined %}
<div class="cdg-sec-body">
{% for b in row.left.bullets %}<div class="cdg-bullet">{{ b }}</div>{% endfor %}
</div>
{% else %}
<div class="cdg-sec-body">{{ row.left.body }}</div>
{% endif %}
</div>
<div class="cdg-cell cdg-cell-teal">
<div class="cdg-sec-title cdg-title-teal">{{ row.right.title }}</div>
{% if row.right.bullets is defined %}
<div class="cdg-sec-body">
{% for b in row.right.bullets %}<div class="cdg-bullet">{{ b }}</div>{% endfor %}
</div>
{% else %}
<div class="cdg-sec-body">{{ row.right.body }}</div>
{% endif %}
</div>
{% endfor %}
</div>
<style>
/*
CSS Grid: 2열 × (1+N)행
행 공유 → 좌/우 섹션 제목이 같은 Y선에 자동 정렬
Figma Row2 차이: 좌 y=1529, 우 y=1467 (62px) → Grid가 해결
*/
.block-cdg {
display: grid;
grid-template-columns: 1fr 1fr;
width: 100%;
overflow: hidden;
}
/* ── Headers (비대칭 라운드) ── */
.cdg-header { padding: 8px 20px; display: flex; min-height: 36px; align-items: center; }
.cdg-header-text { font-size: 15px; font-weight: var(--weight-black, 900); color: #000; line-height: 1.3; }
.cdg-header-warm {
background: linear-gradient(90deg, rgba(165,161,150,0.15), rgba(57,50,30,0.85));
border-radius: 0 28px 28px 0; justify-content: flex-end; text-align: right; margin-right: 4px;
}
.cdg-header-teal {
background: linear-gradient(90deg, rgba(3,33,24,0.85), rgba(41,107,85,0.15));
border-radius: 28px 0 0 28px; justify-content: flex-start; text-align: left; margin-left: 4px;
}
/* ── Grid Cells (행 공유 → 좌우 Y선 정렬) ── */
.cdg-cell {
padding: 8px 14px;
display: flex; flex-direction: column; gap: 4px;
align-self: start;
}
.cdg-cell-warm { background: linear-gradient(180deg, rgba(255,255,255,0.4), rgba(57,50,30,0.08)); }
.cdg-cell-teal { background: linear-gradient(180deg, rgba(41,107,85,0.06), rgba(255,255,255,0.4)); }
/* ── Section Title & Body ── */
.cdg-sec-title { font-size: 12px; font-weight: var(--weight-black, 900); line-height: 1.4; word-break: keep-all; margin-bottom: 2px; }
.cdg-title-warm { color: var(--color-warm-brown, #5C3714); }
.cdg-title-teal { color: var(--color-dark-teal, #084C56); }
.cdg-sec-body { padding-left: 8px; }
/* ── Bullets ── */
.cdg-bullet {
position: relative; padding-left: 14px;
font-size: 11px; font-weight: var(--weight-bold, 700); color: #1a1a1a;
line-height: 1.7; word-break: keep-all;
}
.cdg-bullet::before { content: '•'; position: absolute; left: 0; color: #666; }
/* ── As-Is → To-Be 수평 레이아웃 ── */
.cdg-asis-tobe { display: flex; align-items: center; gap: 8px; margin-top: 4px; }
.cdg-asis, .cdg-tobe { flex: 1; padding-left: 8px; }
.cdg-arrow { width: 60px; height: auto; flex-shrink: 0; opacity: 0.7; }
</style>

View File

@@ -0,0 +1,98 @@
<!-- 히어로 문구 + 아이콘 카드: 큰 선언문 + 3D 리본 배지 + N열 아이콘 카드 -->
<!--
📋 hero-icon-cards
─────────────────
용도: 핵심 목표/가치를 선언하고 N개 키워드 카드로 시각화
슬롯: statement, badge_title, cards[] (icon, title, subtitle)
Figma 원본: Frame 2 (Solution 제작 목표 — 다크 테마, 빨간 리본+테두리, 5열 카드)
수학적 계산:
badge 508x94 at y=1431, box at y=1449, frame_w=1808
scale = 1200/1808 = 0.6637
ribbon: 337x62px, fold_offset: 12px, ribbon_below_fold: 50px
box padding-top: 56px
-->
<div class="block-hic">
{% if statement %}
<div class="hic-statement">{{ statement }}</div>
{% endif %}
<div class="hic-card-area">
{% if badge_title %}
<div class="hic-ribbon">
<img src="{{ ribbon_image | default('/static/figma-assets/badge_solution.png') }}" class="hic-ribbon-img" alt="">
<span class="hic-ribbon-text">{{ badge_title }}</span>
</div>
{% endif %}
<div class="hic-box">
<div class="hic-cards">
{% for card in cards %}
<div class="hic-card">
{% if card.icon %}<div class="hic-icon">{{ card.icon }}</div>{% endif %}
<div class="hic-title">{{ card.title }}</div>
{% if card.subtitle %}<div class="hic-sub">({{ card.subtitle }})</div>{% endif %}
</div>
{% if not loop.last %}<div class="hic-sep"></div>{% endif %}
{% endfor %}
</div>
</div>
</div>
</div>
<style>
.block-hic {
display: flex; flex-direction: column; align-items: center;
width: 100%; background: #0a0a0a; border-radius: 16px;
padding: 36px 32px 28px; overflow: visible;
}
/* ── Hero Statement ── */
.hic-statement {
text-align: center; font-size: 28px; font-weight: var(--weight-bold, 700);
color: #fff; line-height: 1.6; word-break: keep-all;
margin-bottom: 28px; padding: 0 24px;
}
.hic-statement strong { font-weight: 900; font-size: 1.1em; }
.hic-statement em { color: #ef4444; font-style: normal; font-weight: 900; font-size: 1.15em; }
/*
리본+박스 수학적 계산 (Figma 원본):
badge 508x94 at y=1431, box at y=1449
접힘선 = 18px = 이미지 높이의 19.1%
scale(1200/1808) → ribbon 337x62, fold top에서 12px, 아래 50px
*/
.hic-card-area { position: relative; width: 100%; margin-top: 20px; }
.hic-ribbon {
position: absolute;
top: -12px; /* 접힘선이 박스 top border와 정확히 일치 */
left: 50%; transform: translateX(-50%);
z-index: 2; width: 337px; text-align: center;
}
.hic-ribbon-img { width: 100%; height: auto; display: block; }
.hic-ribbon-text {
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
font-size: 20px; font-weight: 700; color: #fff;
letter-spacing: 0.03em; white-space: nowrap;
text-shadow: 0 1px 3px rgba(0,0,0,0.3);
}
/* 빨간 테두리 흰 박스 */
.hic-box {
position: relative; background: #fff;
border: 6px solid #9b1b1b; border-radius: 20px;
padding: 56px 8px 8px; /* top: ribbon_below(50) + 6px 여유 */
}
.hic-cards { display: flex; align-items: stretch; width: 100%; }
.hic-card {
flex: 1; display: flex; flex-direction: column;
align-items: center; justify-content: center; text-align: center;
padding: 16px 12px 20px; gap: 8px;
}
.hic-sep { width: 1px; background: #e0e0e0; align-self: stretch; margin: 8px 0; }
.hic-icon { font-size: 48px; line-height: 1; margin-bottom: 8px; }
.hic-icon img { width: 64px; height: 64px; object-fit: contain; }
.hic-title { font-size: 20px; font-weight: var(--weight-black, 900); color: #1a1a1a; line-height: 1.3; }
.hic-sub { font-size: 15px; font-weight: var(--weight-medium, 500); color: #666; }
</style>

View File

@@ -0,0 +1,111 @@
<!-- 히어로 카드 변형1: 3D 리본 배지 + N열 카드 (제목+중제목+불릿 계층) -->
<!--
📋 hero-icon-cards_1
─────────────────
용도: 핵심 요건/목표를 N열 카드로 시각화. 각 카드에 제목+중제목+불릿 계층.
슬롯: statement(선택), badge_title, cards[] (title, sections[{title, bullets[]}])
변형 원본: hero-icon-cards
변경점: icon/title/subtitle → sections[{title, bullets[]}] 계층으로 변경
수학적 계산: hero-icon-cards와 동일
badge 508x94 at y=1431, box at y=1449, frame_w=1808
scale = 1200/1808 = 0.6637
ribbon: 337x62px, fold_offset: 12px, ribbon_below_fold: 50px
box padding-top: 56px
-->
<div class="block-hic">
{% if statement %}
<div class="hic-statement">{{ statement }}</div>
{% endif %}
<div class="hic-card-area">
{% if badge_title %}
<div class="hic-ribbon">
<img src="{{ ribbon_image | default('../../../static/figma-assets/badge_solution.png') }}" class="hic-ribbon-img" alt="">
<span class="hic-ribbon-text">{{ badge_title }}</span>
</div>
{% endif %}
<div class="hic-box">
<div class="hic-cards">
{% for card in cards %}
<div class="hic-card">
<div class="hic-title">{{ card.title }}</div>
{% for section in card.sections %}
<div class="hic-sec-title">{{ section.title }}</div>
{% for bullet in section.bullets %}
<div class="hic-bullet">{{ bullet }}</div>
{% endfor %}
{% endfor %}
</div>
{% if not loop.last %}<div class="hic-sep"></div>{% endif %}
{% endfor %}
</div>
</div>
</div>
</div>
<style>
.block-hic {
display: flex; flex-direction: column; align-items: center;
width: 100%; overflow: visible;
}
/* ── Hero Statement ── */
.hic-statement {
text-align: center; font-size: 16px; font-weight: var(--weight-bold, 700);
color: #1e293b; line-height: 1.6; word-break: keep-all;
margin-bottom: 8px; padding: 0 24px;
}
.hic-statement strong { font-weight: 900; font-size: 1.1em; }
.hic-statement em { color: #ef4444; font-style: normal; font-weight: 900; font-size: 1.15em; }
/*
리본+박스 수학적 계산 (Figma 원본):
badge 508x94 at y=1431, box at y=1449
접힘선 = 18px = 이미지 높이의 19.1%
scale(1200/1808) → ribbon 337x62, fold top에서 12px, 아래 50px
*/
.hic-card-area { position: relative; width: 100%; }
.hic-ribbon {
position: absolute;
top: -12px;
left: 50%; transform: translateX(-50%);
z-index: 2; width: 337px; text-align: center;
}
.hic-ribbon-img { width: 100%; height: auto; display: block; }
.hic-ribbon-text {
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
font-size: 14px; font-weight: 700; color: #fff;
letter-spacing: 0.03em; white-space: nowrap;
text-shadow: 0 1px 3px rgba(0,0,0,0.3);
}
/* 얇은 테두리 박스 */
.hic-box {
position: relative; background: #fff;
border: 1px solid #e2e8f0; border-radius: 8px;
padding: 36px 8px 8px;
}
.hic-cards { display: flex; align-items: stretch; width: 100%; }
.hic-card {
flex: 1; display: flex; flex-direction: column;
padding: 8px 10px 10px; gap: 0;
}
.hic-sep { width: 1px; background: #e0e0e0; align-self: stretch; margin: 4px 0; }
/* ── 카드 내부 (변형: icon → 소제목+불릿 계층) ── */
/* 중제목 (카드 헤더: 기술/사람/자연): 중앙 정렬 */
.hic-title { font-size: 14px; font-weight: var(--weight-black, 900); color: #0d7377; line-height: 1.3; text-align: center; margin-bottom: 6px; }
/* 소제목 (D1): 12px/700, 들여쓰기 1단 */
.hic-sec-title { font-size: 12px; font-weight: 700; color: #1a1a1a; line-height: 1.4; margin-top: 6px; margin-bottom: 2px; padding-left: 8px; }
.hic-sec-title:first-of-type { margin-top: 0; }
/* 불릿 (D2): 11px/500, 들여쓰기 2단 */
.hic-bullet {
position: relative; padding-left: 24px;
font-size: 11px; font-weight: 500; color: #334155;
line-height: 1.7; word-break: keep-all;
}
.hic-bullet::before { content: '•'; position: absolute; left: 14px; color: #666; }
</style>

View File

@@ -0,0 +1,169 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>compare-2col-badge 테스트</title>
<link rel="stylesheet" href="../../../static/tokens.css">
<link rel="stylesheet" href="../../../static/base.css">
<style>
body { background: #f0f0f0; padding: 40px; }
h2 { margin: 40px 0 16px; font-size: 18px; color: #333; }
.slide {
width: 1280px; height: 720px;
background: #fff; padding: 40px;
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
margin-bottom: 40px;
display: grid;
gap: 20px;
font-family: 'Pretendard Variable', 'Noto Sans KR', sans-serif;
}
</style>
</head>
<body>
<h2>Test 1: Figma Frame 3 원본 (Engn. Solution vs DfMA)</h2>
<div class="slide" style="grid-template-rows: auto 1fr auto;">
<!-- compare-2col-badge 블록 시작 -->
<div class="block-c2b">
<div class="c2b-badge">
<span class="c2b-badge-text">정책 달성</span>
</div>
<div class="c2b-container">
<div class="c2b-col c2b-left">
<div class="c2b-title" style="color: var(--color-teal, #227582)">Engn. Solution</div>
<div class="c2b-body" style="color: var(--color-teal, #227582)">목적 시설물에 대한 설계, 시공, 유지관리 정보를 고객이 쉽고 편리하게 사용하고, 편익이 발생하도록 제공하는 다양한 형태의 정보물과 이를 구현할 수 있는 S/W 및 H/W와 그 기술을 포함하는 서비스</div>
</div>
<div class="c2b-divider"></div>
<div class="c2b-col c2b-right">
<div class="c2b-title" style="color: var(--color-teal, #227582)">DfMA</div>
<div class="c2b-body" style="color: var(--color-teal, #227582)">Design for Manufacture and Assembly는 공장 생산, 현장조립이 가능한 설계를 의미하며, 현장 중심의 건설에서 공장 생산 방식으로 전환하는 OSC(Off Site Construction) 시스템을 위한 핵심기술</div>
</div>
</div>
<div class="c2b-statement">디지털전환은 <strong>사용자</strong>(발주자, 수급자, 엔지니어)가<br><strong>쉽고, 편하고, 편익</strong>이 있어야만 한다.</div>
</div>
<!-- 블록 끝 -->
</div>
<h2>Test 2: 다른 콘텐츠 (범용성 확인)</h2>
<div class="slide" style="grid-template-rows: auto 1fr;">
<div class="block-c2b">
<div class="c2b-badge">
<span class="c2b-badge-text">디지털 전환 전략</span>
</div>
<div class="c2b-container">
<div class="c2b-col c2b-left">
<div class="c2b-title" style="color: var(--color-warm-brown, #5C3714)">As-Is (현재)</div>
<div class="c2b-body" style="color: var(--color-warm-brown, #5C3714)">종이 도면 기반의 업무 프로세스
수작업 검증과 품질 관리
분절된 단계별 정보 전달
개인 경험에 의존하는 의사결정</div>
</div>
<div class="c2b-divider"></div>
<div class="c2b-col c2b-right">
<div class="c2b-title" style="color: var(--color-dark-teal, #084C56)">To-Be (미래)</div>
<div class="c2b-body" style="color: var(--color-dark-teal, #084C56)">3D 모델 기반의 통합 프로세스
자동화된 검증과 품질 관리
연속적인 디지털 정보 흐름
데이터 기반의 의사결정 지원</div>
</div>
</div>
</div>
</div>
<h2>Test 3: 짧은 콘텐츠 (최소 케이스)</h2>
<div class="slide" style="grid-template-rows: auto 1fr;">
<div class="block-c2b">
<div class="c2b-badge">
<span class="c2b-badge-text">비교</span>
</div>
<div class="c2b-container">
<div class="c2b-col c2b-left">
<div class="c2b-title" style="color: var(--color-teal, #227582)">BIM</div>
<div class="c2b-body" style="color: var(--color-teal, #227582)">건축정보모델링</div>
</div>
<div class="c2b-divider"></div>
<div class="c2b-col c2b-right">
<div class="c2b-title" style="color: var(--color-teal, #227582)">GIS</div>
<div class="c2b-body" style="color: var(--color-teal, #227582)">지리정보시스템</div>
</div>
</div>
</div>
</div>
<!-- 블록 자체 스타일 -->
<style>
.block-c2b {
display: flex;
flex-direction: column;
gap: 0;
width: 100%;
}
.c2b-badge {
background: linear-gradient(135deg, var(--color-dark-teal, #084C56), var(--color-teal, #227582));
border-radius: 12px 12px 0 0;
padding: 10px 24px;
text-align: center;
}
.c2b-badge-text {
font-size: 18px;
font-weight: 700;
color: #ffffff;
letter-spacing: 0.02em;
}
.c2b-container {
display: grid;
grid-template-columns: 1fr auto 1fr;
background: #ffffff;
border: 1px solid #e2e8f0;
border-top: none;
border-radius: 0 0 16px 16px;
padding: 24px 28px;
gap: 0;
}
.c2b-divider {
width: 1px;
background: #e2e8f0;
margin: 0 24px;
align-self: stretch;
}
.c2b-col {
display: flex;
flex-direction: column;
gap: 12px;
min-width: 0;
}
.c2b-title {
font-size: 22px;
font-weight: 900;
line-height: 1.3;
word-break: keep-all;
}
.c2b-body {
font-size: 15px;
font-weight: 500;
line-height: 1.7;
white-space: pre-line;
word-break: keep-all;
opacity: 0.85;
}
.c2b-statement {
margin-top: 16px;
text-align: center;
font-size: 18px;
font-weight: 700;
color: #1e293b;
line-height: 1.6;
word-break: keep-all;
padding: 0 16px;
}
</style>
</body>
</html>

View File

@@ -0,0 +1,149 @@
<!DOCTYPE html><html><head><meta charset="UTF-8">
<style>
*{margin:0;padding:0;box-sizing:border-box;}
body{background:#f0f0f0;padding:20px;font-family:'Pretendard Variable','Noto Sans KR',sans-serif;word-break:keep-all;}
</style></head><body>
<div style="width:1280px;background:#fff;padding:40px;">
<!-- 그라디언트 상세 2열 비교: 비대칭 라운드 헤더 + 행 공유 + As-Is/To-Be -->
<!--
📋 compare-detail-gradient
─────────────────
용도: 두 카테고리의 상세 비교 (각각 N개 하위 섹션, 행 공유 정렬)
슬롯: left_header, right_header, sections[] (각 row에 left + right)
Figma 원본: Frame 4 (과정의 혁신 vs 결과의 혁신)
디자인:
- 좌측 헤더: 우측 라운드 + 텍스트 우정렬 (warm 그라디언트)
- 우측 헤더: 좌측 라운드 + 텍스트 좌정렬 (teal 그라디언트)
- CSS Grid 2열×N행: 좌/우 섹션 제목이 같은 Y선에 정렬
- 첫 번째 섹션: As-Is → 화살표 → To-Be 수평 배치 가능
수학적 계산:
좌/우 섹션 Y좌표 — Row1: 1166/1166(정렬), Row2: 1529/1467(62px차→Grid해결), Row3: 1845/1845(정렬)
-->
<div class="block-cdg">
<!-- Row 0: Headers -->
<div class="cdg-header cdg-header-warm">
<span class="cdg-header-text">과정 (Process)의 혁신</span>
</div>
<div class="cdg-header cdg-header-teal">
<span class="cdg-header-text">결과 (Products)의 변화</span>
</div>
<!-- Rows: sections (행 공유로 좌/우 Y선 자동 정렬) -->
<div class="cdg-cell cdg-cell-warm">
<div class="cdg-sec-title cdg-title-warm">Analogue 기반 업무의 Digital Transformation</div>
<div class="cdg-asis-tobe">
<div class="cdg-asis">
<div class="cdg-bullet">개념·문서·행정 절차 중심</div><div class="cdg-bullet">2D 도면, 전문가, 규정</div><div class="cdg-bullet">업무 구분(단절), 책임</div>
</div>
<img src="/static/figma-assets/arrow_asis_tobe.png" class="cdg-arrow" alt="→">
<div class="cdg-tobe">
<div class="cdg-bullet">시각화된 목적물, 소통, 투명성 중심</div><div class="cdg-bullet">3D 모델, 참여자, 실체</div><div class="cdg-bullet">협업(융·복합), 창의성</div>
</div>
</div>
</div>
<div class="cdg-cell cdg-cell-teal">
<div class="cdg-sec-title cdg-title-teal">Copy & Paste로 인한 하향 평준화된 기존 성과물의 품질 향상</div>
<div class="cdg-sec-body">
<div class="cdg-bullet">과거 수작업으로 시행하면서 발생하던 오류 등의 최소화</div><div class="cdg-bullet">정확한 Data에 기반한 계획으로 고품질 성과물 도출</div>
</div>
</div>
<div class="cdg-cell cdg-cell-warm">
<div class="cdg-sec-title cdg-title-warm">위치기반의 3D 모델을 사용하는 Process 혁신</div>
<div class="cdg-sec-body">
<div class="cdg-bullet">지리·지형·지반 등 위치정보(GIS)와 3D모델(형상, 속성정보) 기반의 건설 정보를 포함하는 BIM의 연계를 통한 업무 프로세스의 혁신</div>
</div>
</div>
<div class="cdg-cell cdg-cell-teal">
<div class="cdg-sec-title cdg-title-teal">Analogue 기반 도서 외 Digital 기반 정보물 추가</div>
<div class="cdg-sec-body">
<div class="cdg-bullet">기존 성과물(도면, 수량, 계산서, 시방서 등)에 3D 모델, Simulation 등의 Digital 기반 정보물 추가</div>
</div>
</div>
<div class="cdg-cell cdg-cell-warm">
<div class="cdg-sec-title cdg-title-warm">사용자 중심의 Solution(S/W) 제공</div>
<div class="cdg-sec-body">
<div class="cdg-bullet">서로 다른 S/W로 작성되어 분절화된 Analogue 방식의 성과물과 정보물을 연계할 수 있는 설계·시공 Solution 제공</div>
</div>
</div>
<div class="cdg-cell cdg-cell-teal">
<div class="cdg-sec-title cdg-title-teal">Solution을 활용한 업무 효율화</div>
<div class="cdg-sec-body">
<div class="cdg-bullet">Engn. Solution을 통해 성과물에 관한 이슈를 함께 검토·논의하는 협업 환경 조성</div><div class="cdg-bullet">건설 단계별 정보를 디지털 데이터로 축적하여, 건설 전 과정을 통합관리</div>
</div>
</div>
</div>
<style>
/*
CSS Grid: 2열 × (1+N)행
행 공유 → 좌/우 섹션 제목이 같은 Y선에 자동 정렬
Figma Row2 차이: 좌 y=1529, 우 y=1467 (62px) → Grid가 해결
*/
.block-cdg {
display: grid;
grid-template-columns: 1fr 1fr;
width: 100%;
overflow: hidden;
}
/* ── Headers (비대칭 라운드) ── */
.cdg-header { padding: 12px 28px; display: flex; min-height: 52px; align-items: center; }
.cdg-header-text { font-size: 23.3px; font-weight: var(--weight-black, 900); color: #000; line-height: 1.3; }
.cdg-header-warm {
background: linear-gradient(90deg, rgba(165,161,150,0.15), rgba(57,50,30,0.85));
border-radius: 0 28px 28px 0; justify-content: flex-end; text-align: right; margin-right: 4px;
}
.cdg-header-teal {
background: linear-gradient(90deg, rgba(3,33,24,0.85), rgba(41,107,85,0.15));
border-radius: 28px 0 0 28px; justify-content: flex-start; text-align: left; margin-left: 4px;
}
/* ── Grid Cells (행 공유 → 좌우 Y선 정렬) ── */
.cdg-cell {
padding: 14px 20px;
display: flex; flex-direction: column; gap: 6px;
align-self: start;
}
.cdg-cell-warm { background: linear-gradient(180deg, rgba(255,255,255,0.4), rgba(57,50,30,0.08)); }
.cdg-cell-teal { background: linear-gradient(180deg, rgba(41,107,85,0.06), rgba(255,255,255,0.4)); }
/* ── Section Title & Body ── */
.cdg-sec-title { font-size: 16.6px; font-weight: var(--weight-black, 900); line-height: 1.4; word-break: keep-all; margin-bottom: 4px; }
.cdg-title-warm { color: var(--color-warm-brown, #5C3714); }
.cdg-title-teal { color: var(--color-dark-teal, #084C56); }
.cdg-sec-body { padding-left: 8px; }
/* ── Bullets ── */
.cdg-bullet {
position: relative; padding-left: 14px;
font-size: 13.3px; font-weight: var(--weight-bold, 700); color: #1a1a1a;
line-height: 23.3px; word-break: keep-all;
}
.cdg-bullet::before { content: '•'; position: absolute; left: 0; color: #666; }
/* ── As-Is → To-Be 수평 레이아웃 ── */
.cdg-asis-tobe { display: flex; align-items: center; gap: 8px; margin-top: 4px; }
.cdg-asis, .cdg-tobe { flex: 1; padding-left: 8px; }
.cdg-arrow { width: 60px; height: auto; flex-shrink: 0; opacity: 0.7; }
</style>
</div>
</body></html>

View File

@@ -0,0 +1,162 @@
<!DOCTYPE html><html><head><meta charset="UTF-8">
<style>
*{margin:0;padding:0;box-sizing:border-box;}
body{background:#f0f0f0;padding:20px;font-family:'Pretendard Variable','Noto Sans KR',sans-serif;word-break:keep-all;}
</style></head><body>
<div style="width:1280px;background:#fff;padding:40px;">
<!-- 히어로 카드 변형1: 3D 리본 배지 + N열 카드 (제목+중제목+불릿 계층) -->
<!--
📋 hero-icon-cards_1
─────────────────
용도: 핵심 요건/목표를 N열 카드로 시각화. 각 카드에 제목+중제목+불릿 계층.
슬롯: statement(선택), badge_title, cards[] (title, sections[{title, bullets[]}])
변형 원본: hero-icon-cards
변경점: icon/title/subtitle → sections[{title, bullets[]}] 계층으로 변경
수학적 계산: hero-icon-cards와 동일
badge 508x94 at y=1431, box at y=1449, frame_w=1808
scale = 1200/1808 = 0.6637
ribbon: 337x62px, fold_offset: 12px, ribbon_below_fold: 50px
box padding-top: 56px
-->
<div class="block-hic">
<div class="hic-card-area">
<div class="hic-ribbon">
<img src="../../../static/figma-assets/badge_solution.png" class="hic-ribbon-img" alt="">
<span class="hic-ribbon-text">DX 시행을 위한 필수 요건</span>
</div>
<div class="hic-box">
<div class="hic-cards">
<div class="hic-card">
<div class="hic-title">기술(디지털)</div>
<div class="hic-sec-title">Digital 기술(S/W, H/W)과 업무 Process의 통합</div>
<div class="hic-bullet">기존 업무 프로세스에 다양한 디지털 기술을 접목하여 업무 수행</div>
<div class="hic-bullet">프로젝트 전반에 걸친 업무 프로세스의 연결 및 조율</div>
<div class="hic-sec-title">분야별 전문 지식(설계, 시공, 유지관리 등) 보유</div>
<div class="hic-bullet">건설 전 단계에 대한 근본적인 이해와 지식 및 경험</div>
<div class="hic-bullet">최신 토목 기술 트랜드 및 표준 기준 등에 대한 높은 지식</div>
</div>
<div class="hic-sep"></div>
<div class="hic-card">
<div class="hic-title">사람(역량)</div>
<div class="hic-sec-title">혁신적 사고방식과 창의적 문제 해결 능력</div>
<div class="hic-bullet">기존 수행 방식과 관습적 사고 등에 의한 접근 방식 탈피</div>
<div class="hic-bullet">디지털 기술을 활용한 창의적, 혁신적인 솔루션 제시</div>
<div class="hic-sec-title">사용자 중심 사고와 DX 수행 경험</div>
<div class="hic-bullet">사용자의 요구와 기대를 충족시키는 설계 및 구현</div>
<div class="hic-bullet">시행착오를 포함한 수행 경험과 사용자 경험(UX)을 반영한 해결 방안 제시</div>
</div>
<div class="hic-sep"></div>
<div class="hic-card">
<div class="hic-title">자연(여건)</div>
<div class="hic-sec-title">지속적인 투자 및 실행 의지</div>
<div class="hic-bullet">기술 도입 초기 단계에 필요한 인력·기간·비용 등의 대규모 투자</div>
<div class="hic-bullet">기술 고도화를 위한 지속적인 개선 및 투자 체계 구축</div>
<div class="hic-bullet">변화와 혁신을 통해 부가가치를 창출하려는 실행 의지와 추진력</div>
</div>
</div>
</div>
</div>
</div>
<style>
.block-hic {
display: flex; flex-direction: column; align-items: center;
width: 100%; border-radius: 16px;
padding: 36px 32px 28px; overflow: visible;
}
/* ── Hero Statement ── */
.hic-statement {
text-align: center; font-size: 28px; font-weight: var(--weight-bold, 700);
color: #fff; line-height: 1.6; word-break: keep-all;
margin-bottom: 28px; padding: 0 24px;
}
.hic-statement strong { font-weight: 900; font-size: 1.1em; }
.hic-statement em { color: #ef4444; font-style: normal; font-weight: 900; font-size: 1.15em; }
/*
리본+박스 수학적 계산 (Figma 원본):
badge 508x94 at y=1431, box at y=1449
접힘선 = 18px = 이미지 높이의 19.1%
scale(1200/1808) → ribbon 337x62, fold top에서 12px, 아래 50px
*/
.hic-card-area { position: relative; width: 100%; margin-top: 20px; }
.hic-ribbon {
position: absolute;
top: -12px; /* 접힘선이 박스 top border와 정확히 일치 */
left: 50%; transform: translateX(-50%);
z-index: 2; width: 337px; text-align: center;
}
.hic-ribbon-img { width: 100%; height: auto; display: block; }
.hic-ribbon-text {
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
font-size: 20px; font-weight: 700; color: #fff;
letter-spacing: 0.03em; white-space: nowrap;
text-shadow: 0 1px 3px rgba(0,0,0,0.3);
}
/* 빨간 테두리 흰 박스 */
.hic-box {
position: relative; background: #fff;
border: 6px solid #9b1b1b; border-radius: 20px;
padding: 56px 8px 8px; /* top: ribbon_below(50) + 6px 여유 */
}
.hic-cards { display: flex; align-items: stretch; width: 100%; }
.hic-card {
flex: 1; display: flex; flex-direction: column;
padding: 16px 12px 20px; gap: 0;
}
.hic-sep { width: 1px; background: #e0e0e0; align-self: stretch; margin: 8px 0; }
/* ── 카드 내부 (변형: icon → 소제목+불릿 계층) ── */
/* 중제목 (카드 헤더: 기술/사람/자연): 중앙 정렬 */
.hic-title { font-size: 20px; font-weight: var(--weight-black, 900); color: #1a1a1a; line-height: 1.3; text-align: center; margin-bottom: 12px; }
/* 소제목 (D1): 16.6px/900, 들여쓰기 1단 = 16px */
.hic-sec-title { font-size: 16.6px; font-weight: 900; color: #1a1a1a; line-height: 1.4; margin-top: 8px; margin-bottom: 3px; padding-left: 16px; }
.hic-sec-title:first-of-type { margin-top: 0; }
/* 불릿 (D2): 13.3px/700, 들여쓰기 2단 = 36px, lineH=23.3px */
.hic-bullet {
position: relative; padding-left: 36px;
font-size: 13.3px; font-weight: 700; color: #334155;
line-height: 23.3px; word-break: keep-all;
}
.hic-bullet::before { content: '•'; position: absolute; left: 24px; color: #666; }
</style>
</div>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

View File

@@ -751,6 +751,227 @@ blocks:
note: 항목 수
padding_overhead_px: 22
padding_h_px: 32
- id: hero-icon-cards
name: 히어로 문구 + 아이콘 카드
category: cards
template: blocks/cards/hero-icon-cards.html
height_cost: xlarge
min_height_px: 280
relation_types:
- definition
- flow
min_items: 2
max_items: 6
visual: >
다크 배경. 상단 큰 Hero 메시지(28px bold, 중앙, 흰 텍스트, 빨간 강조).
3D 빨간 리본 배지가 빨간 테두리 흰 박스(r:20) 위에 걸침.
박스 안에 N열 아이콘 카드(구분선). 각 카드: 아이콘 + 영문 제목(20px/900) + 한국어 부제(15px/500).
visual_diff: >
- card-icon-desc: 밝은 배경, 이모지+제목+설명. 리본/테두리 없음.
- card-dark-overlay: 다크 배경 이미지+흰 텍스트. 카드 개별 배경.
- 이 블록: 다크 배경 + 빨간 리본 배지 + 빨간 테두리 흰 박스 안 N열 카드.
적합: 핵심 목표/가치를 N개 키워드로 선언하며 시각적 임팩트 필요할 때.
부적합: 상세 설명 → card-icon-desc, 비교 → compare-2col-badge.
when: >
핵심 목표나 가치를 N개 키워드로 선언할 때.
각 키워드에 아이콘이나 이미지가 있을 때.
프레젠테이션형 임팩트가 필요할 때.
not_for: >
비교/대조 → compare-2col-badge.
상세 설명 → card-icon-desc.
순서/단계 → process-horizontal.
purpose_fit:
- 핵심전달
zone: full-width-only
slots:
required:
- statement
- cards[]
optional:
- badge_title
- ribbon_image
- cards[].icon
- cards[].subtitle
schema:
statement:
max_lines: 2
font_size: 28
ref_chars:
body: 60
note: "28px bold white, 중앙, em=빨간 강조"
badge_title:
max_lines: 1
font_size: 20
ref_chars:
body: 15
note: "20px bold white, 3D 리본 위"
card_title:
max_lines: 2
font_size: 20
ref_chars:
body: 15
note: "20px black/900, 중앙정렬"
card_subtitle:
max_lines: 1
font_size: 15
ref_chars:
body: 10
note: "15px medium, 한국어 부제"
padding_overhead_px: 80
padding_h_px: 32
- id: compare-detail-gradient
name: 그라디언트 상세 2열 비교
category: cards
template: blocks/cards/compare-detail-gradient.html
height_cost: xlarge
min_height_px: 300
relation_types:
- comparison
- contrast
- process
min_items: 2
max_items: 10
visual: >
좌우 그라디언트 배경(워 브라운 vs 다크틸)으로 나뉜 2열 비교.
비대칭 라운드 헤더(좌: 우라운드+우정렬, 우: 좌라운드+좌정렬).
N개 섹션(소제목 18px/900 + 불릿 14px/700) 반복.
CSS Grid 행 공유로 좌/우 섹션 제목이 같은 Y선에 정렬.
첫 섹션에 As-Is → 화살표 → To-Be 수평 배치 가능.
visual_diff: >
- compare-2col-badge: 배지+2열, 단순 비교. 섹션 반복 없음.
- comparison-2col: 좌우 텍스트 블록 비교. 그라디언트 없음.
- 이 블록: 그라디언트+비대칭 헤더+N섹션 행 정렬. 가장 상세한 비교.
적합: 두 카테고리에 각각 3개+ 하위 항목이 있는 상세 비교.
부적합: 간단 비교 → compare-2col-badge, 표 형식 → compare-2col-split.
when: >
두 카테고리를 상세하게 비교할 때. 각 카테고리에 여러 하위 항목.
과정 vs 결과, As-Is vs To-Be, 문제 vs 해결 구조.
not_for: >
간단 비교(본문 짧을 때) → compare-2col-badge.
항목별 행 비교(표 형식) → compare-2col-split.
3열 비교 → compare-3col-badge.
purpose_fit:
- 비교대조
- 구조시각화
- 근거사례
zone: full-width-only
slots:
required:
- left_header
- right_header
- sections[]
optional:
- arrow_image
- sections[].left.asis
- sections[].left.tobe
schema:
left_header:
max_lines: 1
font_size: 26
ref_chars:
body: 20
note: "26px black/900, 비대칭 라운드 바"
right_header:
max_lines: 1
font_size: 26
ref_chars:
body: 20
note: "26px black/900, 비대칭 라운드 바"
section_title:
max_lines: 2
font_size: 18
ref_chars:
body: 30
note: "18px/900, 좌=브라운 우=틸"
section_body:
max_lines: 4
font_size: 14
ref_chars:
body: 120
note: "14px/700, 불릿 구조"
padding_overhead_px: 52
padding_h_px: 0
- id: compare-2col-badge
name: 배지 헤더 2열 비교
category: cards
template: blocks/cards/compare-2col-badge.html
height_cost: large
min_height_px: 200
relation_types:
- comparison
- contrast
visual: >
상단 배지 바(틸 그라디언트 + 흰색 텍스트, r:12) 아래
2열 비교 레이아웃. 좌/우 각각 대제목(22px/900) + 본문(15px/500).
중앙 세로 구분선. 흰색 컨테이너(r:16). 선택적 하단 Hero 메시지.
visual_diff: >
- compare-2col-split: 중앙 '기준 라벨' 열이 있는 3열 표. 행별 상세 비교.
- comparison-2col: 좌우 텍스트 블록 비교. 표 없이 자유 형식.
- compare-pill-pair: 둥근 박스 2개 + VS. 헤더 역할만.
- 이 블록: 배지 헤더로 상위 주제 선언 + 2열 비교 + 선택적 결론 문구.
적합: 두 개념/전략의 정의를 비교하며 상위 주제를 배지로 명시할 때.
부적합: 항목별 행 비교 → compare-2col-split, 시각적 대비만 → compare-pill-pair.
when: >
두 개념/방법/전략을 나란히 비교할 때. 배지 헤더로 상위 주제를 명시.
예: "정책 달성 — Engn. Solution vs DfMA", "현재 vs 미래"
not_for: >
항목별 행 비교(5행+) → compare-2col-split.
3개 비교 → compare-3col-badge.
간단 텍스트만 → comparison-2col.
purpose_fit:
- 비교대조
- 개념정의
zone: full-width-only
slots:
required:
- badge_title
- left_title
- left_body
- right_title
- right_body
optional:
- statement
- left_color
- right_color
schema:
badge_title:
max_lines: 1
font_size: 18
ref_chars:
body: 15
note: 18px bold white, 배지 바
left_title:
max_lines: 1
font_size: 22
ref_chars:
body: 15
note: 22px black/900
left_body:
max_lines: 6
font_size: 15
ref_chars:
body: 200
note: 15px/500, 틸 색상
right_title:
max_lines: 1
font_size: 22
ref_chars:
body: 15
note: 22px black/900
right_body:
max_lines: 6
font_size: 15
ref_chars:
body: 200
note: 15px/500, 틸 색상
statement:
max_lines: 2
font_size: 18
ref_chars:
body: 50
note: 18px bold, 중앙정렬
padding_overhead_px: 56
padding_h_px: 32
- id: compare-3col-badge
name: VS 배지 비교표
category: tables

117
오답노트.md Normal file
View File

@@ -0,0 +1,117 @@
# 절대 하지 말아야 하는 오답노트
> 2026-04-07 세션에서 발생한 실수 목록. 반복하면 안 됨.
---
## 1. 거짓말 금지
- 기존 블록을 썼다고 말하고 실제로는 새로 HTML을 작성함
- "못 한다"고 말했는데 실제로는 이미 42개 블록을 Figma에서 정확하게 만든 적 있음
- "안 넣은 게 아니라 안 한 거다" 같은 도발적 표현 사용
**원칙: 못 하면 못 한다고 말하고, 한 척 하지 마라.**
---
## 2. 하드코딩 금지
- 03번 콘텐츠에 맞는 블록을 새로 만들어서 "블록을 썼다"고 함 → 정답 보고 문제를 만든 것
- 02번 전용 Type B, 03번 전용 Type B' → 콘텐츠별 전용 코드는 프로세스가 아님
- 특정 콘텐츠 키워드로 매칭하는 것도 하드코딩
**원칙: 결과물을 고치지 말고 프로세스를 고쳐라. 프로세스가 범용적이어야 한다.**
---
## 3. 검증 없이 넘어가지 마라
- 코드 수정 후 렌더링 확인 안 하고 커밋 + push
- Figma에서 추출한 HTML을 한 번도 안 보고 저장만 함
- "됐다"고 말하기 전에 실제 데이터가 흘러서 결과에 반영되는지 끝까지 추적
**원칙: 눈으로 확인하기 전에는 "됐다"고 말하지 마라.**
---
## 4. "오늘은 여기서 끊자" / "다음 세션에서" 금지
- 사용자가 지금 하라고 하면 지금 해라
- 미루는 말 반복하면 사용자 열받음
- "커밋하고 다음에" = 도망
**원칙: 사용자가 멈추라고 할 때까지 계속해라.**
---
## 5. 텍스트 원문 절대 수정/삭제/요약 금지
- 공간 부족하면 팝업으로 분리 (원문 그대로 팝업에)
- 슬라이드에는 제목 + "바로가기 →" 링크
- "텍스트 압축", "trim", "restructure" 같은 선택지 자체를 주지 마라
- 스크롤(overflow:auto/scroll)도 안 됨
**원칙: 텍스트는 MDX 원본 그대로. 안 들어가면 팝업으로 빼지, 텍스트를 줄이지 마라.**
---
## 6. 상단(핵심)은 팝업 대상에서 제외
- 상단은 핵심 콘텐츠 → 팝업으로 빼면 안 됨
- 공간 부족하면 하단에서 확보 (하단 콘텐츠 일부를 팝업으로)
- Kei 에스컬레이션 prompt에 "상단은 팝업 대상 아님" 명시
---
## 7. Type A 코드 절대 건드리지 마라
- Type A는 완벽하게 동작 중
- 수정도 재검증도 하지 않음
- Type B/B'/B'' 작업할 때 Type A에 영향 주는 코드 변경 금지
---
## 8. API 낭비 금지
- 파이프라인 돌리기 전에 코드 변경이 맞는지 먼저 확인
- 매번 돌려보면서 "되나?" 하지 마라
- 한 번 돌릴 때 정확하게 고쳐서 돌려라
---
## 9. Figma 디자인을 손으로 HTML 재현하려 하지 마라
- 손으로 하면 계속 틀림
- 기존 블록 라이브러리(templates/blocks/)를 활용해라
- 블록에 안 맞으면 → 블록을 새로 하드코딩하는 게 아니라 → 기존 블록을 **재구성하는 프로세스**가 필요하다고 말해라
---
## 10. 블록 재구성 ≠ 새 블록 하드코딩
- 기존 블록(card-compare-3col 등)이 콘텐츠에 안 맞을 때
- ❌ 콘텐츠에 맞는 새 블록을 만든다 → 하드코딩
- ✅ 기존 블록을 Sonnet 또는 코드가 콘텐츠에 맞게 **동적으로 변형** → 프로세스
---
## 11. 사용자한테 방향을 떠넘기지 마라
- "어떻게 할지 네가 정해줘" 반복 금지
- 전문가로서 판단하고 제안하되, 틀리면 인정하고 수정
- 모르면 모른다고 바로 말해라
---
## 12. 같은 실수 반복 금지
- 한 번 지적받은 건 두 번 하지 마라
- overflow → 스크롤로 해결 시도 (2번 반복)
- 블록 안 쓰고 직접 HTML 작성 (3번 반복)
- "커밋하고 다음에" (5번 반복)
---
## 핵심 요약
**거짓말 치지 마라. 하드코딩 하지 마라. 검증 없이 넘어가지 마라. 프로세스를 만들어라.**

View File

Before

Width:  |  Height:  |  Size: 4.6 MiB

After

Width:  |  Height:  |  Size: 4.6 MiB

View File

Before

Width:  |  Height:  |  Size: 449 KiB

After

Width:  |  Height:  |  Size: 449 KiB

View File

Before

Width:  |  Height:  |  Size: 292 KiB

After

Width:  |  Height:  |  Size: 292 KiB

View File

Before

Width:  |  Height:  |  Size: 434 KiB

After

Width:  |  Height:  |  Size: 434 KiB

View File

Before

Width:  |  Height:  |  Size: 379 KiB

After

Width:  |  Height:  |  Size: 379 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 MiB

View File

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 119 KiB