# 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 정책 달성{{ badge_title }} Engn. Solution{{ left_title }} ``` ### 6.2 반복 요소 → 루프 ```html {% for card in cards %}
{{ card.title }}
{% endfor %} ``` ### 6.3 이미지 자산 → 슬롯 ```html ``` ### 6.4 계산된 CSS → CSS 변수 ```html
``` --- ## 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
이전 상태 1
이전 상태 2
→
변환 후 1
변환 후 2
``` ```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 등록