Files
C.E.L_Slide_test2/figma_to_html_agent/FIGMA-EXTRACTION.md
kyeongmin 51548fdc41 figma_to_html_agent 추가 + MCP/Claude 설정
figma_to_html_agent/:
- Figma MCP 기반 블록 추출 에이전트 (CLAUDE.md, PLAN.md, PROCESS.md 등)
- block-tests/: Figma→HTML 변환 결과물 (bim-3roles-cards 등)
- templates_staging/: Jinja2 템플릿 + meta.yaml + example.yaml
- figma-analysis/, figma-assets/: Figma 분석 데이터 + 에셋
- scripts/: gradient_math.py 등 유틸리티

설정:
- .mcp.json: Figma MCP 서버 연결 설정
- .claude/settings.json: Claude Code 프로젝트 설정

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 11:00:31 +09:00

675 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` | 4꼭짓점 동일 border-radius |
| **라운드 (개별)** | **`rectangleCornerRadii`** | **[TL, TR, BR, BL] 꼭짓점별 다른 반지름. pill-shape 등 비대칭 라운드 감지** |
| **형상 경로** | **`fillGeometry[].path`** | **SVG path 문자열. `C`/`Q`/`A` 명령어 존재 시 곡선 형상 → `clip-path: path()` 또는 SVG로 구현** |
| **호/부채꼴** | **`arcData`** | **원호, 부채꼴 등 호 형상 파라미터** |
| 이미지 | `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
│ └── arrow_asis_tobe.png (As-Is→To-Be 화살표)
├── data/figma_ref/ ← 비교 리뷰용
│ ├── comparison.html (Figma vs HTML 비교 페이지 — 전체 블록)
│ ├── frame2_1-5.png (Page 1 Figma 원본)
│ ├── frame3_1-35.png
│ ├── frame4_1-49.png
│ ├── strip_table.png (Page 2 필수조건 테이블)
│ ├── checklist_dark.png (Page 3 체크리스트)
│ ├── system_2col.png (Page 3 시스템 구성)
│ └── cycle_orbit.png (Page 4 순환 궤도)
├── templates/blocks/cards/ ← 카드 블록 (Figma 신규 5개)
│ ├── hero-icon-cards.html (Page 1 — 히어로 + N열 아이콘 카드)
│ ├── compare-2col-badge.html (Page 1 — 3D 리본 배지 + 2열 비교)
│ ├── compare-detail-gradient.html (Page 1 — 그라디언트 상세 2열 비교)
│ ├── category-strip-table.html (Page 2 — 컬러 스트립 N열 테이블)
│ └── system-2col-center.html (Page 3 — 중앙 라벨 + 좌/우 항목)
├── templates/blocks/emphasis/ ← 강조 블록 (Figma 신규 1개)
│ └── checklist-dark.html (Page 3 — 체크 아이콘 + 제목:설명 리스트)
├── templates/blocks/visuals/ ← 비주얼 블록 (Figma 신규 1개)
│ └── cycle-orbit.html (Page 4 — 3D 원 투영 순환 궤도 다이어그램)
├── 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. 순환 궤도 다이어그램 (cycle-orbit) 수학 공식
### 10.1 핵심 개념: 3D 원 → Z축 기울임 → 2D 투영
타원이 아니라 **3D 원을 Z축으로 기울인 것**. 토성의 고리와 같은 원리.
```
3D 공간: 2D 투영 (화면):
y y
| / z |
| / |
| / θ=80° (기울임) |
|/______ x |______ x
원 (R=400) 타원 (rx=400, ry=69)
→ Z축으로 80° 뒤로 눕힘 → y축이 cos(80°)=0.1736으로 압축
```
### 10.2 기본 공식
```python
import math
R = 400 # 3D 원 반지름
cx, cy = 500, 200 # 원 중심 (SVG 좌표)
theta = 80 # Z축 기울임 각도 (°)
tilt = math.radians(theta)
# 투영된 타원 파라미터
rx = R # x축은 변하지 않음
ry = R * math.cos(tilt) # y축만 cos(θ)로 압축
# 원 위의 한 점 (각도 α)을 2D로 투영
def project(alpha_deg):
a = math.radians(alpha_deg)
x = cx + R * math.cos(a)
y = cy + R * math.sin(a) * math.cos(tilt)
return round(x), round(y)
```
### 10.3 N개 노드 배치
```python
N = 3 # 노드 개수 (3, 4, 5, 6 모두 가능)
start_angle = 270 # 상단부터 시작
# 기본 간격: 360°/N
base_gap = 360 / N # 3개→120°, 4개→90°, 5개→72°
# 사이각 축소 (2/3): 양끝이 너무 벌어지는 것 방지
gap = base_gap * 2 / 3
# 각 노드 각도 계산 (상단 노드 고정, 나머지가 원 위에서 이동)
angles = []
for i in range(N):
if i == 0:
angles.append(start_angle) # 상단 고정
else:
# 상단 기준 좌/우 대칭 배치
# 홀수 인덱스: 좌측 (반시계)
# 짝수 인덱스: 우측 (시계)
if i % 2 == 1: # 좌측
step = (i + 1) // 2
angles.append(start_angle - gap * step)
else: # 우측
step = i // 2
angles.append(start_angle + gap * step)
# 각 노드의 2D 좌표
for i, angle in enumerate(angles):
x, y = project(angle)
print(f'Node {i}: angle={angle:.0f}°, pos=({x}, {y})')
```
### 10.4 N별 계산 예시
| N | 기본 간격 | 축소 간격 (2/3) | 노드 각도 |
|---|---------|--------------|---------|
| 3 | 120° | 80° | 270°, 190°, 350° |
| 4 | 90° | 60° | 270°, 210°, 330°, 150° |
| 5 | 72° | 48° | 270°, 222°, 318°, 174°, 366° |
| 6 | 60° | 40° | 270°, 230°, 310°, 190°, 350°, 150° |
### 10.5 화살표 >> 위치 계산
화살표는 **두 노드 사이 호의 1/3, 2/3 지점**에 배치. 방향은 **접선 방향**.
```python
def arrow_positions(angle1, angle2):
"""두 노드 사이 호에 화살표 2개 배치"""
mid1 = angle1 + (angle2 - angle1) * 0.35
mid2 = angle1 + (angle2 - angle1) * 0.65
pos1 = project(mid1)
pos2 = project(mid2)
# 접선 방향 (화살표 회전각)
def tangent_angle(alpha):
a = math.radians(alpha)
tx = -math.sin(a)
ty = math.cos(a) * math.cos(tilt)
return math.degrees(math.atan2(ty, tx))
rot1 = tangent_angle(mid1)
rot2 = tangent_angle(mid2)
return (pos1, rot1), (pos2, rot2)
```
### 10.6 설명 텍스트 배치 규칙
**Figma 원본 분석 결과**: 설명은 원 바깥 방향(Q꼬리)이 아니라, **노드 이름 옆에 수평으로** 배치.
```
규칙:
원 중심 기준 좌측 노드 (angle > 90° and < 270°):
→ 설명이 노드 이름의 좌측에 (text-anchor: end)
원 중심 기준 우측 또는 상단 노드 (나머지):
→ 설명이 노드 이름의 우측에 (text-anchor: start)
텍스트 구조:
[설명 제목] ← 이름과 같은 Y선, 옆에 수평
[노드 아이콘 원] • 불릿 1 ← 제목 아래 들여쓰기
[라벨] (원 아래) • 불릿 2
[서브라벨] (라벨 아래)
```
**실제 배치 (3노드 예시)**:
```
👥 사람(역량) 혁신적 사고방식 ← 우측
• 창의적 문제 해결
• 사용자 중심 접근
Digital 기술과... 🖥 기술(디지털) 자연(여건) 📋 지속적 투자 의지 ← 우측
• 건설 전문 지식 ↑ • 실행 추진력
• 최신 기술 좌측 • 변화를 통한 가치 창출
```
```python
def desc_position(node_x, node_y, node_angle, cx):
"""설명 텍스트 위치 계산"""
if node_x < cx: # 왼쪽 노드
desc_x = node_x - 36 # 노드 좌측
anchor = 'end'
else: # 오른쪽 또는 상단 노드
desc_x = node_x + 36 # 노드 우측
anchor = 'start'
desc_y = node_y + 37 # 라벨과 같은 높이 (아이콘 아래)
return desc_x, desc_y, anchor
```
### 10.7 SVG 구조
```xml
<svg viewBox="0 0 1000 380">
<!-- 1. 타원 궤도 -->
<ellipse cx="500" cy="200" rx="400" ry="69"/>
<!-- 2. 화살표 >> (N개 구간 × 2개씩) -->
<text transform="rotate(각도)">»</text>
<!-- 3. 노드 (원 + 아이콘 + 라벨) -->
<circle cx="x" cy="y" r="26"/>
<text>아이콘</text>
<text>라벨</text>
<!-- 4. 설명 텍스트 -->
<text>설명 제목</text>
<text>• 불릿</text>
</svg>
```
### 10.8 중요: 파이프라인에서 좌표 계산
이 블록은 **Jinja2 템플릿에 좌표를 하드코딩하면 안 됨**.
파이프라인(Python)에서 N, R, θ를 받아 좌표를 계산한 뒤 템플릿에 전달해야 함.
```python
# pipeline에서 호출
def calculate_orbit(n_nodes, radius=400, tilt_deg=80):
"""N개 노드의 SVG 좌표와 화살표 위치를 계산"""
cx, cy = 500, 200
tilt = math.radians(tilt_deg)
gap = (360 / n_nodes) * 2 / 3
nodes = []
arrows = []
# ... 위 공식 적용
return {
'ellipse': {'cx': cx, 'cy': cy, 'rx': radius, 'ry': round(radius * math.cos(tilt))},
'nodes': nodes, # [{x, y, angle}, ...]
'arrows': arrows, # [{x, y, rotation}, ...]
}
```
---
## 11. 실수 방지 (Anti-patterns)
### 11.1 절대 하면 안 되는 것
| Anti-pattern | 왜 안 되는지 | 올바른 방법 |
|-------------|------------|-----------|
| px 시행착오 조정 ("좀 더 올려볼게") | 3번 이상 실패, 시간 낭비 | Figma 좌표에서 수학적 계산 |
| 3D 효과를 CSS로 재현 | 평면적이라 품질 차이 심각 | Figma에서 PNG 추출 |
| 비교 리뷰를 좌/우 배치 | 크기 차이로 비교 불가 | 위/아래 같은 폭으로 배치 |
| Jinja2 템플릿을 브라우저에서 직접 열기 | 변수 미렌더, 이미지 경로 깨짐 | comparison.html 또는 FastAPI로 확인 |
| 독립 flex-column으로 2열 비교 | 행 정렬 안 됨 | CSS Grid 행 공유 |
| 느낌으로 폰트/색상 설정 | Figma와 다른 결과물 | Figma API에서 정확한 값 추출 |
### 11.2 반드시 해야 하는 것
| 원칙 | 이유 |
|------|------|
| CSS 주석에 계산 근거 기록 | 나중에 왜 이 값인지 추적 가능 |
| 비교 리뷰 후 진행 | 디자인 차이를 사전에 발견 |
| 이미지 자산은 `static/figma-assets/`에 저장 | FastAPI가 서빙, 경로 일관성 |
| `comparison.html`에 모든 프레임 포함 | 한 페이지에서 전체 리뷰 가능 |
| Figma 노드 ID 기록 | 나중에 업데이트된 디자인 재추출 가능 |
---
## 12. Figma 소스 정보
### 현재 등록된 Figma 파일
| 항목 | 값 |
|------|---|
| File Key | `9S6LsQyO6zlRxtiqZccOUM` |
**Page 1 (0:1)** — 기본 디자인
| 블록 | Node ID | 설명 |
|------|---------|------|
| hero-icon-cards | `1:5` | Frame 2 (Solution 제작 목표) |
| compare-2col-badge | `1:35` | Frame 3 (정책 달성) |
| compare-detail-gradient | `1:49` | Frame 4 (과정 vs 결과 혁신) |
| Badge 빨간 리본 | `1:33` | image 4019 |
| Badge 틸 리본 | `1:43` | image 2197 |
| Arrow As-Is→To-Be | `1:67` | image 2645 |
| Box 빨간 테두리 | `1:12` | Rectangle 42894 |
| Box 틸 테두리 | `1:37` | Rectangle 42598 |
**Page 2 (15:2)** — 프레젠테이션 슬라이드
| 블록 | Node ID | 설명 |
|------|---------|------|
| category-strip-table | `17:1264` | 001_개요 우측 하단 (필수조건 3열) |
**Page 3 (18:8204)** — 컴포넌트
| 블록 | Node ID | 설명 |
|------|---------|------|
| checklist-dark | `18:8351` | f5 (체크리스트 6행) |
| system-2col-center | `18:8405` | f8 (System 구성 H/W vs S/W) |
**Page 4 (29:439)** — 순환 다이어그램
| 블록 | Node ID | 설명 |
|------|---------|------|
| cycle-orbit | `29:439` | DX 시행 필수 요건 (3노드 순환) |
---
## 13. 체크리스트
새 Figma 프레임을 블록으로 변환할 때:
- [ ] Figma API로 노드 데이터 추출 (좌표, 크기, 색상, 폰트)
- [ ] PNG 렌더링 다운로드 (scale=2)
- [ ] 복잡한 비주얼 요소 식별 → 이미지로 추출 (CSS로 만들지 않음)
- [ ] 스케일 팩터 계산 (1200 / frame_width)
- [ ] 핵심 정렬 포인트 수학적 계산 (좌표 차이 × 스케일)
- [ ] CSS 값 도출 (계산 근거를 주석으로 기록)
- [ ] 비교 리뷰 페이지에 추가 (위/아래 같은 폭)
- [ ] 사용자 피드백 확인
- [ ] Jinja2 템플릿 변환 (고정값→변수, 반복→루프)
- [ ] catalog.yaml 등록