@su-record/vibe 2.8.17 → 2.8.19

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.
@@ -10,12 +10,57 @@ triggers: []
10
10
  디자인 URL 하나를 받아서 **프레임별로 쪼개서 추출**하고, Step A에서 만든 컴포넌트에 스타일/이미지를 채움.
11
11
  어떤 뷰포트든 동일한 프로세스. 두 번째 호출 시 반응형 레이어만 추가.
12
12
 
13
- > **⛔ 실행 지시: 분석만 하지 말 것.**
14
- > - 이미지: WebFetch로 다운로드 → Write 도구로 파일 저장
15
- > - 스타일: Write 도구로 SCSS/CSS 파일 생성 (layout/ + components/)
13
+ > **실행 지시: 분석만 하지 말 것.**
14
+ > - 이미지: WebFetch로 다운로드 → 파일 저장
15
+ > - 스타일: Write 도구로 SCSS/CSS 파일 생성
16
16
  > - 코드: Edit 도구로 Step A 컴포넌트의 template/style 채움
17
- > - 토큰: Edit 도구로 _tokens.scss에 추출한 값 추가
18
- > 모든 Phase가 끝나면 디스크에 실제 파일이 변경되어 있어야 함.
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
+ ```
19
64
 
20
65
  ## 입력
21
66
 
@@ -23,44 +68,30 @@ triggers: []
23
68
  - Step A에서 생성된 컴포넌트 파일들
24
69
  - 호출 횟수 (첫 번째 = base, 두 번째 이후 = responsive 추가)
25
70
 
26
- ## Phase 1: 디자인 URL 입력
71
+ ## B-1. 디자인 URL 입력
27
72
 
28
73
  AskUserQuestion (options 사용 금지, 자유 텍스트만):
29
74
 
30
75
  ```
31
76
  첫 번째 호출:
32
- question: "🎨 디자인 Figma URL을 입력해주세요."
33
- ⏸️ 응답 대기 (응답 다음 진행 금지)
34
- → URL 저장 → Phase 2~5 실행
77
+ question: "디자인 Figma URL을 입력해주세요."
78
+ → 응답 대기 URL 저장 B-2~B-5 실행
35
79
 
36
80
  검증 완료 후:
37
- question: "🎨 추가 디자인 URL이 있나요? (없으면 '없음')"
38
- ⏸️ 응답 대기
39
- URL 입력 responsive 모드로 Phase 2~5 재실행
40
- → "없음" → Step C(공통화)로 진행
81
+ question: "추가 디자인 URL이 있나요? (없으면 '없음')"
82
+ URL 입력 → responsive 모드로 B-2~B-5 재실행
83
+ "없음"Step D(공통화)로 진행
41
84
 
42
85
  모바일/PC 순서를 강제하지 않음. 어떤 뷰포트든 먼저 입력 가능.
43
86
  첫 번째 URL = base 스타일, 추가 URL = 반응형 레이어 추가.
44
87
  ```
45
88
 
46
- ## Phase 2: 전체 → 섹션 프레임 매핑
89
+ ## B-2. 전체 → 섹션 프레임 매핑
47
90
 
48
91
  ```
49
92
  1. get_metadata(fileKey, nodeId) → 전체 페이지 하위 프레임 목록
50
93
 
51
94
  2. 프레임 이름으로 Step A 컴포넌트와 매핑:
52
- | 디자인 프레임 이름 | Step A 컴포넌트 |
53
- |------------------|----------------|
54
- | "키 비주얼" / "Hero" / "KV" | HeroSection.vue |
55
- | "출석" / "Check" / "Mission 01" | DailyCheckInSection.vue |
56
- | "플레이타임" / "Mission 02" | PlayTimeMissionSection.vue |
57
- | "교환" / "Exchange" / "Shop" | TokenExchangeSection.vue |
58
- | "응모" / "Raffle" / "Prize" | TokenRaffleSection.vue |
59
- | "유의사항" / "Caution" / "Notice" | CautionSection.vue |
60
- | "GNB" / "Header" / "Nav" | GnbHeader.vue |
61
- | "Footer" | EventFooter.vue |
62
-
63
- 매핑 방법:
64
95
  - 프레임 이름 키워드 매칭
65
96
  - 매칭 안 되면 순서(위→아래)로 Step A 섹션과 1:1 대응
66
97
  - 그래도 안 되면 get_screenshot으로 비주얼 비교
@@ -70,125 +101,346 @@ AskUserQuestion (options 사용 금지, 자유 텍스트만):
70
101
  매핑 안 된 프레임이 있으면 사용자에게 확인
71
102
  ```
72
103
 
73
- ## Phase 3: 섹션별 개별 추출
104
+ ## B-3. 섹션별 개별 추출
74
105
 
75
106
  **각 매핑된 섹션에 대해 순서대로:**
76
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
+
77
163
  ```
78
- for each (designFrame, component) in mappings:
79
-
80
- 1. get_design_context(fileKey, designFrame.nodeId)
81
- → 해당 섹션 전용 **참조 코드** + 스크린샷 + 에셋 URL
82
-
83
- ⚠️ 참조 코드가 핵심:
84
- get_design_context는 React+Tailwind 기반 참조 코드를 반환함.
85
- 이 코드에 이미지 URL, 레이아웃 구조, 색상값, 폰트 정보가 모두 포함됨.
86
- 참조 코드를 **기반으로** 프로젝트 스택에 맞게 변환해야 함.
87
- 참조 코드를 무시하고 스크린샷만 보고 자체 해석하면 안 됨.
88
- 참조 코드의 이미지 URL, 색상 hex, 폰트 크기를 그대로 가져온 후 스케일 적용.
89
-
90
- ⚠️ 스크린샷은 보조:
91
- 참조 코드와 스크린샷이 다르면 스크린샷(디자인 의도) 우선
92
- 참조 코드에 없는 시각적 요소가 스크린샷에 보이면 → 추가 구현
93
-
94
- 2. 이미지 에셋 다운로드 (BLOCKING 코드 반영 필수 완료):
95
-
96
- Step 2-a: get_design_context 응답에서 에셋 URL 추출
97
- 응답 코드에 아래 패턴이 포함됨:
98
- const heroImage = 'https://www.figma.com/api/mcp/asset/xxxx'
99
- const bgImage = 'https://www.figma.com/api/mcp/asset/yyyy'
100
- 모든 https://www.figma.com/api/mcp/asset/ URL을 수집
101
-
102
- Step 2-b: 각 URL을 WebFetch로 다운로드 → Bash로 파일 저장
103
- 다운로드 명령 예시:
104
- Bash: curl -L "https://www.figma.com/api/mcp/asset/xxxx" -o static/images/{feature}/hero-bg.webp
105
- 또는 WebFetch Write로 저장
106
- 파일명: 변수명/레이어명 기반 kebab-case (heroImage → hero-bg.webp)
107
-
108
- Step 2-c: URL→로컬경로 매핑 테이블 생성
109
- | 변수명 | Figma URL | 로컬 경로 |
110
- |--------|-----------|----------|
111
- | heroImage | https://figma.com/api/mcp/asset/xxxx | /images/{feature}/hero-bg.webp |
112
- | bgImage | https://figma.com/api/mcp/asset/yyyy | /images/{feature}/section-bg.webp |
113
-
114
- Step 2-d: 다운로드 검증
115
- 파일이 실제로 존재하는지 Bash: ls -la static/images/{feature}/
116
- 누락된 파일이 있으면 재다운로드
117
- → 0byte 파일 체크 (다운로드 실패)
118
-
119
- 3. 스크린샷 분석 + ⚠️ 스케일 팩터 적용:
120
- → 레이아웃 (flex/grid 방향, 정렬)
121
- 색상 (배경, 텍스트, 보더) — 스케일 불필요
122
- → 타이포 (크기, 굵기, 줄간격) — ⚠️ 스케일 팩터 적용
123
- 간격 (padding, gap, margin) — ⚠️ 스케일 팩터 적용
124
- → ⚠️ 배경 이미지 분류 (아래 참조)
125
-
126
- 스케일 팩터:
127
- 코드 = Figma 추출 × scaleFactor
128
- scaleFactor는 Step A의 storyboardSpec.scaleFactor에서 참조
129
- 디폴트: PC 0.75 (1920/2560), Mobile 0.667 (480/720)
130
-
131
- 4. 배경 이미지 분류 + Multi-Layer 패턴 적용:
132
-
133
- 섹션에 배경 이미지가 있으면 반드시 Multi-Layer 구조로 작성:
134
-
135
- <template>:
136
- <section class="{section}Section">
137
- <div class="{section}Bg" /> ← 배경 이미지 (z-index: 0)
138
- <div class="{section}BgOverlay" /> ← 오버레이 (z-index: 1, 선택)
139
- <div class="{section}Content"> ← 콘텐츠 (z-index: 최상위)
140
- ...실제 UI...
141
- </div>
142
- </section>
143
-
144
- <style> (layout/{section}.scss):
145
- .{section}Section { position: relative; overflow: hidden; }
146
- .{section}Bg {
147
- position: absolute; inset: 0; z-index: 0;
148
- background-image: url('/images/{feature}/{image-name}.webp');
149
- background-size: cover;
150
- background-position: center;
151
- }
152
- .{section}BgOverlay {
153
- position: absolute; inset: 0; z-index: 1;
154
- background: linear-gradient(to bottom, rgba(0,0,0,0.3), rgba(0,0,0,0.7));
155
- }
156
- .{section}Content { position: relative; z-index: 2; }
157
-
158
- 배경이 없는 섹션은 Multi-Layer 불필요 — 일반 구조 사용.
159
-
160
- 5. component 파일에 반영 (Edit 도구):
161
-
162
- ⚠️ 변환 순서 (참조 코드 → 프로젝트 코드):
163
- a. get_design_context 참조 코드에서 추출:
164
- - HTML 구조 (div 계층, 이미지 배치, 텍스트 위치)
165
- - 이미지 URL (const xxxImage = '...')
166
- - 인라인 스타일/Tailwind 클래스 색상, 폰트, 간격
167
- - 배경 이미지 패턴 (background-image가 있으면 Multi-Layer 적용)
168
-
169
- b. 프로젝트 스택으로 변환:
170
- - React+Tailwind → Vue/Nuxt SFC + SCSS (또는 프로젝트 스택)
171
- - className → :class
172
- - style={{ }} → SCSS 변수/클래스
173
- - Tailwind 값 → SCSS 토큰 변수
174
-
175
- c. 스케일 팩터 적용:
176
- - 참조 코드의 px 값 × scaleFactor = 코드 값
177
- - 색상 hex는 그대로 (스케일 불필요)
178
-
179
- d. 이미지 경로 교체:
180
- - figma.com/api/mcp/asset/xxx → /images/{feature}/xxx.webp (매핑 테이블)
181
-
182
- e. Step A 코드와 병합:
183
- - Step A의 기능 주석/핸들러/인터페이스 보존
184
- - template의 placeholder → 참조 코드 기반 실제 마크업으로 교체
185
- - style 블록 → 참조 코드에서 추출한 스타일 작성
186
-
187
- ⛔ 참조 코드에 이미지가 있는데 생성 코드에 없으면 → 누락, 반드시 포함
188
- ⛔ Figma 임시 URL이 코드에 남으면 안 됨
189
- ```
190
-
191
- ## Phase 4: 뷰포트 모드에 따른 스타일 적용
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. 뷰포트 모드에 따른 스타일 적용
192
444
 
193
445
  ### 첫 번째 URL (base)
194
446
 
@@ -203,59 +455,80 @@ for each (designFrame, component) in mappings:
203
455
  ```
204
456
  기존 코드를 수정하지 않고 반응형 레이어만 추가:
205
457
 
206
- 1. 프레임 width 비교 → 어떤 뷰포트인지 자동 판별
207
- (base보다 크면 desktop 방향, 작으면 mobile 방향)
208
-
209
- 2. 값이 다른 속성 → clamp() fluid 토큰으로 변환
210
- - SCSS: figma-fluid($min, $max) 함수 사용
211
- - CSS: clamp(min, preferred, max)
212
-
458
+ 1. 프레임 width 뷰포트 자동 판별
459
+ 2. 값이 다른 속성 → clamp() fluid 토큰 (계산: vibe-figma-rules R-3)
213
460
  3. 레이아웃 구조가 다른 부분 → @media (min-width: {breakpoint}px)
214
- - flex-direction 변경
215
- - grid-template-columns 변경
216
- - display toggle (baseOnly/responsiveOnly)
217
-
218
461
  4. 뷰포트별 배경 이미지 → @media 분기
219
- .heroBg { background-image: url(base.webp); }
220
- @media (min-width: {breakpoint}px) { .heroBg { background-image: url(alt.webp); } }
221
-
222
462
  5. 추가 이미지 에셋 다운로드 (base와 동일하면 스킵)
223
-
224
463
  6. 기존 base 코드/주석/핸들러는 절대 삭제하지 않음
225
464
  ```
226
465
 
227
- ## Phase 5: 검증 루프
466
+ ## B-5. 검증 루프
228
467
 
229
- ```
230
- 🔄 Figma 원본 스크린샷 vs 생성된 코드 비교:
468
+ 공통 프로세스: **vibe-figma-rules R-6** (6-1 ~ 6-7) 전체 적용.
231
469
 
232
- 1. get_screenshot으로 Figma 원본 획득 (이미 Phase 3에서 있음)
233
- 2. 생성된 코드의 렌더링 결과와 비교
234
- 3. 비교 항목:
235
- - 레이아웃 (배치, 정렬, 간격)
236
- - 타이포 (크기, 굵기, 색상)
237
- - 색상 (배경, 텍스트, 보더)
238
- - 이미지 (배경/에셋 표시 여부)
239
- - 누락 요소
470
+ ### Step B 검증 흐름
240
471
 
241
- 4. Diff Report:
242
- | 섹션 | 항목 | Figma | 코드 | 심각도 |
243
- |------|------|-------|------|--------|
244
- | Hero | 배경 이미지 | 있음 | 누락 | P1 |
245
- | 출석 | font-size | 24px | 20px | P2 |
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
+ ```
246
481
 
247
- 5. P1 불일치 수정 → 재비교 (횟수 제한 없음)
248
- 동일 항목 3회 연속 미해결 → 해당 항목만 사용자 확인 후 계속
482
+ ### Step B 추가 검증 항목
249
483
 
250
- 6. 완료 조건:
251
- P1 = 0
252
- 모든 이미지 에셋 표시
253
- (두 번째 호출 ) 이전 뷰포트도 깨지지 않았는지 재확인
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
+ □ 이전 뷰포트 스타일/이미지 깨지지 않았는지
254
527
  ```
255
528
 
256
529
  ## 참조 스킬
257
530
 
258
531
  코드 생성 시 다음 스킬의 규칙을 적용:
259
- - `vibe-figma-rules` — 분석/감지 (Phase 1-3)
260
- - `vibe-figma-style` — 토큰/스타일 (Phase 4, 클래스 네이밍, SCSS)
261
- - `vibe-figma-codegen` — 마크업/코드 생성 (Phase 5-6, 이미지 패턴, 반응형)
532
+ - `vibe-figma-rules` — 공통 규칙 (R-1~R-7)
533
+ - `vibe-figma-style` — 토큰/스타일 아키텍처
534
+ - `vibe-figma-codegen` — 마크업/코드 생성 규칙