@su-record/vibe 2.8.19 → 2.8.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/vibe-figma/SKILL.md +279 -62
- package/skills/vibe-figma-analyze/SKILL.md +4 -227
- package/skills/vibe-figma-codegen/SKILL.md +3 -189
- package/skills/vibe-figma-consolidate/SKILL.md +3 -93
- package/skills/vibe-figma-convert/SKILL.md +284 -0
- package/skills/vibe-figma-extract/SKILL.md +150 -0
- package/skills/vibe-figma-frame/SKILL.md +4 -528
- package/skills/vibe-figma-rules/SKILL.md +3 -456
- package/skills/vibe-figma-style/SKILL.md +3 -207
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vibe-figma-extract
|
|
3
|
+
description: Figma MCP에서 이미지 다운로드 + CSS 값 추출
|
|
4
|
+
triggers: []
|
|
5
|
+
tier: standard
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# vibe-figma-extract — 데이터 추출
|
|
9
|
+
|
|
10
|
+
`get_design_context` 응답에서 이미지와 CSS 값을 추출하는 절차.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 1. 이미지 에셋 추출 + 다운로드
|
|
15
|
+
|
|
16
|
+
### URL 추출
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
참조 코드에서 모든 에셋 URL을 수집:
|
|
20
|
+
패턴: const {변수명} = "https://www.figma.com/api/mcp/asset/{uuid}"
|
|
21
|
+
|
|
22
|
+
예:
|
|
23
|
+
const img21 = "https://www.figma.com/api/mcp/asset/76e951df-..."
|
|
24
|
+
const imgTitle = "https://www.figma.com/api/mcp/asset/f97dad41-..."
|
|
25
|
+
const imgSnowParticle12 = "https://www.figma.com/api/mcp/asset/3de2eeed-..."
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 파일명 결정
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
변수명 → kebab-case 파일명:
|
|
32
|
+
img21 → img-21.webp
|
|
33
|
+
imgTitle → title.webp
|
|
34
|
+
imgSnowParticle12 → snow-particle-12.webp
|
|
35
|
+
imgSnowmanItem11 → snowman-item-11.webp
|
|
36
|
+
imgImgBannerStatic → banner-static.webp
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 다운로드
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
for each (변수명, url) in assets:
|
|
43
|
+
Bash: curl -sL "{url}" -o public/images/{feature}/{파일명}.webp
|
|
44
|
+
|
|
45
|
+
다운로드 후 검증:
|
|
46
|
+
Bash: ls -la public/images/{feature}/
|
|
47
|
+
→ 모든 파일 존재 + 0byte 아닌지 확인
|
|
48
|
+
→ 실패한 파일 → 재시도 1회
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 누락 에셋 처리
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
스크린샷에 보이는 이미지가 참조 코드에 에셋 URL로 없을 때:
|
|
55
|
+
|
|
56
|
+
1. get_metadata(섹션 nodeId)로 하위 노드 목록 확보
|
|
57
|
+
2. 이미지로 의심되는 하위 nodeId에 get_design_context 재호출
|
|
58
|
+
→ 에셋 URL 발견 시 다운로드
|
|
59
|
+
3. 그래도 없으면 → get_screenshot(해당 nodeId)으로 이미지 직접 저장
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 이미지 매핑 테이블
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
다운로드 완료 후 매핑 생성:
|
|
66
|
+
|
|
67
|
+
imageMap = {
|
|
68
|
+
img21: '/images/{feature}/img-21.webp',
|
|
69
|
+
imgTitle: '/images/{feature}/title.webp',
|
|
70
|
+
imgSnowParticle12: '/images/{feature}/snow-particle-12.webp',
|
|
71
|
+
...
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
이 매핑은 코드 변환 시 src={변수명}을 로컬 경로로 교체하는 데 사용.
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 2. CSS 값 추출
|
|
80
|
+
|
|
81
|
+
### Tailwind → CSS 변환표
|
|
82
|
+
|
|
83
|
+
참조 코드의 Tailwind 클래스에서 CSS 속성 + 값을 추출:
|
|
84
|
+
|
|
85
|
+
| Tailwind | CSS | 스케일 적용 |
|
|
86
|
+
|----------|-----|-----------|
|
|
87
|
+
| `text-[48px]` | `font-size: 48px` | ✅ × scaleFactor |
|
|
88
|
+
| `text-[#1B3A1D]` | `color: #1B3A1D` | ❌ |
|
|
89
|
+
| `font-black` | `font-weight: 900` | ❌ |
|
|
90
|
+
| `font-bold` | `font-weight: 700` | ❌ |
|
|
91
|
+
| `font-semibold` | `font-weight: 600` | ❌ |
|
|
92
|
+
| `font-medium` | `font-weight: 500` | ❌ |
|
|
93
|
+
| `leading-[1.4]` | `line-height: 1.4` | ❌ (단위 없음) |
|
|
94
|
+
| `tracking-[-0.36px]` | `letter-spacing: -0.36px` | ✅ |
|
|
95
|
+
| `bg-[#0A1628]` | `background-color: #0A1628` | ❌ |
|
|
96
|
+
| `bg-[rgba(13,40,61,0.5)]` | `background: rgba(13,40,61,0.5)` | ❌ |
|
|
97
|
+
| `pt-[120px]` | `padding-top: 120px` | ✅ |
|
|
98
|
+
| `gap-[24px]` | `gap: 24px` | ✅ |
|
|
99
|
+
| `rounded-[12px]` | `border-radius: 12px` | ✅ |
|
|
100
|
+
| `shadow-[...]` | `box-shadow: ...` | 부분 (px만) |
|
|
101
|
+
| `w-[720px]` | `width: 720px` | ✅ |
|
|
102
|
+
| `h-[1280px]` | `height: 1280px` | ✅ |
|
|
103
|
+
| `opacity-40` | `opacity: 0.4` | ❌ |
|
|
104
|
+
| `blur-[3.5px]` | `filter: blur(3.5px)` | ✅ |
|
|
105
|
+
| `mix-blend-lighten` | `mix-blend-mode: lighten` | ❌ |
|
|
106
|
+
| `mix-blend-multiply` | `mix-blend-mode: multiply` | ❌ |
|
|
107
|
+
| `overflow-clip` | `overflow: clip` | ❌ |
|
|
108
|
+
| `absolute` | `position: absolute` | ❌ |
|
|
109
|
+
| `relative` | `position: relative` | ❌ |
|
|
110
|
+
| `inset-0` | `inset: 0` | ❌ |
|
|
111
|
+
| `flex` | `display: flex` | ❌ |
|
|
112
|
+
| `flex-col` | `flex-direction: column` | ❌ |
|
|
113
|
+
| `items-center` | `align-items: center` | ❌ |
|
|
114
|
+
| `justify-center` | `justify-content: center` | ❌ |
|
|
115
|
+
| `object-cover` | `object-fit: cover` | ❌ |
|
|
116
|
+
| `object-contain` | `object-fit: contain` | ❌ |
|
|
117
|
+
| `whitespace-nowrap` | `white-space: nowrap` | ❌ |
|
|
118
|
+
| `text-center` | `text-align: center` | ❌ |
|
|
119
|
+
| `text-white` | `color: #FFFFFF` | ❌ |
|
|
120
|
+
| `size-full` | `width: 100%; height: 100%` | ❌ |
|
|
121
|
+
| `max-w-none` | `max-width: none` | ❌ |
|
|
122
|
+
| `pointer-events-none` | `pointer-events: none` | ❌ |
|
|
123
|
+
|
|
124
|
+
### CSS 변수 패턴
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
참조 코드에 Figma 디자인 토큰이 CSS 변수로 포함될 수 있음:
|
|
128
|
+
font-[family-name:var(--font/family/pretendard,...)]
|
|
129
|
+
text-[length:var(--font/size/heading/24,24px)]
|
|
130
|
+
text-[color:var(--color/grayscale/950,#171716)]
|
|
131
|
+
|
|
132
|
+
→ var() 안의 fallback 값(24px, #171716)을 사용.
|
|
133
|
+
→ CSS 변수명은 프로젝트 토큰 네이밍에 참고.
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## 3. HTML 구조 추출
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
참조 코드의 JSX 구조가 HTML 구조.
|
|
142
|
+
data-name 속성으로 레이어 용도 파악:
|
|
143
|
+
data-name="BG" → 배경 레이어
|
|
144
|
+
data-name="Title" → 제목 영역
|
|
145
|
+
data-name="Period" → 기간 정보 영역
|
|
146
|
+
data-name="Light" → 장식 조명
|
|
147
|
+
data-name="BTN_Share" → 공유 버튼
|
|
148
|
+
|
|
149
|
+
구조 변환은 vibe-figma-convert에서 처리.
|
|
150
|
+
```
|
|
@@ -1,534 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: vibe-figma-frame
|
|
3
|
-
|
|
4
|
-
description: "디자인 URL → 프레임별 개별 추출 → 스타일/이미지/코드 반영 → 검증 루프. Step B/C 공통."
|
|
3
|
+
description: "[Merged] → vibe-figma-extract 참조"
|
|
5
4
|
triggers: []
|
|
5
|
+
tier: standard
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
#
|
|
9
|
-
|
|
10
|
-
디자인 URL 하나를 받아서 **프레임별로 쪼개서 추출**하고, Step A에서 만든 컴포넌트에 스타일/이미지를 채움.
|
|
11
|
-
어떤 뷰포트든 동일한 프로세스. 두 번째 호출 시 반응형 레이어만 추가.
|
|
12
|
-
|
|
13
|
-
> **실행 지시: 분석만 하지 말 것.**
|
|
14
|
-
> - 이미지: WebFetch로 다운로드 → 파일 저장
|
|
15
|
-
> - 스타일: Write 도구로 SCSS/CSS 파일 생성
|
|
16
|
-
> - 코드: Edit 도구로 Step A 컴포넌트의 template/style 채움
|
|
17
|
-
> - 토큰: Edit 도구로 _tokens 파일에 추출한 값 추가
|
|
18
|
-
|
|
19
|
-
## HARD RULES (위반 시 Step B 미완성)
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
1. PLACEHOLDER 금지
|
|
23
|
-
코드에 "placeholder", "Key Visual Image", 빈 dashed box,
|
|
24
|
-
alt="placeholder", src="" 등이 남아있으면 → Step B 미완성.
|
|
25
|
-
이미지를 추출 못하면 placeholder가 아니라 대체 추출(B-3.3 Step e)을 실행한다.
|
|
26
|
-
|
|
27
|
-
2. 이미지 없는 섹션은 완료가 아니다
|
|
28
|
-
스크린샷에 이미지(배경/캐릭터/일러스트/아이콘)가 보이는 섹션에서
|
|
29
|
-
생성 코드에 실제 이미지 파일이 없으면 → 해당 섹션 미완성.
|
|
30
|
-
다음 섹션으로 넘어가지 않고 현재 섹션의 이미지를 확보할 때까지 머문다.
|
|
31
|
-
|
|
32
|
-
3. 단색 배경으로 대체 금지
|
|
33
|
-
원본에 이미지 배경이 있는데 생성 코드가 CSS gradient/단색으로 대체하면 → P1.
|
|
34
|
-
이미지를 반드시 다운로드하여 background-image로 적용해야 한다.
|
|
35
|
-
|
|
36
|
-
4. 이미지 추출 실패 = 전체 실패
|
|
37
|
-
인벤토리의 이미지 중 하나라도 확보 못하면 Step B를 완료로 마킹하지 않는다.
|
|
38
|
-
대체 추출 경로(하위 노드 탐색 → 개별 스크린샷 → 크롭)를 전부 시도한 후,
|
|
39
|
-
그래도 실패하면 사용자에게 해당 이미지를 직접 제공해달라고 요청한다.
|
|
40
|
-
|
|
41
|
-
5. 텍스트 스타일 미적용 = 미완성
|
|
42
|
-
스크린샷에서 읽은 모든 텍스트 스타일을 코드에 반영해야 한다:
|
|
43
|
-
- font-size (스케일 팩터 적용)
|
|
44
|
-
- font-weight
|
|
45
|
-
- color
|
|
46
|
-
- line-height
|
|
47
|
-
- letter-spacing (있으면)
|
|
48
|
-
- text-align
|
|
49
|
-
제목, 본문, 버튼 텍스트, 설명 등 스크린샷에 보이는 모든 텍스트 요소에 적용.
|
|
50
|
-
스타일이 적용되지 않은 텍스트 요소가 있으면 → P1.
|
|
51
|
-
브라우저 기본 스타일(검은색 16px)로 보이는 텍스트가 있으면 → 미완성.
|
|
52
|
-
|
|
53
|
-
6. 스타일은 반드시 외부 파일에 작성
|
|
54
|
-
컴포넌트 파일(.vue/.tsx) 안에 <style> 블록이나 인라인 스타일을 작성하지 않는다.
|
|
55
|
-
모든 스타일은 외부 파일에 작성:
|
|
56
|
-
--new 모드: styles/{feature}/layout/_섹션.scss, components/_요소.scss
|
|
57
|
-
기본 모드: 프로젝트 기존 스타일 패턴에 따름
|
|
58
|
-
|
|
59
|
-
작성 후 검증:
|
|
60
|
-
Grep: "<style" in components/{feature}/ → 0건
|
|
61
|
-
Grep: "style=" in components/{feature}/ → 0건 (동적 바인딩 제외)
|
|
62
|
-
위반 시 → 해당 스타일을 외부 파일로 이동 후 재검증.
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## 입력
|
|
66
|
-
|
|
67
|
-
- 디자인 Figma URL (전체 페이지)
|
|
68
|
-
- Step A에서 생성된 컴포넌트 파일들
|
|
69
|
-
- 호출 횟수 (첫 번째 = base, 두 번째 이후 = responsive 추가)
|
|
70
|
-
|
|
71
|
-
## B-1. 디자인 URL 입력
|
|
72
|
-
|
|
73
|
-
AskUserQuestion (options 사용 금지, 자유 텍스트만):
|
|
74
|
-
|
|
75
|
-
```
|
|
76
|
-
첫 번째 호출:
|
|
77
|
-
question: "디자인 Figma URL을 입력해주세요."
|
|
78
|
-
→ 응답 대기 → URL 저장 → B-2~B-5 실행
|
|
79
|
-
|
|
80
|
-
검증 완료 후:
|
|
81
|
-
question: "추가 디자인 URL이 있나요? (없으면 '없음')"
|
|
82
|
-
→ URL 입력 → responsive 모드로 B-2~B-5 재실행
|
|
83
|
-
→ "없음" → Step D(공통화)로 진행
|
|
84
|
-
|
|
85
|
-
모바일/PC 순서를 강제하지 않음. 어떤 뷰포트든 먼저 입력 가능.
|
|
86
|
-
첫 번째 URL = base 스타일, 추가 URL = 반응형 레이어 추가.
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
## B-2. 전체 → 섹션 프레임 매핑
|
|
90
|
-
|
|
91
|
-
```
|
|
92
|
-
1. get_metadata(fileKey, nodeId) → 전체 페이지 하위 프레임 목록
|
|
93
|
-
|
|
94
|
-
2. 프레임 이름으로 Step A 컴포넌트와 매핑:
|
|
95
|
-
- 프레임 이름 키워드 매칭
|
|
96
|
-
- 매칭 안 되면 순서(위→아래)로 Step A 섹션과 1:1 대응
|
|
97
|
-
- 그래도 안 되면 get_screenshot으로 비주얼 비교
|
|
98
|
-
|
|
99
|
-
3. 매핑 결과 출력:
|
|
100
|
-
"디자인 프레임 N개 → 컴포넌트 N개 매핑 완료"
|
|
101
|
-
매핑 안 된 프레임이 있으면 사용자에게 확인
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
## B-3. 섹션별 개별 추출
|
|
105
|
-
|
|
106
|
-
**각 매핑된 섹션에 대해 순서대로:**
|
|
107
|
-
|
|
108
|
-
### 3-1. 참조 코드에서 스타일 값 + 에셋 추출
|
|
109
|
-
|
|
110
|
-
```
|
|
111
|
-
get_design_context(fileKey, designFrame.nodeId)
|
|
112
|
-
→ 참조 코드 (React+Tailwind) + 에셋 URL
|
|
113
|
-
|
|
114
|
-
참조 코드에서 추출하는 것 (Figma 토큰 = 정확한 값):
|
|
115
|
-
✅ 색상: hex 값 (text-[#1B3A1D], bg-[#0A1628] 등)
|
|
116
|
-
✅ 폰트: font-size(px), font-weight, line-height, font-family
|
|
117
|
-
✅ 간격: padding, margin, gap (px)
|
|
118
|
-
✅ 장식: border-radius, box-shadow, opacity
|
|
119
|
-
✅ 에셋 URL: https://figma.com/api/mcp/asset/...
|
|
120
|
-
|
|
121
|
-
이 값들은 디자이너가 Figma UI에서 보는 것과 동일한 토큰 값.
|
|
122
|
-
스크린샷에서 추정하지 않고 이 값을 그대로 사용한다.
|
|
123
|
-
px 값에만 스케일 팩터(R-3)를 적용.
|
|
124
|
-
|
|
125
|
-
⚠️ HTML 구조는 레이어가 비정형이면 부정확할 수 있음.
|
|
126
|
-
→ 구조는 3-2 스크린샷에서 판단.
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### 3-2. 스크린샷으로 구조 + 이미지 인벤토리
|
|
130
|
-
|
|
131
|
-
```
|
|
132
|
-
get_screenshot(fileKey, designFrame.nodeId)
|
|
133
|
-
→ 원본 디자인 이미지 확보
|
|
134
|
-
|
|
135
|
-
스크린샷에서 판단하는 것 (구조):
|
|
136
|
-
→ 레이아웃 구조 (섹션 경계, flex/grid 방향, 요소 배치 순서)
|
|
137
|
-
→ 이미지 배치 분류 (Background/Content/Overlay, R-4 참조)
|
|
138
|
-
→ z-index 관계 (겹침 구조, 오버레이 유무)
|
|
139
|
-
→ 참조 코드에 없는 시각 요소 발견 → 추가 구현 대상
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
**이미지 인벤토리 작성 (필수):**
|
|
143
|
-
|
|
144
|
-
```
|
|
145
|
-
스크린샷을 보고 해당 섹션에 보이는 모든 이미지를 목록화:
|
|
146
|
-
|
|
147
|
-
imageInventory = [
|
|
148
|
-
{ name: "hero-bg", type: "background", description: "눈 테마 풀스크린 배경" },
|
|
149
|
-
{ name: "hero-character", type: "overlay", description: "캐릭터 일러스트 우하단" },
|
|
150
|
-
{ name: "hero-vehicle", type: "content", description: "차량 이미지 중앙" },
|
|
151
|
-
{ name: "hero-logo", type: "content", description: "이벤트 로고 상단" },
|
|
152
|
-
]
|
|
153
|
-
|
|
154
|
-
→ 이 인벤토리가 B-3.3 다운로드의 체크리스트가 됨
|
|
155
|
-
→ B-5 검증에서 인벤토리 vs 코드의 이미지를 1:1 대조
|
|
156
|
-
→ 인벤토리에 있는데 코드에 없으면 = P1
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### 3-3. 이미지 에셋 다운로드 (BLOCKING — 코드 반영 전 필수)
|
|
160
|
-
|
|
161
|
-
> **인벤토리의 모든 이미지가 로컬에 존재해야 다음 단계로 넘어갈 수 있다.**
|
|
162
|
-
|
|
163
|
-
```
|
|
164
|
-
Step a: 참조 코드에서 에셋 URL 추출
|
|
165
|
-
→ 모든 https://www.figma.com/api/mcp/asset/ URL 수집
|
|
166
|
-
→ 각 URL을 imageInventory 항목과 매칭
|
|
167
|
-
|
|
168
|
-
Step b: 인벤토리 vs 에셋 URL 대조
|
|
169
|
-
→ 인벤토리에 있는데 에셋 URL이 없는 이미지 = 누락 후보
|
|
170
|
-
→ 누락 후보에 대해 대체 추출 실행 (Step e)
|
|
171
|
-
|
|
172
|
-
Step c: 매칭된 에셋 다운로드
|
|
173
|
-
Bash: curl -L "{url}" -o static/images/{feature}/{name}.webp
|
|
174
|
-
파일명: 인벤토리 name 기반 kebab-case
|
|
175
|
-
|
|
176
|
-
Step d: 다운로드 검증
|
|
177
|
-
→ 파일 존재 + 0byte 아닌지 확인
|
|
178
|
-
→ 누락/실패 시 재다운로드
|
|
179
|
-
|
|
180
|
-
Step e: 대체 추출 (참조 코드에 에셋 URL이 없는 이미지)
|
|
181
|
-
레이어가 비정형("Frame 633372")이면 참조 코드에 이미지가 누락될 수 있음.
|
|
182
|
-
이 경우 다음 순서로 시도:
|
|
183
|
-
|
|
184
|
-
1. 하위 노드 탐색:
|
|
185
|
-
get_metadata로 섹션 하위 프레임 목록 확보
|
|
186
|
-
→ 이미지로 의심되는 하위 nodeId에 대해 get_design_context 재호출
|
|
187
|
-
→ 에셋 URL 확보되면 다운로드
|
|
188
|
-
|
|
189
|
-
2. 하위 노드 개별 스크린샷:
|
|
190
|
-
이미지로 의심되는 하위 nodeId에 대해 get_screenshot
|
|
191
|
-
→ 해당 스크린샷 자체를 이미지 에셋으로 저장
|
|
192
|
-
→ 배경 이미지: 스크린샷을 background-image로 사용
|
|
193
|
-
→ 콘텐츠 이미지: 스크린샷을 <img>로 사용
|
|
194
|
-
|
|
195
|
-
3. 섹션 전체 스크린샷 크롭:
|
|
196
|
-
위 방법이 다 실패하면, 섹션 스크린샷에서 해당 영역을 잘라서 사용
|
|
197
|
-
→ 최후 수단이지만 이미지 누락보다는 낫다
|
|
198
|
-
|
|
199
|
-
Step f: 최종 인벤토리 체크
|
|
200
|
-
인벤토리 항목 수 = 다운로드된 파일 수
|
|
201
|
-
하나라도 빠지면 → Step e 재시도
|
|
202
|
-
모든 이미지가 로컬에 있어야 → 3-4로 진행
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
### 3-4. 이미지 코드 패턴 적용
|
|
206
|
-
|
|
207
|
-
이미지 분류 결과에 따라 코드 생성:
|
|
208
|
-
|
|
209
|
-
**Background Image → Multi-Layer 패턴 (vibe-figma-rules R-4 참조)**
|
|
210
|
-
|
|
211
|
-
**Content Image:**
|
|
212
|
-
```tsx
|
|
213
|
-
// React / Next.js — Image 컴포넌트 우선
|
|
214
|
-
<Image src="/images/{feature}/product.webp" alt="설명" width={600} height={400} />
|
|
215
|
-
|
|
216
|
-
// Vue / Nuxt — NuxtImg 우선
|
|
217
|
-
<NuxtImg src="/images/{feature}/product.webp" alt="설명" width="600" height="400" loading="lazy" />
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
**반응형 Content Image:**
|
|
221
|
-
```html
|
|
222
|
-
<picture>
|
|
223
|
-
<source media="(min-width: {breakpoint}px)" srcset="/images/{feature}/hero-pc.webp" />
|
|
224
|
-
<img src="/images/{feature}/hero-mobile.webp" alt="설명" loading="eager" />
|
|
225
|
-
</picture>
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
**반응형 Background Image:**
|
|
229
|
-
```css
|
|
230
|
-
.heroBg { background-image: url('/images/{feature}/hero-mobile.webp'); }
|
|
231
|
-
@media (min-width: {breakpoint}px) {
|
|
232
|
-
.heroBg { background-image: url('/images/{feature}/hero-pc.webp'); }
|
|
233
|
-
}
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
### 3-5. 글로벌 스타일 파일 구조 생성 (BLOCKING — 컴포넌트 수정 전 필수)
|
|
237
|
-
|
|
238
|
-
> **이 단계를 건너뛰면 Step B 미완성. 컴포넌트를 수정하기 전에 스타일 파일을 먼저 만든다.**
|
|
239
|
-
|
|
240
|
-
첫 번째 섹션 처리 시 전체 구조를 Write로 생성. 이후 섹션에서는 해당 파일에 Edit으로 추가.
|
|
241
|
-
|
|
242
|
-
```
|
|
243
|
-
styles/{feature}/
|
|
244
|
-
index.scss ← [1] 진입점 (모든 파일 import)
|
|
245
|
-
_tokens.scss ← [2] 토큰 (색상, 폰트, 간격 변수)
|
|
246
|
-
_mixins.scss ← [3] mixin (breakpoint, fluid 함수)
|
|
247
|
-
_base.scss ← [4] 공통 (reset, font-face, 전역 규칙)
|
|
248
|
-
layout/
|
|
249
|
-
_page.scss ← [5] 페이지 전체 레이아웃
|
|
250
|
-
_{section}.scss ← [6] 각 섹션별 배치/구조/배경
|
|
251
|
-
components/
|
|
252
|
-
_{element}.scss ← [7] 재사용 UI 요소 (card, button, badge 등)
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
#### [1] index.scss — Write로 생성
|
|
256
|
-
|
|
257
|
-
```scss
|
|
258
|
-
// Foundation
|
|
259
|
-
@use 'tokens';
|
|
260
|
-
@use 'mixins';
|
|
261
|
-
@use 'base';
|
|
262
|
-
|
|
263
|
-
// Layout
|
|
264
|
-
@use 'layout/page';
|
|
265
|
-
@use 'layout/hero';
|
|
266
|
-
@use 'layout/daily-checkin';
|
|
267
|
-
// ... Step A의 모든 섹션
|
|
268
|
-
|
|
269
|
-
// Components
|
|
270
|
-
@use 'components/card';
|
|
271
|
-
@use 'components/button';
|
|
272
|
-
// ... 반복 패턴에서 추출된 재사용 요소
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
#### [2] _tokens.scss — 참조 코드에서 추출한 값으로 Write
|
|
276
|
-
|
|
277
|
-
```scss
|
|
278
|
-
@use 'sass:math';
|
|
279
|
-
|
|
280
|
-
// ── Colors (참조 코드 hex 그대로) ──
|
|
281
|
-
$figma-bg-primary: #0A1628;
|
|
282
|
-
$figma-bg-section: #1A2B4A;
|
|
283
|
-
$figma-text-heading: #1B3A1D;
|
|
284
|
-
$figma-text-body: #333333;
|
|
285
|
-
$figma-text-light: #FFFFFF;
|
|
286
|
-
$figma-accent: #FFD700;
|
|
287
|
-
|
|
288
|
-
// ── Typography (참조 코드 px × scaleFactor) ──
|
|
289
|
-
$figma-text-hero: 36px; // Figma 48px × 0.75
|
|
290
|
-
$figma-text-sub: 18px; // Figma 24px × 0.75
|
|
291
|
-
$figma-text-body: 14px; // Figma 18px × 0.75
|
|
292
|
-
$figma-text-caption: 12px; // Figma 16px × 0.75
|
|
293
|
-
$figma-font-family: 'Noto Sans KR', sans-serif;
|
|
294
|
-
|
|
295
|
-
// ── Spacing (참조 코드 px × scaleFactor) ──
|
|
296
|
-
$figma-space-section: 60px; // Figma 80px × 0.75
|
|
297
|
-
$figma-space-content: 24px; // Figma 32px × 0.75
|
|
298
|
-
$figma-space-element: 12px; // Figma 16px × 0.75
|
|
299
|
-
|
|
300
|
-
// ── Decorations ──
|
|
301
|
-
$figma-radius-card: 12px;
|
|
302
|
-
$figma-shadow-card: 0 4px 12px rgba(0,0,0,0.15);
|
|
303
|
-
|
|
304
|
-
// ── Breakpoints ──
|
|
305
|
-
$figma-bp: 1024px;
|
|
306
|
-
$figma-bp-mobile-min: 360px;
|
|
307
|
-
$figma-bp-pc-target: 1920px;
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
#### [3] _mixins.scss — Write로 생성
|
|
311
|
-
|
|
312
|
-
```scss
|
|
313
|
-
@use 'tokens' as t;
|
|
314
|
-
|
|
315
|
-
@mixin figma-pc { @media (min-width: t.$figma-bp) { @content; } }
|
|
316
|
-
|
|
317
|
-
@function figma-fluid($mobile, $desktop) {
|
|
318
|
-
// clamp 계산 (vibe-figma-rules R-3)
|
|
319
|
-
}
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
#### [4] _base.scss — Write로 생성
|
|
323
|
-
|
|
324
|
-
```scss
|
|
325
|
-
@use 'tokens' as t;
|
|
326
|
-
|
|
327
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
328
|
-
body { font-family: t.$figma-font-family; }
|
|
329
|
-
img { max-width: 100%; height: auto; }
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
#### [6] layout/_{section}.scss — 섹션별 Write
|
|
333
|
-
|
|
334
|
-
```scss
|
|
335
|
-
@use '../tokens' as t;
|
|
336
|
-
@use '../mixins' as m;
|
|
337
|
-
|
|
338
|
-
// 참조 코드의 레이아웃 관련 값 + 스크린샷의 구조를 적용
|
|
339
|
-
.heroSection {
|
|
340
|
-
position: relative;
|
|
341
|
-
overflow: hidden;
|
|
342
|
-
min-height: 100vh;
|
|
343
|
-
padding: t.$figma-space-section 0;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// Multi-Layer 배경 (이미지가 있는 섹션)
|
|
347
|
-
.heroBg {
|
|
348
|
-
position: absolute; inset: 0; z-index: 0;
|
|
349
|
-
background-image: url('/images/{feature}/hero-bg.webp');
|
|
350
|
-
background-size: cover;
|
|
351
|
-
background-position: center;
|
|
352
|
-
}
|
|
353
|
-
.heroBgOverlay {
|
|
354
|
-
position: absolute; inset: 0; z-index: 1;
|
|
355
|
-
background: linear-gradient(to bottom, rgba(0,0,0,0.3), rgba(0,0,0,0.7));
|
|
356
|
-
}
|
|
357
|
-
.heroContent {
|
|
358
|
-
position: relative; z-index: 2;
|
|
359
|
-
display: flex; flex-direction: column; align-items: center;
|
|
360
|
-
padding: 0 t.$figma-space-content;
|
|
361
|
-
}
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
#### [7] components/_{element}.scss — 텍스트 + UI 요소
|
|
365
|
-
|
|
366
|
-
```scss
|
|
367
|
-
@use '../tokens' as t;
|
|
368
|
-
|
|
369
|
-
// 참조 코드의 Figma 토큰 값을 그대로 사용
|
|
370
|
-
.heroTitle {
|
|
371
|
-
font-size: t.$figma-text-hero;
|
|
372
|
-
font-weight: 900;
|
|
373
|
-
color: t.$figma-text-heading;
|
|
374
|
-
line-height: 1.2;
|
|
375
|
-
text-align: center;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
.heroDescription {
|
|
379
|
-
font-size: t.$figma-text-sub;
|
|
380
|
-
font-weight: 400;
|
|
381
|
-
color: t.$figma-text-body;
|
|
382
|
-
line-height: 1.6;
|
|
383
|
-
text-align: center;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
.heroCta {
|
|
387
|
-
font-size: t.$figma-text-body;
|
|
388
|
-
font-weight: 700;
|
|
389
|
-
color: t.$figma-text-light;
|
|
390
|
-
background: t.$figma-accent;
|
|
391
|
-
border-radius: t.$figma-radius-card;
|
|
392
|
-
padding: t.$figma-space-element t.$figma-space-content;
|
|
393
|
-
}
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
### 3-6. 생성 확인 (BLOCKING)
|
|
397
|
-
|
|
398
|
-
```
|
|
399
|
-
스타일 파일 생성 후 반드시 확인:
|
|
400
|
-
|
|
401
|
-
Glob: styles/{feature}/index.scss → 존재
|
|
402
|
-
Glob: styles/{feature}/_tokens.scss → 존재
|
|
403
|
-
Glob: styles/{feature}/_mixins.scss → 존재
|
|
404
|
-
Glob: styles/{feature}/_base.scss → 존재
|
|
405
|
-
Glob: styles/{feature}/layout/*.scss → 섹션 수만큼 존재
|
|
406
|
-
Glob: styles/{feature}/components/*.scss → 1개 이상 존재
|
|
407
|
-
|
|
408
|
-
Grep: "font-size" in styles/{feature}/ → 0건이면 P1 (텍스트 스타일 미작성)
|
|
409
|
-
Grep: "color:" in styles/{feature}/ → 0건이면 P1
|
|
410
|
-
Grep: "background-image" in styles/{feature}/ → 배경 이미지 섹션 수만큼
|
|
411
|
-
|
|
412
|
-
하나라도 실패 → Write/Edit으로 보완 → 재확인
|
|
413
|
-
파일이 모두 존재하고 내용이 있어야 → 3-7로 진행
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
### 3-7. 컴포넌트 파일에 반영 (Edit 도구)
|
|
417
|
-
|
|
418
|
-
```
|
|
419
|
-
컴포넌트 파일에는 template + script만 수정한다.
|
|
420
|
-
모든 스타일은 3-5에서 생성한 외부 파일에만 존재.
|
|
421
|
-
|
|
422
|
-
a. template 수정:
|
|
423
|
-
- placeholder → 실제 마크업으로 교체
|
|
424
|
-
- 클래스명 추가 (layout/*.scss, components/*.scss의 셀렉터와 매칭)
|
|
425
|
-
- 이미지 태그에 로컬 경로 설정
|
|
426
|
-
- Multi-Layer 구조 적용 (.{section}Bg + .{section}Content)
|
|
427
|
-
|
|
428
|
-
b. Step A 코드 보존:
|
|
429
|
-
- 기능 주석/핸들러/인터페이스 유지
|
|
430
|
-
- 목 데이터/이벤트 바인딩 유지
|
|
431
|
-
|
|
432
|
-
c. 스타일 import 설정:
|
|
433
|
-
- 루트 페이지 또는 설정 파일에서 styles/{feature}/index.scss import
|
|
434
|
-
- Nuxt: nuxt.config의 css 배열에 추가
|
|
435
|
-
- Next.js: _app 또는 layout에서 import
|
|
436
|
-
|
|
437
|
-
d. 컴포넌트 안에 스타일 작성 금지:
|
|
438
|
-
- <style> 블록 금지
|
|
439
|
-
- style="" 인라인 금지 (동적 바인딩 제외)
|
|
440
|
-
- 스타일이 필요하면 → 외부 파일에 추가 → 클래스명으로 연결
|
|
441
|
-
```
|
|
442
|
-
|
|
443
|
-
## B-4. 뷰포트 모드에 따른 스타일 적용
|
|
444
|
-
|
|
445
|
-
### 첫 번째 URL (base)
|
|
446
|
-
|
|
447
|
-
```
|
|
448
|
-
- 모든 스타일을 base로 작성 (반응형 미고려)
|
|
449
|
-
- 토큰 파일에 추출한 값 저장
|
|
450
|
-
- 이미지 다운로드 + 로컬 경로 매핑
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
### 추가 URL (responsive)
|
|
454
|
-
|
|
455
|
-
```
|
|
456
|
-
기존 코드를 수정하지 않고 반응형 레이어만 추가:
|
|
457
|
-
|
|
458
|
-
1. 프레임 width로 뷰포트 자동 판별
|
|
459
|
-
2. 값이 다른 속성 → clamp() fluid 토큰 (계산: vibe-figma-rules R-3)
|
|
460
|
-
3. 레이아웃 구조가 다른 부분 → @media (min-width: {breakpoint}px)
|
|
461
|
-
4. 뷰포트별 배경 이미지 → @media 분기
|
|
462
|
-
5. 추가 이미지 에셋 다운로드 (base와 동일하면 스킵)
|
|
463
|
-
6. 기존 base 코드/주석/핸들러는 절대 삭제하지 않음
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
## B-5. 검증 루프
|
|
467
|
-
|
|
468
|
-
공통 프로세스: **vibe-figma-rules R-6** (6-1 ~ 6-7) 전체 적용.
|
|
469
|
-
|
|
470
|
-
### Step B 검증 흐름
|
|
471
|
-
|
|
472
|
-
```
|
|
473
|
-
for each section in mappings:
|
|
474
|
-
|
|
475
|
-
1. 원본 확보: B-3.1에서 이미 get_screenshot한 섹션 이미지
|
|
476
|
-
2. 생성 결과 확보: /vibe.utils --preview 또는 dev 서버 스크린샷 (R-6.2)
|
|
477
|
-
3. 섹션별 비교: 레이아웃, 배경, 색상, 타이포, 간격, 이미지 (R-6.3~4)
|
|
478
|
-
4. Diff Report 출력 (R-6.5)
|
|
479
|
-
5. P1 → 해당 섹션 수정 → 재비교 (R-6.6)
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
### Step B 추가 검증 항목
|
|
483
|
-
|
|
484
|
-
```
|
|
485
|
-
1. 이미지 인벤토리 대조:
|
|
486
|
-
for each item in imageInventory:
|
|
487
|
-
□ 로컬 파일 존재 (Glob)
|
|
488
|
-
□ 0byte 아님 (ls -la)
|
|
489
|
-
□ 코드에서 참조됨 (Grep: 파일명으로 검색)
|
|
490
|
-
□ 올바른 패턴 적용됨:
|
|
491
|
-
- background → .{section}Bg { background-image: url(...) }
|
|
492
|
-
- content → <img src="..." /> 또는 <Image />
|
|
493
|
-
- overlay → .{section}Character { background-image: url(...) }
|
|
494
|
-
하나라도 실패 → P1 → 수정 → 재검증
|
|
495
|
-
|
|
496
|
-
2. 텍스트 스타일 검증:
|
|
497
|
-
for each section:
|
|
498
|
-
□ 외부 스타일 파일 존재 (Glob: styles/{feature}/**/*.scss)
|
|
499
|
-
□ 스크린샷의 텍스트 요소 수 ≈ font-size 선언 수
|
|
500
|
-
Grep: "font-size" in styles/{feature}/
|
|
501
|
-
□ 브라우저 기본 스타일로 보이는 텍스트 0건
|
|
502
|
-
→ 모든 텍스트에 font-size, color, font-weight 지정 확인
|
|
503
|
-
□ color 값이 적용됨 (원본 스크린샷 색상과 매칭)
|
|
504
|
-
미적용 텍스트 발견 → P1
|
|
505
|
-
|
|
506
|
-
3. 스타일 분리 검증:
|
|
507
|
-
□ Grep: "<style" in components/{feature}/**/*.vue → 0건
|
|
508
|
-
□ Grep: "<style" in components/{feature}/**/*.tsx → 0건
|
|
509
|
-
□ Grep: 'style="' in components/{feature}/ → 0건 (v-bind:style 동적 바인딩 제외)
|
|
510
|
-
위반 → 외부 파일로 이동 → 재검증
|
|
511
|
-
|
|
512
|
-
4. Figma 임시 URL + placeholder 잔존 체크:
|
|
513
|
-
□ Grep: "figma.com/api/mcp/asset" → 0건
|
|
514
|
-
□ Grep: "placeholder" (대소문자 무시) → 0건
|
|
515
|
-
□ Grep: "Key Visual" → 0건
|
|
516
|
-
|
|
517
|
-
5. 배경 이미지 Multi-Layer 검증:
|
|
518
|
-
스크린샷에 배경 이미지가 보이는 섹션:
|
|
519
|
-
□ .{section}Bg 클래스 존재 (Grep)
|
|
520
|
-
□ .{section}Content 클래스 존재 (z-index 최상위)
|
|
521
|
-
□ 배경 위 텍스트 가독성 확보 (오버레이 유무)
|
|
522
|
-
누락 → P1
|
|
523
|
-
|
|
524
|
-
6. (responsive) 뷰포트별:
|
|
525
|
-
□ 뷰포트별 다른 배경 이미지 → @media 분기 있는지
|
|
526
|
-
□ 이전 뷰포트 스타일/이미지 깨지지 않았는지
|
|
527
|
-
```
|
|
528
|
-
|
|
529
|
-
## 참조 스킬
|
|
8
|
+
# vibe-figma-frame
|
|
530
9
|
|
|
531
|
-
|
|
532
|
-
- `vibe-figma-rules` — 공통 규칙 (R-1~R-7)
|
|
533
|
-
- `vibe-figma-style` — 토큰/스타일 아키텍처
|
|
534
|
-
- `vibe-figma-codegen` — 마크업/코드 생성 규칙
|
|
10
|
+
이 스킬은 **vibe-figma-extract**으로 병합되었습니다.
|