@su-record/vibe 2.8.35 → 2.8.36

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.
@@ -1,21 +1,31 @@
1
1
  ---
2
2
  name: vibe.figma
3
- description: Figma design to code — 스토리보드(기능) + 디자인(비주얼) 프로덕션 코드
3
+ description: Figma design to code — 시각 기반 퍼즐 조립 방식
4
4
  triggers: []
5
5
  tier: standard
6
6
  ---
7
7
 
8
- # vibe.figma — Figma Design to Code
8
+ # vibe.figma — Visual Puzzle Assembly
9
+
10
+ ## 핵심 원칙
11
+
12
+ ```
13
+ 스크린샷이 정답이다. Figma 데이터는 재료일 뿐이다.
14
+
15
+ ❌ Figma 트리 구조를 HTML로 변환하지 않는다 (이 방식은 실패한다)
16
+ ✅ 스크린샷을 보고 "무엇을 만들어야 하는지" 파악한다
17
+ ✅ Figma 데이터(이미지, 색상, 수치)를 정확한 재료로 사용한다
18
+ ✅ 사람 개발자처럼: 디자인 보고 → 에셋 받고 → 만들면서 비교
19
+ ```
9
20
 
10
21
  ## 금지 사항
11
22
 
12
23
  ```
13
- CSS로 이미지 재현 (삼각형/원/gradient로 나무/눈사람/배경 그리기)
24
+ Figma 레이어 트리를 그대로 div 구조로 변환
25
+ ❌ CSS로 이미지 재현 (gradient/shape으로 그림 그리기)
14
26
  ❌ 이미지 다운로드 없이 코드 생성 진행
15
- ❌ placeholder / 빈 template / 빈 src="" 남기기
16
- CSS 값을 추정 (참조 코드의 Tailwind 클래스에 정확한 값이 있음)
17
- ❌ 브라우저 기본 스타일(검은색 16px)로 보이는 텍스트
18
- ❌ 핵심 에셋만 다운로드 (const img... 전부 다운로드)
27
+ ❌ placeholder / 빈 src="" 남기기
28
+ 색상·크기를 추정 (재료함에 정확한 값이 있음)
19
29
  ❌ 컴포넌트 파일 안에 <style> 블록 / 인라인 style=""
20
30
  ✅ 외부 SCSS 파일에만 스타일 작성
21
31
  ```
@@ -25,9 +35,10 @@ tier: standard
25
35
  ```
26
36
  /vibe.figma
27
37
  → Phase 0: Setup (스택 감지, 디렉토리 생성)
28
- → Phase 1: Storyboard (선택 레이아웃+컴포넌트 구성 + 기능 주석)
29
- → Phase 2: Design (최소 뷰포트 퍼스트 브레이크포인트별 반복)
30
- → Phase 3: Verification (Grep 체크 + 스크린샷 비교)
38
+ → Phase 1: Storyboard (스토리보드 레이아웃 + 컴포넌트 + 기능 정의)
39
+ → Phase 2: 재료 확보 (디자인 URL스크린샷 + 이미지 + CSS + 텍스트)
40
+ → Phase 3: 퍼즐 조립 (스크린샷 보면서 Phase 1 컴포넌트에 디자인 입히기)
41
+ → Phase 4: 검증 루프 (빌드 → 스크린샷 → 비교 → 수정 → 반복)
31
42
  ```
32
43
 
33
44
  ---
@@ -52,6 +63,10 @@ tier: standard
52
63
  - components/{feature}/
53
64
  - public/images/{feature}/ (또는 static/images/{feature}/)
54
65
  - styles/{feature}/ (layout/, components/ 하위)
66
+
67
+ 5. 기존 컴포넌트 스캔:
68
+ - Glob "components/**/*.vue" or "components/**/*.tsx"
69
+ - 재사용 가능한 컴포넌트 목록 수집 (GNB, Footer, Button 등)
55
70
  ```
56
71
 
57
72
  ---
@@ -86,7 +101,7 @@ URL에서 fileKey, nodeId 추출
86
101
  1순위: SPEC (기능 정의서) — 1개
87
102
  2순위: CONFIG (해상도) — 1개
88
103
  3순위: PAGE 중 메인 섹션만 (3.1, 3.2, 3.3, 3.4, 3.5, 3.6)
89
- 하위 케이스(3.1.1, 3.2.1 등)는 건너뜀 — Phase 2에서 필요 시 참조
104
+ 하위 케이스(3.1.1, 3.2.1 등)는 건너뜀
90
105
  4순위: SHARED (공통 요소, Popup) — 필요 시
91
106
 
92
107
  높이 1500px 이상 프레임:
@@ -139,7 +154,7 @@ URL에서 fileKey, nodeId 추출
139
154
  - 목 데이터 (빈 배열 금지, 3~7개 아이템)
140
155
  - 이벤트 핸들러 stub (body는 // TODO:)
141
156
 
142
- <style> 블록 없음 — 스타일은 Phase 2에서 외부 파일로.
157
+ <style> 블록 없음 — 스타일은 Phase 3에서 외부 파일로.
143
158
 
144
159
  3. 공통 컴포넌트 (SHARED에서 파악):
145
160
  → 프로젝트에 이미 있으면 import 재사용
@@ -165,7 +180,65 @@ Phase 1 완료 조건:
165
180
  □ 빌드 성공
166
181
 
167
182
  빈 화면 = Phase 1 미완성. Phase 2로 넘어가지 않는다.
168
- 스타일/이미지는 없어도 됨 — Phase 2에서 채움.
183
+ 스타일/이미지는 없어도 됨 — Phase 3에서 채움.
184
+ ```
185
+
186
+ ---
187
+
188
+ ## Phase 2: 재료 확보
189
+
190
+ Phase 1 컴포넌트가 준비된 상태에서, 디자인 URL로 시각 재료를 수집한다.
191
+
192
+ 사용자에게 질문한다:
193
+ - question: "베이스 디자인(모바일) Figma URL을 입력해주세요."
194
+ - options 제공 금지 — 자유 텍스트 입력만 허용
195
+
196
+ ### 2-1. 디자인 재료 추출
197
+
198
+ ```
199
+ URL에서 fileKey, nodeId 추출
200
+
201
+ 1단계 — 전체 스크린샷 (정답 사진):
202
+ node "[FIGMA_SCRIPT]" screenshot {fileKey} {nodeId} --out=/tmp/{feature}/full-screenshot.png
203
+
204
+ 2단계 — 전체 트리 + CSS (수치 재료):
205
+ node "[FIGMA_SCRIPT]" tree {fileKey} {nodeId} --depth=10
206
+ → /tmp/{feature}/tree.json 에 저장
207
+
208
+ 3단계 — 전체 이미지 다운로드 (시각 재료):
209
+ node "[FIGMA_SCRIPT]" images {fileKey} {nodeId} --out=/tmp/{feature}/images/ --depth=10
210
+ → 모든 이미지 에셋 확보. 누락 0건, 0byte 0건.
211
+
212
+ 4단계 — 섹션별 스크린샷 (부분 정답):
213
+ 트리의 1depth 자식 프레임 각각:
214
+ node "[FIGMA_SCRIPT]" screenshot {fileKey} {child.nodeId} --out=/tmp/{feature}/sections/{child.name}.png
215
+ ```
216
+
217
+ ### 2-2. 재료함 정리
218
+
219
+ ```
220
+ Phase 2 완료 시 /tmp/{feature}/ 에 다음이 준비되어야 함:
221
+
222
+ /tmp/{feature}/
223
+ ├── full-screenshot.png ← 전체 정답 사진
224
+ ├── tree.json ← 노드 트리 + CSS 수치
225
+ ├── images/ ← 모든 이미지 에셋
226
+ │ ├── hero-bg.png
227
+ │ ├── hero-title.png
228
+ │ ├── card-item-1.png
229
+ │ └── ...
230
+ └── sections/ ← 섹션별 정답 사진
231
+ ├── hero.png
232
+ ├── daily-checkin.png
233
+ ├── playtime-mission.png
234
+ └── ...
235
+
236
+ 재료 목록 (material-inventory):
237
+ - 이미지: 파일명 + 크기 + 용도 추정 (BG/icon/title/decoration)
238
+ - 색상: tree.json에서 추출한 모든 고유 색상값
239
+ - 폰트: 사용된 font-family, size, weight 목록
240
+ - 텍스트: 모든 TEXT 노드의 characters 값
241
+ - 간격: padding, gap, margin 사용 빈도 높은 값
169
242
  ```
170
243
 
171
244
  ### 스케일 팩터
@@ -181,145 +254,238 @@ Phase 1 완료 조건:
181
254
 
182
255
  ---
183
256
 
184
- ## Phase 2: Design
257
+ ## Phase 3: 퍼즐 조립
185
258
 
186
- Phase 1 컴포넌트에 **이미지 + 스타일**을 입힌다.
187
- 모바일 퍼스트. base = 최소 뷰포트, @media (min-width:)로 확장.
259
+ **Phase 1에서 만든 컴포넌트에 Phase 2의 재료로 디자인을 입힌다.**
260
+ **스크린샷을 보면서 퍼즐을 맞추듯 조립한다.**
261
+ **첫 섹션(Hero) 단독 완료 후 나머지 섹션 병렬 진행.**
188
262
 
189
- 사용자에게 질문한다:
190
- - question: "베이스 디자인(모바일) Figma URL을 입력해주세요."
191
- - options 제공 금지 — 자유 텍스트 입력만 허용
192
-
193
- → 2-1 → 2-2 섹션 루프 실행.
194
-
195
- 완료 후 다시 질문:
196
- - question: "다음 브레이크포인트 디자인 URL을 입력해주세요. (없으면 '없음')"
197
- → URL 입력 시: 2-3 반응형 추가 후 다시 질문
198
- → "없음" 응답 시: Phase 3으로
199
-
200
- ### 2-1. SCSS Setup + 등록 (첫 섹션 전)
263
+ ### 3-0. SCSS Setup + 등록 (첫 섹션 전)
201
264
 
202
265
  ```
203
- SCSS 파일 기본 내용 Write:
266
+ Phase 1에서 생성한 빈 SCSS 파일에 기본 내용 Write:
204
267
  styles/{feature}/index.scss ← @import 진입점
205
- styles/{feature}/_tokens.scss ← 파일 (섹션마다 채움)
268
+ styles/{feature}/_tokens.scss ← 재료함에서 추출한 디자인 토큰
206
269
  styles/{feature}/_mixins.scss ← breakpoint mixin
207
- styles/{feature}/_base.scss ← 루트 클래스 (.winterPcbang 등)
208
- styles/{feature}/layout/ ← 디렉토리
209
- styles/{feature}/components/ ← 디렉토리
270
+ styles/{feature}/_base.scss ← 루트 클래스
210
271
 
211
- 스타일 등록 (BLOCKING 미등록 시 섹션 루프 진행 금지):
212
- Grep "{feature}/index.scss"이미 등록되어 있으면 건너뜀.
272
+ 토큰 추출 (tree.json의 CSS 수치에서):
273
+ - 색상primitive ($color-navy: #0a1628) + semantic ($color-bg-primary)
274
+ - 폰트 → $font-pretendard, $font-size-md: 16px
275
+ - 간격 → $space-sm: 8px, $space-md: 16px
213
276
 
214
- 신규 프로젝트 (--new):
215
- 루트 페이지 파일에서 직접 로드:
216
- pages/{feature}.vue <style lang="scss" src="~/assets/scss/{feature}/index.scss" />
217
- app/{feature}/page.tsx → import '~/styles/{feature}/index.scss'
277
+ 스타일 등록 (BLOCKING):
278
+ Grep "{feature}/index.scss" 이미 등록되어 있으면 건너뜀.
279
+ 없으면 프로젝트 방식에 맞게 등록.
280
+ ```
218
281
 
219
- 기존 프로젝트 (업데이트):
220
- Grep "\.scss\|\.css" in nuxt.config.*/next.config.*/vite.config.*/main.ts
221
- → 기존 방식과 동일하게 등록
282
+ ### 3-1. 섹션 조립 프로세스
222
283
 
223
- 검증: Grep "{feature}/index.scss" in 프로젝트 전체 → 0건이면 실패
284
+ ```
285
+ 각 섹션에 대해:
286
+
287
+ 1. 정답 확인 — 섹션 스크린샷을 Read로 본다
288
+ /tmp/{feature}/sections/{section}.png
289
+ → "이 화면처럼 만들어야 한다"
290
+
291
+ 2. 재료 확인 — 이 섹션에 필요한 재료 목록 확인
292
+ - 이미지: /tmp/{feature}/images/ 에서 해당 파일들
293
+ - CSS 수치: tree.json에서 해당 노드의 정확한 값
294
+ - 텍스트: TEXT 노드의 characters
295
+
296
+ 3. Phase 1 컴포넌트에 디자인 입히기
297
+ a. 이미지 복사: images/ → static/images/{feature}/
298
+ b. template 업데이트:
299
+ - Phase 1의 기능 요소(v-for, @click, v-if, $emit) 보존
300
+ - 스크린샷을 보고 시각 구조에 맞게 HTML 재구성
301
+ - Figma 레이어 구조를 HTML로 변환하지 않음 (시각 기반)
302
+ - 재료함의 정확한 이미지 경로 사용
303
+ - 재료함의 정확한 텍스트 사용
304
+ c. script 보존:
305
+ - Phase 1의 JSDoc, 인터페이스, 핸들러 유지
306
+ d. SCSS 작성: 재료함의 정확한 CSS 수치 사용 (scaleFactor 적용)
307
+ - layout/_{section}.scss ← 포지션, 사이즈, 플렉스
308
+ - components/_{section}.scss ← 폰트, 색상, 보더
309
+ - _tokens.scss에 새 토큰 추가
310
+
311
+ 4. 즉시 검증 — 스크린샷과 비교
312
+ - 이미지 빠진 거 없나?
313
+ - 텍스트 빠진 거 없나?
314
+ - 레이아웃 구조가 스크린샷과 맞나?
315
+ - Phase 1의 기능 요소가 보존되었나?
224
316
  ```
225
317
 
226
- ### 2-2. 섹션 루프
318
+ ### 3-2. 코드 작성 규칙
227
319
 
228
- **섹션별로 a→d 순서는 지키되, 섹션 간 병렬 처리 허용.**
229
- **단, 번째 섹션(Hero)은 단독 완료 후 나머지를 병렬로.**
320
+ ```
321
+ 컴포넌트 (Vue 예시):
322
+ <template>
323
+ 스크린샷에서 보이는 시각 구조대로 작성.
324
+ Figma 레이어 구조 무시.
325
+ Phase 1의 기능 요소(v-for, @click, v-if) 보존.
326
+ 시맨틱 HTML 사용 (<section>, <h2>, <ul>, <button>).
327
+ 이미지 경로: /images/{feature}/파일명.png
328
+ 텍스트: tree.json의 TEXT 노드 characters 값 그대로.
329
+
330
+ <script setup>
331
+ Phase 1의 JSDoc + 인터페이스 + 핸들러 보존.
332
+ 새로운 데이터/상태 추가 시 기존과 병합.
333
+
334
+ <style> 블록 없음 — 외부 SCSS만.
335
+
336
+ SCSS (vibe.figma.convert 참조):
337
+ layout/ → position, display, flex, width, height, padding, gap
338
+ components/ → font, color, border, shadow, opacity
339
+ 모든 수치는 재료함의 정확한 값 × scaleFactor.
340
+ 추정 금지 — 값이 없으면 tree.json에서 다시 찾는다.
341
+ ```
230
342
 
231
- #### a. render 실행 (BLOCKING)
343
+ ### 반응형 (추가 URL)
232
344
 
233
345
  ```
234
- # [FIGMA_SCRIPT] = ~/.vibe/hooks/scripts/figma-extract.js
235
- node "[FIGMA_SCRIPT]" render {fileKey} {섹션.nodeId} --out=/tmp/{feature}-{section}/ --depth=10
346
+ 완료 질문: "다음 브레이크포인트 디자인 URL을 입력해주세요. (없으면 '없음')"
236
347
 
237
- 번 호출로 아래 파일이 생성됨:
238
- /tmp/{feature}-{section}/
239
- ├── {section}.html ← HTML 구조 (class명 포함)
240
- ├── {section}.scss ← 전체 SCSS (모든 CSS 속성)
241
- ├── {section}.json ← 원본 트리 JSON
242
- ├── {section}-screenshot.png ← 섹션 스크린샷 (시각 기준점)
243
- └── images/
244
- ├── {section}-bg-composite.png ← 복합 BG 합성 이미지
245
- ├── {section}-title.png ← 이름 있는 이미지
246
- └── ...
247
-
248
- 이미지는 노드 name 기반 파일명 (해시 아님).
249
- 복합 BG(3개+ 하위 레이어)는 자동으로 합성 스크린샷 생성.
250
- 인스턴스 내부 자식도 depth로 전부 조회.
348
+ URL 있으면:
349
+ 1. 새 URL에서 재료 확보 (Phase 1 반복)
350
+ 2. 스크린샷과 기존 코드 비교
351
+ 3. @media (min-width: $bp-desktop) 오버라이드만 추가
352
+ 4. 기존 모바일 코드 삭제 금지
251
353
  ```
252
354
 
253
- #### b. 생성물 적용
355
+ ---
254
356
 
255
- ```
256
- render 출력물을 프로젝트에 적용:
357
+ ## Phase 4: 검증 루프
257
358
 
258
- 1. 이미지 복사:
259
- /tmp/{feature}-{section}/images/* static/images/{feature}/
359
+ **Puppeteer + CDP로 실제 렌더링 결과를 확인하며 자동 수정한다.**
360
+ **사람이 브라우저 보면서 고치는 것과 동일한 루프.**
361
+ **인프라: `src/infra/lib/browser/` (범용 UI 검증 도구)**
260
362
 
261
- 2. SCSS 적용:
262
- {section}.scss를 읽고 scaleFactor 적용하여 프로젝트 SCSS에 Write:
263
- styles/{feature}/layout/_{section}.scss ← position, display, flex, width, height
264
- styles/{feature}/components/_{section}.scss ← font, color, border, shadow
265
- styles/{feature}/_tokens.scss ← 새 값 추가 (primitive/semantic, vibe.figma.convert 참조)
266
- index.scss에 새 섹션 @import 추가.
363
+ ```
364
+ 자동 반복: P1=0 때까지. 최대 3라운드.
365
+ ```
267
366
 
268
- 3. template 업데이트:
269
- {section}.html을 읽고 Phase 1 컴포넌트에 반영:
270
- - HTML 구조를 프로젝트 스택으로 변환 (class 유지)
271
- - 이미지 경로를 static/images/{feature}/ 로 교체
272
- - Phase 1 기능 요소(v-for, @click, v-if, $emit) 재배치
273
- - script(JSDoc, 인터페이스, 핸들러) 보존
367
+ ### 4-0. 환경 준비
274
368
 
275
- 4. 스크린샷 참조:
276
- {section}-screenshot.png과 비교하면서 작업
369
+ ```
370
+ 1. dev 서버 시작:
371
+ npm run dev (또는 프로젝트 dev 명령)
372
+ → localhost:3000 (또는 해당 포트) 확인
373
+
374
+ 2. Puppeteer 브라우저 시작:
375
+ import { launchBrowser, openPage } from 'src/infra/lib/browser'
376
+ const browser = await launchBrowser({ headless: true })
377
+ const page = await openPage(browser, 'http://localhost:3000/{feature}', {
378
+ width: 480, // 모바일 퍼스트 (또는 타겟 뷰포트)
379
+ height: 960,
380
+ })
277
381
  ```
278
382
 
279
- #### c. 섹션 검증
383
+ ### 4-1. 렌더링 스크린샷 vs Figma 스크린샷
280
384
 
281
385
  ```
282
- Grep 체크:
283
- □ 'src=""' in 컴포넌트 파일 → 0건
284
- "<style" in 컴포넌트 파일 → 0건
386
+ import { captureScreenshot, compareScreenshots } from 'src/infra/lib/browser'
387
+
388
+ 섹션에 대해:
389
+ 1. 렌더링 결과 스크린샷 캡처:
390
+ await captureScreenshot(page, {
391
+ outPath: '/tmp/{feature}/rendered-{section}.png',
392
+ selector: '.{section}Section', // Phase 1에서 만든 클래스
393
+ })
394
+
395
+ 2. Figma 원본과 픽셀 비교:
396
+ const diff = await compareScreenshots(
397
+ '/tmp/{feature}/sections/{section}.png', // Figma 원본
398
+ '/tmp/{feature}/rendered-{section}.png', // 렌더링 결과
399
+ '/tmp/{feature}/diff-{section}.png', // 차이 시각화
400
+ )
401
+
402
+ 3. diff 이미지를 Read로 확인:
403
+ → 빨간색 영역 = 차이 나는 부분
404
+ → diffRatio > 0.1 이면 P1 이슈
405
+ ```
285
406
 
286
- Read 체크:
287
- □ 외부 SCSS 파일에 font-size, color 존재
288
- □ 이미지 파일 존재 + 0byte 없음
407
+ ### 4-2. CSS 수치 정밀 비교
408
+
409
+ ```
410
+ import { getComputedStyles, compareStyles, diffsToIssues } from 'src/infra/lib/browser'
411
+
412
+ 각 섹션의 주요 요소에 대해:
413
+ 1. 렌더링된 computed CSS 추출:
414
+ const actual = await getComputedStyles(page, '.heroTitle', [
415
+ 'font-size', 'color', 'width', 'height', 'padding', 'margin',
416
+ 'background-color', 'border-radius', 'gap',
417
+ ])
418
+
419
+ 2. Figma 재료함의 기대값과 비교:
420
+ // tree.json에서 해당 노드의 CSS 수치 (scaleFactor 적용 후)
421
+ const expected = { 'font-size': '16px', 'color': '#ffffff', 'width': '465px' }
422
+ const diffs = compareStyles(expected, actual)
423
+
424
+ 3. 차이 → 이슈 변환:
425
+ const issues = diffsToIssues(diffs)
426
+ → delta > 4px: P1 (레이아웃 영향)
427
+ → delta ≤ 4px: P2 (미세 차이)
428
+ ```
289
429
 
290
- 스크린샷 비교:
291
- □ {section}-screenshot.png vs dev 서버 → 주요 차이 없음
430
+ ### 4-3. 이미지·텍스트 누락 체크
292
431
 
293
- 실패 → 수정 → 재검증
432
+ ```
433
+ import { extractImages, extractTextContent } from 'src/infra/lib/browser'
434
+
435
+ 1. 이미지 로드 상태 확인:
436
+ const images = await extractImages(page)
437
+ images.filter(img => !img.loaded)
438
+ → 로드 실패 이미지 = P1 (이미지 누락)
439
+ → src="" 이미지 = P1 (빈 경로)
440
+
441
+ 2. 텍스트 콘텐츠 확인:
442
+ const texts = await extractTextContent(page)
443
+ → 재료함의 TEXT 노드 characters와 대조
444
+ → 누락된 텍스트 = P1
294
445
  ```
295
446
 
296
- ### 2-3. 반응형 (두 번째 URL부터)
447
+ ### 4-4. 자동 수정 루프
297
448
 
298
449
  ```
299
- 번째 이후 URL: 기존 스타일 유지 + @media 추가만.
450
+ 라운드 1~3:
451
+ 1. 4-1 ~ 4-3 실행 → 이슈 목록 수집
452
+ 2. P1 이슈 우선 수정:
453
+ - 이미지 누락 → 이미지 경로 확인, static/ 에 파일 존재 확인
454
+ - 레이아웃 다름 → 스크린샷 diff 이미지 + computed CSS로 원인 파악
455
+ - 텍스트 누락 → 재료함의 정확한 텍스트 삽입
456
+ - CSS 수치 틀림 → 재료함(tree.json)의 정확한 값으로 교체
457
+ ⚠️ 추정으로 수정하지 않는다. 반드시 재료함 참조.
458
+ 3. 수정 후 페이지 리로드 → 다시 캡처 → 비교
459
+ 4. P1=0 이면 종료
460
+
461
+ 라운드 종료 조건:
462
+ - P1=0: 성공 → 브라우저 종료, 결과 보고
463
+ - 3라운드 후 P1 남음: TODO 목록으로 사용자에게 보고
464
+ - 같은 이슈가 반복: 해당 이슈 스킵, 다음 이슈로
465
+
466
+ 결과 보고:
467
+ - 수정한 파일 목록
468
+ - 남은 P2 이슈 목록 (선택적 수정)
469
+ - 최종 diff 스크린샷 경로
470
+ ```
471
+
472
+ ### 4-5. 반응형 검증 (추가 뷰포트)
300
473
 
301
- 같은 값 → 유지
302
- 다른 px 값 → @media (min-width: $bp-desktop) 오버라이드
303
- 다른 레이아웃 → @media 블록 추가
304
- 다른 배경 이미지 → @media 이미지 분기
305
- 기존 코드/스타일 삭제 금지
306
474
  ```
475
+ 모바일 검증 완료 후, 추가 브레이크포인트가 있으면:
307
476
 
308
- ---
477
+ await page.setViewport({ width: 1920, height: 1080 })
478
+ await page.reload({ waitUntil: 'networkidle0' })
479
+
480
+ → 데스크탑 Figma 스크린샷과 동일한 4-1 ~ 4-4 루프 반복
481
+ ```
309
482
 
310
- ## Phase 3: Verification
483
+ ### 4-6. 브라우저 정리
311
484
 
312
485
  ```
313
- Grep 체크:
314
- □ "<style" in components/{feature}/ → 0건
315
- □ 'src=""' in components/{feature}/ → 0건
316
- □ Glob: images/{feature}/ → 이미지 파일 존재
486
+ import { closeBrowser } from 'src/infra/lib/browser'
317
487
 
318
- 시각 검증:
319
- 섹션 스크린샷:
320
- node "[FIGMA_SCRIPT]" screenshot {fileKey} {nodeId} --out=/tmp/{section}.png
321
- → dev 서버/preview와 비교
322
- P1 (필수): 이미지 누락, 레이아웃 구조 다름, 텍스트 스타일 미적용
323
- P2 (권장): 미세 간격, 미세 색상 차이
324
- → P1 수정 → 재검증 (P1=0 될 때까지)
488
+ 검증 완료 후:
489
+ await closeBrowser()
490
+ dev 서버 종료 (필요 시)
325
491
  ```