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>
This commit is contained in:
674
figma_to_html_agent/FIGMA-EXTRACTION.md
Normal file
674
figma_to_html_agent/FIGMA-EXTRACTION.md
Normal file
@@ -0,0 +1,674 @@
|
||||
# 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 등록
|
||||
Reference in New Issue
Block a user