@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.
- package/dist/infra/lib/config/GlobalConfigManager.d.ts +8 -0
- package/dist/infra/lib/config/GlobalConfigManager.d.ts.map +1 -1
- package/dist/infra/lib/config/GlobalConfigManager.js +37 -0
- package/dist/infra/lib/config/GlobalConfigManager.js.map +1 -1
- package/hooks/scripts/context-save.js +88 -1
- package/hooks/scripts/pre-tool-guard.js +30 -5
- package/hooks/scripts/sentinel-guard.js +28 -4
- package/hooks/scripts/utils.js +48 -0
- package/package.json +1 -1
- package/skills/vibe-figma/SKILL.md +25 -66
- package/skills/vibe-figma-analyze/SKILL.md +95 -318
- package/skills/vibe-figma-codegen/SKILL.md +112 -782
- package/skills/vibe-figma-consolidate/SKILL.md +53 -90
- package/skills/vibe-figma-frame/SKILL.md +454 -181
- package/skills/vibe-figma-pipeline/SKILL.md +4 -45
- package/skills/vibe-figma-rules/SKILL.md +363 -159
- package/skills/vibe-figma-style/SKILL.md +107 -476
|
@@ -1,866 +1,196 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: vibe-figma-codegen
|
|
3
|
-
description: Code generation — markup, stack rules,
|
|
3
|
+
description: Code generation — markup quality, stack rules, responsive, verification
|
|
4
4
|
triggers: []
|
|
5
5
|
tier: standard
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Skill: vibe-figma-codegen — 코드 생성 규칙
|
|
9
9
|
|
|
10
|
-
마크업 품질, 스택별 코드 생성,
|
|
10
|
+
마크업 품질, 스택별 코드 생성, 반응형, 검증.
|
|
11
|
+
이미지 패턴은 **vibe-figma-rules R-4** + **vibe-figma-frame B-3.4** 참조.
|
|
11
12
|
|
|
12
13
|
---
|
|
13
14
|
|
|
14
|
-
##
|
|
15
|
-
|
|
16
|
-
### 5-1. Semantic HTML (Mandatory)
|
|
17
|
-
|
|
18
|
-
Every element MUST use the most specific semantic tag available. `<div>` is a last resort.
|
|
19
|
-
|
|
20
|
-
| Visual Element | Correct Tag | Wrong |
|
|
21
|
-
|---------------|------------|-------|
|
|
22
|
-
| Page section | `<section>`, `<article>`, `<aside>` | `<div>` |
|
|
23
|
-
| Navigation | `<nav>` | `<div class="nav">` |
|
|
24
|
-
| Page header | `<header>` | `<div class="header">` |
|
|
25
|
-
| Page footer | `<footer>` | `<div class="footer">` |
|
|
26
|
-
| Heading hierarchy | `<h1>`→`<h6>` (sequential, no skips) | `<div class="title">` |
|
|
27
|
-
| Paragraph text | `<p>` | `<div>` or `<span>` |
|
|
28
|
-
| List of items | `<ul>`/`<ol>` + `<li>` | `<div>` repeated |
|
|
29
|
-
| Clickable action | `<button>` | `<div onClick>` |
|
|
30
|
-
| Navigation link | `<a href>` | `<span onClick>` |
|
|
31
|
-
| Form field | `<input>` + `<label>` | `<div contenteditable>` |
|
|
32
|
-
| Image | `<img alt="descriptive">` or `<figure>` + `<figcaption>` | `<div style="background-image">` for content images |
|
|
33
|
-
| Tabular data | `<table>` + `<thead>` + `<tbody>` | `<div>` grid |
|
|
34
|
-
| Time/Date | `<time datetime>` | `<span>` |
|
|
35
|
-
| Emphasized text | `<strong>`, `<em>` | `<span class="bold">` |
|
|
36
|
-
| Grouped fields | `<fieldset>` + `<legend>` | `<div>` |
|
|
37
|
-
|
|
38
|
-
### 5-2. Accessibility Checklist
|
|
39
|
-
|
|
40
|
-
Every generated component MUST pass:
|
|
41
|
-
|
|
42
|
-
- [ ] All interactive elements keyboard-reachable (tab order)
|
|
43
|
-
- [ ] `<button>` for actions, `<a>` for navigation — never reversed
|
|
44
|
-
- [ ] `<img>` has descriptive `alt` (not "image", not filename)
|
|
45
|
-
- [ ] Form `<input>` linked to `<label>` (via `htmlFor` / `id`)
|
|
46
|
-
- [ ] Color contrast >= 4.5:1 (text), >= 3:1 (large text, UI controls)
|
|
47
|
-
- [ ] Focus indicator visible on all interactive elements
|
|
48
|
-
- [ ] `aria-label` on icon-only buttons
|
|
49
|
-
- [ ] `role` attribute on custom interactive widgets
|
|
50
|
-
- [ ] Heading hierarchy is sequential (no h1 → h3 skip)
|
|
51
|
-
- [ ] `<ul>`/`<ol>` for any visually listed items
|
|
52
|
-
|
|
53
|
-
### 5-3. Wrapper Elimination (Fragment / template)
|
|
54
|
-
|
|
55
|
-
**불필요한 래핑 태그를 제거**하고 프레임워크가 제공하는 투명 래퍼를 사용:
|
|
56
|
-
|
|
57
|
-
| Stack | 투명 래퍼 | 사용 시점 |
|
|
58
|
-
|-------|----------|----------|
|
|
59
|
-
| React / Next.js | `<>...</>` 또는 `<React.Fragment>` | 형제 요소를 그룹핑할 때 (DOM에 노드 추가 안 함) |
|
|
60
|
-
| Vue / Nuxt | `<template>` (컴포넌트 루트 이외) | `v-if`, `v-for` 로 여러 요소를 조건부 렌더링할 때 |
|
|
61
|
-
| Svelte | `{#if}`, `{#each}` 블록 | 자체적으로 래핑 불필요 |
|
|
62
|
-
|
|
63
|
-
```tsx
|
|
64
|
-
// WRONG: 불필요한 div 래핑
|
|
65
|
-
<div>
|
|
66
|
-
<Header />
|
|
67
|
-
<Main />
|
|
68
|
-
<Footer />
|
|
69
|
-
</div>
|
|
70
|
-
|
|
71
|
-
// CORRECT: React Fragment
|
|
72
|
-
<>
|
|
73
|
-
<Header />
|
|
74
|
-
<Main />
|
|
75
|
-
<Footer />
|
|
76
|
-
</>
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
```vue
|
|
80
|
-
<!-- WRONG: 불필요한 div 래핑 -->
|
|
81
|
-
<div v-if="showGroup">
|
|
82
|
-
<ItemA />
|
|
83
|
-
<ItemB />
|
|
84
|
-
</div>
|
|
85
|
-
|
|
86
|
-
<!-- CORRECT: Vue template (DOM에 렌더링 안 됨) -->
|
|
87
|
-
<template v-if="showGroup">
|
|
88
|
-
<ItemA />
|
|
89
|
-
<ItemB />
|
|
90
|
-
</template>
|
|
91
|
-
```
|
|
15
|
+
## C-1. Semantic HTML (필수)
|
|
92
16
|
|
|
93
|
-
|
|
17
|
+
모든 요소는 가장 구체적인 semantic 태그를 사용. `<div>`는 최후 수단.
|
|
94
18
|
|
|
95
|
-
|
|
19
|
+
| 시각 요소 | 올바른 태그 | 잘못된 태그 |
|
|
20
|
+
|----------|-----------|-----------|
|
|
21
|
+
| 페이지 섹션 | `<section>`, `<article>`, `<aside>` | `<div>` |
|
|
22
|
+
| 네비게이션 | `<nav>` | `<div class="nav">` |
|
|
23
|
+
| 헤더/푸터 | `<header>`, `<footer>` | `<div class="header">` |
|
|
24
|
+
| 제목 계층 | `<h1>`→`<h6>` (순차, 건너뛰기 금지) | `<div class="title">` |
|
|
25
|
+
| 텍스트 | `<p>` | `<div>` or `<span>` |
|
|
26
|
+
| 리스트 | `<ul>`/`<ol>` + `<li>` | `<div>` 반복 |
|
|
27
|
+
| 액션 버튼 | `<button>` | `<div onClick>` |
|
|
28
|
+
| 링크 | `<a href>` | `<span onClick>` |
|
|
29
|
+
| 폼 필드 | `<input>` + `<label>` | `<div contenteditable>` |
|
|
30
|
+
| 이미지 | `<img alt="설명">` | content 이미지에 `background-image` |
|
|
31
|
+
| 시간 | `<time datetime>` | `<span>` |
|
|
96
32
|
|
|
97
|
-
|
|
33
|
+
## C-2. Accessibility Checklist
|
|
98
34
|
|
|
99
35
|
```
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
36
|
+
- [ ] 모든 인터랙티브 요소 키보드 접근 가능
|
|
37
|
+
- [ ] <button> = 액션, <a> = 네비게이션 (역할 혼용 금지)
|
|
38
|
+
- [ ] <img> alt 설명적 (장식은 alt="" + aria-hidden)
|
|
39
|
+
- [ ] <input> ↔ <label> 연결
|
|
40
|
+
- [ ] 색상 대비 >= 4.5:1 (텍스트), >= 3:1 (큰 텍스트, UI 컨트롤)
|
|
41
|
+
- [ ] 포커스 인디케이터 표시
|
|
42
|
+
- [ ] 아이콘 전용 버튼에 aria-label
|
|
43
|
+
- [ ] 제목 계층 순차적
|
|
107
44
|
```
|
|
108
45
|
|
|
109
|
-
|
|
110
|
-
|-------|----------|
|
|
111
|
-
| React | `variant` prop + 조건부 className / style |
|
|
112
|
-
| Vue | `variant` prop + `<slot>` for 커스텀 영역 |
|
|
113
|
-
| Svelte | `variant` prop + `<slot>` |
|
|
114
|
-
| React Native | `variant` prop + StyleSheet 조건 선택 |
|
|
115
|
-
|
|
116
|
-
```tsx
|
|
117
|
-
// React — 하나의 Card 컴포넌트가 3가지 변형 처리
|
|
118
|
-
interface CardProps {
|
|
119
|
-
variant: 'default' | 'highlight' | 'compact';
|
|
120
|
-
title: string;
|
|
121
|
-
children: React.ReactNode;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function Card({ variant, title, children }: CardProps): JSX.Element {
|
|
125
|
-
return (
|
|
126
|
-
<article className={styles[variant]}>
|
|
127
|
-
<h3 className={styles.title}>{title}</h3>
|
|
128
|
-
{children}
|
|
129
|
-
</article>
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
```
|
|
46
|
+
## C-3. 래퍼 제거 + 컴포넌트 통합
|
|
133
47
|
|
|
134
|
-
|
|
135
|
-
<!-- Vue — slot으로 커스텀 영역 제공 -->
|
|
136
|
-
<template>
|
|
137
|
-
<article :class="$style[variant]">
|
|
138
|
-
<h3 :class="$style.title">{{ title }}</h3>
|
|
139
|
-
<slot />
|
|
140
|
-
</article>
|
|
141
|
-
</template>
|
|
142
|
-
|
|
143
|
-
<script setup lang="ts">
|
|
144
|
-
defineProps<{ variant: 'default' | 'highlight' | 'compact'; title: string }>();
|
|
145
|
-
</script>
|
|
146
|
-
```
|
|
48
|
+
래퍼 제거 및 80% Rule은 **vibe-figma-rules R-5** 참조.
|
|
147
49
|
|
|
148
|
-
###
|
|
50
|
+
### 컴포넌트 구조 제한
|
|
149
51
|
|
|
150
52
|
```
|
|
151
|
-
Max nesting depth: 3 levels
|
|
53
|
+
Max nesting depth: 3 levels
|
|
152
54
|
Max component length: 50 lines
|
|
153
55
|
Max props: 5 per component
|
|
154
56
|
```
|
|
155
57
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
| Signal | Action |
|
|
159
|
-
|--------|--------|
|
|
160
|
-
| Component > 50 lines | Split into sub-components |
|
|
161
|
-
| Repeated visual pattern (2+ times) | Extract shared component |
|
|
162
|
-
| **Similar pattern (80%+ match)** | **Single component + variant prop** |
|
|
163
|
-
| Distinct visual boundary (card, modal, form) | Own component + own style file |
|
|
164
|
-
| 3+ related props | Group into object prop or extract sub-component |
|
|
165
|
-
|
|
166
|
-
### 5-6. Markup Anti-Patterns (NEVER Generate)
|
|
167
|
-
|
|
168
|
-
```tsx
|
|
169
|
-
// WRONG: div soup
|
|
170
|
-
<div className="card">
|
|
171
|
-
<div className="card-header">
|
|
172
|
-
<div className="title">Login</div>
|
|
173
|
-
</div>
|
|
174
|
-
<div className="card-body">
|
|
175
|
-
<div className="input-group">
|
|
176
|
-
<div className="label">Email</div>
|
|
177
|
-
<div className="input"><input /></div>
|
|
178
|
-
</div>
|
|
179
|
-
</div>
|
|
180
|
-
</div>
|
|
181
|
-
|
|
182
|
-
// CORRECT: semantic markup
|
|
183
|
-
<article className={styles.card}>
|
|
184
|
-
<header className={styles.header}>
|
|
185
|
-
<h2 className={styles.title}>Login</h2>
|
|
186
|
-
</header>
|
|
187
|
-
<form className={styles.body}>
|
|
188
|
-
<fieldset className={styles.fieldGroup}>
|
|
189
|
-
<label htmlFor="email" className={styles.label}>Email</label>
|
|
190
|
-
<input id="email" type="email" className={styles.input} />
|
|
191
|
-
</fieldset>
|
|
192
|
-
</form>
|
|
193
|
-
</article>
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
```tsx
|
|
197
|
-
// WRONG: 불필요한 래핑
|
|
198
|
-
<div>
|
|
199
|
-
<ComponentA />
|
|
200
|
-
<ComponentB />
|
|
201
|
-
</div>
|
|
202
|
-
|
|
203
|
-
// CORRECT: Fragment
|
|
204
|
-
<>
|
|
205
|
-
<ComponentA />
|
|
206
|
-
<ComponentB />
|
|
207
|
-
</>
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
```tsx
|
|
211
|
-
// WRONG: 유사한 UI를 별도 컴포넌트로
|
|
212
|
-
<DefaultCard /> // 구조 동일, 색상만 다름
|
|
213
|
-
<HighlightCard /> // 구조 동일, 색상만 다름
|
|
214
|
-
|
|
215
|
-
// CORRECT: 단일 컴포넌트 + variant
|
|
216
|
-
<Card variant="default" />
|
|
217
|
-
<Card variant="highlight" />
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
---
|
|
221
|
-
|
|
222
|
-
## Phase 6: Code Generation
|
|
223
|
-
|
|
224
|
-
### 6-0. Apply Storyboard Spec + Design Context
|
|
225
|
-
|
|
226
|
-
**스토리보드 스펙 (Phase 0-1)이 있으면 우선 적용:**
|
|
227
|
-
|
|
228
|
-
| storyboardSpec | Effect on Code Generation |
|
|
229
|
-
|----------------|--------------------------|
|
|
230
|
-
| `interactions` | 호버/클릭/스크롤 이벤트 → CSS `:hover`/`:active`/`:focus` + JS 핸들러 |
|
|
231
|
-
| `animations` | 트랜지션/애니메이션 → `transition`, `@keyframes`, timing/easing 스펙대로 |
|
|
232
|
-
| `states` | 로딩/에러/성공/빈 상태 → 조건부 렌더링 + 상태별 UI 컴포넌트 |
|
|
233
|
-
| `breakpoints` | Phase 3-3에서 이미 적용됨 |
|
|
234
|
-
| `colorGuide` | 스토리보드 컬러 가이드 → 토큰 검증 |
|
|
235
|
-
| `typographyGuide` | 스토리보드 타이포 가이드 → 토큰 검증 |
|
|
236
|
-
|
|
237
|
-
**design-context.json이 있으면 추가 적용:**
|
|
238
|
-
|
|
239
|
-
| Context Field | Effect on Code Generation |
|
|
240
|
-
|---------------|--------------------------|
|
|
241
|
-
| `constraints.accessibility = "AAA"` | Use `aria-describedby` on all form fields, `prefers-reduced-motion` media query, `prefers-contrast` support |
|
|
242
|
-
| `constraints.devices = ["mobile"]` | Mobile-only layout, no desktop breakpoints, touch target ≥ 44px |
|
|
243
|
-
| `constraints.devices = ["mobile","desktop"]` | Mobile-first with `md:` breakpoint |
|
|
244
|
-
| `aesthetic.style = "minimal"` | Reduce visual weight — fewer shadows, thinner borders, more whitespace |
|
|
245
|
-
| `aesthetic.style = "bold"` | Stronger shadows, thicker borders, larger typography scale |
|
|
246
|
-
| `brand.personality` | Preserve brand-unique visual patterns (do NOT distill these away) |
|
|
247
|
-
| `detectedStack.fonts` | Use project's existing font stack instead of Figma's font family |
|
|
248
|
-
|
|
249
|
-
### 6-1. Stack-Specific Rules
|
|
250
|
-
|
|
251
|
-
Generate code following these rules per stack:
|
|
252
|
-
|
|
253
|
-
#### React / Next.js + TypeScript
|
|
254
|
-
|
|
255
|
-
```
|
|
256
|
-
- Functional components with explicit return types
|
|
257
|
-
- Props interface defined above component
|
|
258
|
-
- Named exports (not default)
|
|
259
|
-
- <></> Fragment for sibling grouping (no unnecessary wrapper div)
|
|
260
|
-
- CSS Modules: import styles from './Component.module.css'
|
|
261
|
-
- Tailwind: classes in JSX, extract repeated patterns to @apply
|
|
262
|
-
- Responsive: mobile-first breakpoints
|
|
263
|
-
- Variant pattern: discriminated union props for similar UI
|
|
264
|
-
- useMemo/useCallback only when measurably needed (not by default)
|
|
265
|
-
- Next.js: use 'use client' only when client interactivity needed
|
|
266
|
-
- Next.js: Image component for images, Link for navigation
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
#### Vue 3 / Nuxt
|
|
270
|
-
|
|
271
|
-
```
|
|
272
|
-
- <script setup lang="ts"> composition API
|
|
273
|
-
- defineProps with TypeScript interface (with defaults via withDefaults)
|
|
274
|
-
- <template> for invisible grouping (v-if, v-for on multiple elements)
|
|
275
|
-
- <style scoped> (or <style module>) with CSS custom property references
|
|
276
|
-
- v-bind in <style> for dynamic values: color: v-bind(themeColor)
|
|
277
|
-
- <slot> + named slots for composable variant patterns
|
|
278
|
-
- <Teleport> for modals/overlays
|
|
279
|
-
- Or Tailwind classes in template
|
|
280
|
-
- Nuxt: <NuxtLink> for navigation, <NuxtImg> for images
|
|
281
|
-
- Nuxt: definePageMeta for page-level config
|
|
282
|
-
- computed() for derived state (not methods for template expressions)
|
|
283
|
-
```
|
|
58
|
+
### 분리 트리거
|
|
284
59
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
- <slot> for composable patterns
|
|
292
|
-
- <style> block with CSS custom property references
|
|
293
|
-
- Or Tailwind classes in markup
|
|
294
|
-
- Reactive declarations ($:) for derived values
|
|
295
|
-
- transition: directive for animations
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
#### SCSS (any framework with SCSS)
|
|
299
|
-
|
|
300
|
-
```
|
|
301
|
-
- @use 'figma-tokens' as figma — namespaced import (not @import)
|
|
302
|
-
- $변수 for tokens: figma.$figma-primary, figma.$figma-space-4
|
|
303
|
-
- @include figma.figma-pc { } for breakpoint — @media 직접 사용 금지
|
|
304
|
-
- figma-fluid($mobile, $desktop) for responsive values — 수동 clamp() 금지
|
|
305
|
-
- Nesting max 3 levels: .section { .title { .icon { } } } ← 한계
|
|
306
|
-
- & for BEM-like modifiers: &--active, &__title
|
|
307
|
-
- @each for variant generation from map
|
|
308
|
-
- Partials: _figma-tokens.scss, _figma-mixins.scss
|
|
309
|
-
- Vue: <style lang="scss" scoped> with @use
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
```scss
|
|
313
|
-
// Component usage example
|
|
314
|
-
@use 'figma-tokens' as figma;
|
|
315
|
-
|
|
316
|
-
.heroSection {
|
|
317
|
-
position: relative;
|
|
318
|
-
padding: figma.figma-fluid(1rem, 3rem);
|
|
319
|
-
|
|
320
|
-
&Title {
|
|
321
|
-
font-size: figma.figma-fluid(1.5rem, 3rem);
|
|
322
|
-
color: figma.$figma-text-primary;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
&Cta {
|
|
326
|
-
background: figma.$figma-primary;
|
|
327
|
-
border-radius: figma.$figma-radius-md;
|
|
328
|
-
|
|
329
|
-
&:hover { background: figma.$figma-primary-hover; }
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
.cardGrid {
|
|
334
|
-
display: grid;
|
|
335
|
-
grid-template-columns: 1fr;
|
|
336
|
-
|
|
337
|
-
@include figma.figma-pc {
|
|
338
|
-
grid-template-columns: repeat(3, 1fr);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
```
|
|
60
|
+
| 신호 | 조치 |
|
|
61
|
+
|------|------|
|
|
62
|
+
| 컴포넌트 > 50줄 | 서브 컴포넌트로 분리 |
|
|
63
|
+
| 시각 패턴 2회+ 반복 | 공유 컴포넌트 추출 |
|
|
64
|
+
| 유사도 80%+ | 단일 컴포넌트 + variant prop (R-5) |
|
|
65
|
+
| 명확한 시각 경계 (카드, 모달, 폼) | 자체 컴포넌트 + 스타일 파일 |
|
|
342
66
|
|
|
343
|
-
|
|
67
|
+
## C-4. 스토리보드 스펙 + Design Context 적용
|
|
344
68
|
|
|
345
69
|
```
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
#### Flutter (Dart)
|
|
70
|
+
storyboardSpec → 코드:
|
|
71
|
+
interactions → CSS :hover/:active/:focus + JS 핸들러
|
|
72
|
+
animations → transition, @keyframes (타이밍/이징 스펙대로)
|
|
73
|
+
states → 조건부 렌더링 + 상태별 UI
|
|
74
|
+
colorGuide / typographyGuide → 토큰 검증
|
|
354
75
|
|
|
76
|
+
design-context.json → 코드:
|
|
77
|
+
accessibility = "AAA" → aria-describedby, prefers-reduced-motion
|
|
78
|
+
devices = ["mobile"] → 모바일 전용, touch target >= 44px
|
|
79
|
+
aesthetic.style = "minimal" → 적은 그림자, 얇은 보더
|
|
80
|
+
aesthetic.style = "bold" → 강한 그림자, 두꺼운 보더
|
|
81
|
+
brand.personality → 브랜드 고유 패턴 보존
|
|
355
82
|
```
|
|
356
|
-
- StatelessWidget or StatefulWidget as appropriate
|
|
357
|
-
- Theme.of(context) for design tokens
|
|
358
|
-
- Extract shared values to lib/theme/figma_tokens.dart
|
|
359
|
-
- Proper widget composition
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
### 6-2. Image Code Patterns (from Phase 2-A classification)
|
|
363
|
-
|
|
364
|
-
Phase 2-A에서 분류된 이미지 유형별 코드 생성 규칙:
|
|
365
|
-
|
|
366
|
-
#### Background Image Class Separation (핵심 원칙)
|
|
367
|
-
|
|
368
|
-
배경 이미지 관련 요소는 **용도별로 별도 클래스**로 분리한다. 레이아웃과 이미지를 합치지 않는다.
|
|
369
|
-
|
|
370
|
-
##### 별도 클래스로 분리해야 하는 유형
|
|
371
83
|
|
|
372
|
-
|
|
373
|
-
|------|-----------|----------|
|
|
374
|
-
| **섹션 전면 배경** | `.heroBg`, `.eventBg`, `.rewardsBg` | 이벤트 기간/시즌별 이미지 교체 |
|
|
375
|
-
| **오버레이** | `.heroBgOverlay`, `.eventBgOverlay` | 투명도/그라데이션 독립 조절 |
|
|
376
|
-
| **패턴/텍스처** | `.sectionPattern`, `.headerTexture` | `background-repeat`/`size` 별도 제어 |
|
|
377
|
-
| **파티클/장식 효과** | `.heroParticle`, `.sparkleEffect` | 애니메이션 on/off, 성능 이슈 시 제거 |
|
|
378
|
-
| **캐릭터/일러스트** | `.heroCharacter`, `.eventIllust` | 콜라보/캐릭터별 교체, position 조절 |
|
|
379
|
-
| **그라데이션** | `.sectionGradient`, `.fadeBottom` | 테마별 색상 변경 |
|
|
380
|
-
| **비디오 포스터** | `.videoPoster` | 비디오 로드 전 폴백, JS에서 교체 |
|
|
84
|
+
## C-5. 스택별 코드 생성 규칙
|
|
381
85
|
|
|
382
|
-
|
|
86
|
+
### React / Next.js + TypeScript
|
|
383
87
|
|
|
384
|
-
Figma에서 배경 이미지가 있는 섹션은 다음 레이어 구조로 생성:
|
|
385
|
-
|
|
386
|
-
```
|
|
387
|
-
.{section}Section ← 레이아웃 (position, size, padding, overflow)
|
|
388
|
-
.{section}Bg ← 배경 이미지 (URL, size, position) — z-index: 0
|
|
389
|
-
.{section}BgOverlay ← 오버레이 (gradient, opacity) — z-index: 1
|
|
390
|
-
.{section}Character ← 캐릭터/일러스트 (선택) — z-index: 2
|
|
391
|
-
.{section}Pattern ← 패턴/텍스처 (선택) — z-index: 1
|
|
392
|
-
.{section}Content ← 텍스트/버튼/UI — z-index: 최상위
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
```tsx
|
|
396
|
-
// 실제 마크업 예시 — 게임 이벤트 히어로 섹션
|
|
397
|
-
<section className={styles.heroSection}>
|
|
398
|
-
<div className={styles.heroBg} />
|
|
399
|
-
<div className={styles.heroBgOverlay} />
|
|
400
|
-
<div className={styles.heroCharacter} />
|
|
401
|
-
<div className={styles.heroContent}>
|
|
402
|
-
<h1 className={styles.heroTitle}>Stellar Blade × PUBG</h1>
|
|
403
|
-
<p className={styles.heroDescription}>기간한정 콜라보 이벤트</p>
|
|
404
|
-
<a href="#rewards" className={styles.heroCta}>보상 확인하기</a>
|
|
405
|
-
</div>
|
|
406
|
-
</section>
|
|
407
88
|
```
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
.heroBg {
|
|
414
|
-
position: absolute; inset: 0; z-index: 0;
|
|
415
|
-
background-image: url('/assets/hero-bg.webp');
|
|
416
|
-
background-size: cover;
|
|
417
|
-
background-position: center;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
.heroBgOverlay {
|
|
421
|
-
position: absolute; inset: 0; z-index: 1;
|
|
422
|
-
background: linear-gradient(to bottom, rgba(0,0,0,0.3), rgba(0,0,0,0.7));
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
.heroCharacter {
|
|
426
|
-
position: absolute; bottom: 0; right: 5%; z-index: 2;
|
|
427
|
-
width: 40%; height: 80%;
|
|
428
|
-
background-image: url('/assets/hero-character.webp');
|
|
429
|
-
background-size: contain;
|
|
430
|
-
background-position: bottom center;
|
|
431
|
-
background-repeat: no-repeat;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
.heroContent { position: relative; z-index: 3; }
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
**모든 배경 관련 이미지가 독립 클래스**이므로:
|
|
438
|
-
- JS에서 `.heroBg`만 교체하면 배경만 바뀜
|
|
439
|
-
- `.heroCharacter`만 교체하면 캐릭터만 바뀜
|
|
440
|
-
- `.heroBgOverlay`의 opacity를 조절하면 텍스트 가독성만 조절 가능
|
|
441
|
-
- 성능 이슈 시 `.heroParticle`만 `display: none`
|
|
442
|
-
|
|
443
|
-
#### Responsive Background Image (반응형 배경 — PC/Mobile 분기)
|
|
444
|
-
|
|
445
|
-
```css
|
|
446
|
-
/* 배경 이미지 클래스 안에서만 반응형 처리 */
|
|
447
|
-
.heroBg {
|
|
448
|
-
background-image: url('/assets/hero-mobile.webp');
|
|
449
|
-
background-size: cover;
|
|
450
|
-
background-position: center;
|
|
451
|
-
position: absolute;
|
|
452
|
-
inset: 0;
|
|
453
|
-
z-index: 0;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
@media (min-width: 1024px) { /* {breakpoint}px */
|
|
457
|
-
.heroBg {
|
|
458
|
-
background-image: url('/assets/hero-pc.webp');
|
|
459
|
-
}
|
|
460
|
-
}
|
|
89
|
+
- 함수형 컴포넌트, 명시적 return type
|
|
90
|
+
- Props interface 정의, named exports
|
|
91
|
+
- <></> Fragment
|
|
92
|
+
- CSS Modules 또는 Tailwind
|
|
93
|
+
- Next.js: 'use client' 필요 시만, Image/Link 컴포넌트 사용
|
|
461
94
|
```
|
|
462
95
|
|
|
463
|
-
|
|
96
|
+
### Vue 3 / Nuxt
|
|
464
97
|
|
|
465
|
-
```css
|
|
466
|
-
.heroBg { background-image: url('/assets/hero-bg.webp'); /* ... */ }
|
|
467
|
-
.eventBg { background-image: url('/assets/event-bg.webp'); /* ... */ }
|
|
468
|
-
.rewardsBg { background-image: url('/assets/rewards-bg.webp'); /* ... */ }
|
|
469
98
|
```
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
// React / Next.js
|
|
477
|
-
<img
|
|
478
|
-
src="/assets/product.webp"
|
|
479
|
-
alt="상품 설명" // Figma 레이어 이름 기반
|
|
480
|
-
width={600} // Figma 레이어 width (scaled)
|
|
481
|
-
height={400} // Figma 레이어 height (scaled)
|
|
482
|
-
loading="lazy" // 뷰포트 밖이면 lazy
|
|
483
|
-
/>
|
|
484
|
-
|
|
485
|
-
// Next.js — Image 컴포넌트 우선
|
|
486
|
-
import Image from 'next/image';
|
|
487
|
-
<Image
|
|
488
|
-
src="/assets/product.webp"
|
|
489
|
-
alt="상품 설명"
|
|
490
|
-
width={600}
|
|
491
|
-
height={400}
|
|
492
|
-
priority={false} // hero 이미지만 priority={true}
|
|
493
|
-
/>
|
|
494
|
-
```
|
|
495
|
-
|
|
496
|
-
```vue
|
|
497
|
-
<!-- Nuxt — NuxtImg 우선 -->
|
|
498
|
-
<NuxtImg
|
|
499
|
-
src="/assets/product.webp"
|
|
500
|
-
alt="상품 설명"
|
|
501
|
-
width="600"
|
|
502
|
-
height="400"
|
|
503
|
-
loading="lazy"
|
|
504
|
-
/>
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
#### Responsive Content Image (뷰포트별 다른 이미지)
|
|
508
|
-
|
|
509
|
-
```html
|
|
510
|
-
<picture>
|
|
511
|
-
<source media="(min-width: 1024px)" srcset="/assets/hero-pc.webp" />
|
|
512
|
-
<img src="/assets/hero-mobile.webp" alt="히어로 이미지" loading="eager" />
|
|
513
|
-
</picture>
|
|
99
|
+
- <script setup lang="ts"> composition API
|
|
100
|
+
- defineProps + TypeScript interface
|
|
101
|
+
- <template>로 invisible 그룹핑
|
|
102
|
+
- <style scoped> or <style module>
|
|
103
|
+
- <Teleport> for 모달/오버레이
|
|
104
|
+
- Nuxt: <NuxtLink>, <NuxtImg> 사용
|
|
514
105
|
```
|
|
515
106
|
|
|
516
|
-
|
|
107
|
+
### Svelte
|
|
517
108
|
|
|
518
|
-
| 규칙 | 설명 |
|
|
519
|
-
|------|------|
|
|
520
|
-
| **format** | `.webp` 우선 (fallback: `.png`/`.jpg`) |
|
|
521
|
-
| **alt** | Figma 레이어 이름에서 파생, 장식 이미지는 `alt=""` + `aria-hidden="true"` |
|
|
522
|
-
| **width/height** | 항상 명시 (CLS 방지), Figma 레이어 크기를 스케일 팩터 적용 후 사용 |
|
|
523
|
-
| **loading** | 뷰포트 상단(hero, header) → `eager`, 나머지 → `lazy` |
|
|
524
|
-
| **object-fit** | `cover` (배경/히어로), `contain` (로고/아이콘), `fill` 사용 금지 |
|
|
525
|
-
| **반응형 크기** | Content image는 `max-width: 100%; height: auto;` 기본 |
|
|
526
|
-
| **배경 이미지 + 텍스트** | 반드시 오버레이(`::before` 또는 gradient)로 텍스트 가독성 확보 |
|
|
527
|
-
| **장식 패턴** | `background-repeat: repeat` + `background-size` 조절 |
|
|
528
|
-
|
|
529
|
-
#### Anti-Patterns
|
|
530
|
-
|
|
531
|
-
```tsx
|
|
532
|
-
// WRONG: 레이아웃 클래스에 background-image를 합침
|
|
533
|
-
<section className={styles.hero} /> /* .hero에 bg-image + padding + flex 등 다 섞임 */
|
|
534
|
-
|
|
535
|
-
// CORRECT: 배경 이미지는 별도 클래스
|
|
536
|
-
<section className={styles.heroSection}>
|
|
537
|
-
<div className={styles.heroBg} />
|
|
538
|
-
<div className={styles.heroContent}>...</div>
|
|
539
|
-
</section>
|
|
540
109
|
```
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
<
|
|
545
|
-
|
|
546
|
-
// CORRECT: 전용 클래스에 이미지 URL
|
|
547
|
-
<div className={styles.heroBg} />
|
|
548
|
-
/* JS 동적 교체 시: element.style.backgroundImage = url(...) on .heroBg만 */
|
|
110
|
+
- <script lang="ts">
|
|
111
|
+
- export let + 타입
|
|
112
|
+
- {#if}/{#each}/{#await} 블록
|
|
113
|
+
- <slot>, transition: directive
|
|
549
114
|
```
|
|
550
115
|
|
|
551
|
-
|
|
552
|
-
/* WRONG: 배경 + 텍스트, 오버레이 없음 */
|
|
553
|
-
.hero { background-image: url(...); color: white; }
|
|
116
|
+
### SCSS (공통)
|
|
554
117
|
|
|
555
|
-
/* CORRECT: 3-layer 분리 (bg / overlay / content) */
|
|
556
|
-
.heroSection { position: relative; }
|
|
557
|
-
.heroBg { background-image: url(...); position: absolute; inset: 0; z-index: 0; }
|
|
558
|
-
.heroBgOverlay { position: absolute; inset: 0; background: rgba(0,0,0,0.5); z-index: 1; }
|
|
559
|
-
.heroContent { position: relative; z-index: 2; color: white; }
|
|
560
118
|
```
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
.overlay { }
|
|
567
|
-
|
|
568
|
-
/* CORRECT: 섹션별 명시적 이름 */
|
|
569
|
-
.heroBg { }
|
|
570
|
-
.heroBgOverlay { }
|
|
571
|
-
.eventBg { }
|
|
572
|
-
.rewardsBg { }
|
|
119
|
+
- @use 'figma-tokens' as figma — 네임스페이스 import
|
|
120
|
+
- @include figma.figma-pc { } — @media 직접 사용 금지
|
|
121
|
+
- figma-fluid($mobile, $desktop) — 수동 clamp() 금지
|
|
122
|
+
- Nesting max 3 levels
|
|
123
|
+
- & for BEM-like modifiers
|
|
573
124
|
```
|
|
574
125
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
When `responsive.json` exists, apply these rules on top of stack-specific rules:
|
|
578
|
-
|
|
579
|
-
#### Principle: Fluid First, Breakpoint Second
|
|
126
|
+
## C-6. 반응형 코드 생성
|
|
580
127
|
|
|
581
|
-
|
|
582
|
-
1. Typography & Spacing → clamp() fluid tokens (no breakpoints needed)
|
|
583
|
-
2. Layout direction changes → @media at project breakpoint (flex-direction, grid-template)
|
|
584
|
-
3. Visibility toggles → @media at project breakpoint (display: none/block)
|
|
585
|
-
4. Component swaps → conditional render with useMediaQuery or CSS
|
|
586
|
-
```
|
|
128
|
+
### 원칙: Fluid First, Breakpoint Second
|
|
587
129
|
|
|
588
|
-
**Breakpoint selection**: Use `{breakpoints}` from Phase 3-3. Pick the breakpoint closest to where the layout structurally changes between Figma viewports. For example:
|
|
589
|
-
- Figma mobile=375px, desktop=1440px → use project's `md` (typically 768px) as primary breakpoint
|
|
590
|
-
- If project has `tablet: 1024px` → use that instead
|
|
591
|
-
- If 3 viewports (mobile/tablet/desktop) → use 2 breakpoints (e.g., `sm` and `lg`)
|
|
592
|
-
|
|
593
|
-
#### CSS Modules (responsive example)
|
|
594
|
-
|
|
595
|
-
```css
|
|
596
|
-
/* Component.module.css */
|
|
597
|
-
/* Breakpoints from project: {breakpoints} */
|
|
598
|
-
|
|
599
|
-
.container {
|
|
600
|
-
display: flex;
|
|
601
|
-
flex-direction: column; /* mobile-first */
|
|
602
|
-
gap: var(--figma-space-content); /* fluid: clamp() */
|
|
603
|
-
padding: var(--figma-space-section); /* fluid: clamp() */
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
.title {
|
|
607
|
-
font-size: var(--figma-text-h1); /* fluid: clamp() */
|
|
608
|
-
line-height: 1.2;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
.cardGrid {
|
|
612
|
-
display: grid;
|
|
613
|
-
grid-template-columns: 1fr; /* mobile: single column */
|
|
614
|
-
gap: var(--figma-space-content);
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
/* Layout breakpoint — use project's breakpoint, NOT hardcoded */
|
|
618
|
-
@media (min-width: 1024px) { /* {breakpoint}px from Phase 3-3 */
|
|
619
|
-
.container {
|
|
620
|
-
flex-direction: row;
|
|
621
|
-
}
|
|
622
|
-
.cardGrid {
|
|
623
|
-
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
/* Visibility toggle — same breakpoint */
|
|
628
|
-
.mobileOnly { display: block; }
|
|
629
|
-
.desktopOnly { display: none; }
|
|
630
|
-
|
|
631
|
-
@media (min-width: 1024px) { /* {breakpoint}px from Phase 3-3 */
|
|
632
|
-
.mobileOnly { display: none; }
|
|
633
|
-
.desktopOnly { display: block; }
|
|
634
|
-
}
|
|
635
130
|
```
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
```tsx
|
|
642
|
-
{/* Uses project's Tailwind breakpoints — md: maps to project's screens.md */}
|
|
643
|
-
<div className="flex flex-col md:flex-row gap-[--figma-space-content] p-[--figma-space-section]">
|
|
644
|
-
<h1 className="text-[length:--figma-text-h1] leading-tight">Title</h1>
|
|
645
|
-
<div className="grid grid-cols-1 md:grid-cols-3 gap-[--figma-space-content]">
|
|
646
|
-
{/* cards */}
|
|
647
|
-
</div>
|
|
648
|
-
<nav className="md:hidden">Mobile Nav</nav>
|
|
649
|
-
<nav className="hidden md:flex">Desktop Nav</nav>
|
|
650
|
-
</div>
|
|
131
|
+
1. Typography & Spacing → clamp() fluid 토큰 (브레이크포인트 불필요)
|
|
132
|
+
2. 레이아웃 방향 변경 → @media at breakpoint
|
|
133
|
+
3. 가시성 토글 → @media display toggle
|
|
134
|
+
4. 컴포넌트 교체 → 조건부 렌더링 또는 CSS
|
|
651
135
|
```
|
|
652
136
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
| Wrong | Right |
|
|
656
|
-
|-------|-------|
|
|
657
|
-
| Separate mobile/desktop component files | Single component with responsive CSS |
|
|
658
|
-
| `@media` for font-size/spacing | `clamp()` via fluid tokens |
|
|
659
|
-
| Hardcoded `@media (min-width: 768px)` | Use project breakpoint from Phase 3-3 or Tailwind prefix |
|
|
660
|
-
| Pixel values in responsive styles | Token variables everywhere |
|
|
661
|
-
| Duplicated markup for each viewport | Visibility toggles or conditional sections |
|
|
662
|
-
| `window.innerWidth` checks in JS | CSS-only responsive (`@media`, `clamp()`, `auto-fit`) |
|
|
663
|
-
| Inventing new breakpoints | Use project's existing breakpoint system |
|
|
137
|
+
### Anti-Patterns (금지)
|
|
664
138
|
|
|
665
|
-
|
|
139
|
+
| 잘못됨 | 올바름 |
|
|
140
|
+
|--------|--------|
|
|
141
|
+
| 모바일/데스크탑 별도 컴포넌트 파일 | 단일 컴포넌트 + 반응형 CSS |
|
|
142
|
+
| font-size에 @media | clamp() fluid 토큰 |
|
|
143
|
+
| 하드코딩 `@media (min-width: 768px)` | 프로젝트 브레이크포인트 사용 |
|
|
144
|
+
| px 값 직접 사용 | 토큰 변수 사용 |
|
|
145
|
+
| window.innerWidth JS 체크 | CSS-only 반응형 |
|
|
666
146
|
|
|
667
|
-
##
|
|
147
|
+
## C-7. Correction Notes
|
|
668
148
|
|
|
669
|
-
|
|
149
|
+
코드 생성 후 출력:
|
|
670
150
|
|
|
671
151
|
```markdown
|
|
672
|
-
## Correction Notes
|
|
673
|
-
|
|
674
152
|
### Generation Mode
|
|
675
153
|
- Mode: default / --new / responsive
|
|
676
154
|
- Output directory: {path}
|
|
677
|
-
- Viewports: {list of viewport labels + widths, if responsive}
|
|
678
155
|
|
|
679
156
|
### Files Generated
|
|
680
157
|
| File | Type | Description |
|
|
681
|
-
|------|------|-------------|
|
|
682
|
-
| styles/tokens.css | Global tokens | {N} colors, {N} spacing, {N} typography |
|
|
683
|
-
| styles/global.css | Base styles | Reset + typography + layout |
|
|
684
|
-
| ComponentName/ComponentName.tsx | Component | Root component |
|
|
685
|
-
| ComponentName/ComponentName.module.css | Styles | Component-specific styles |
|
|
686
158
|
|
|
687
159
|
### Responsive Summary (responsive mode only)
|
|
688
160
|
| Token | Mobile | Desktop | Strategy |
|
|
689
|
-
|-------|--------|---------|----------|
|
|
690
|
-
| --figma-text-h1 | 24px | 48px | clamp() |
|
|
691
|
-
| --figma-space-section | 16px | 48px | clamp() |
|
|
692
|
-
| Card grid | 1col | 3col | @media grid |
|
|
693
|
-
| Sidebar | hidden | visible | @media display |
|
|
694
|
-
|
|
695
|
-
- Fluid tokens generated: {N}
|
|
696
|
-
- Layout breakpoints used: {list}
|
|
697
|
-
- Component swaps: {list or "none"}
|
|
698
|
-
|
|
699
|
-
### Layer Issues Found
|
|
700
|
-
- [Layer name] was ambiguous → interpreted as [component] based on image
|
|
701
|
-
- [Layer structure] didn't match visual → used image-based layout
|
|
702
161
|
|
|
703
162
|
### Markup Quality
|
|
704
|
-
- Semantic tags
|
|
705
|
-
- Accessibility: {pass/fail
|
|
706
|
-
|
|
707
|
-
### Recommendations for Figma File
|
|
708
|
-
- Use Auto Layout for [specific frame] to improve extraction accuracy
|
|
709
|
-
- Name layers semantically (e.g., "login-form" not "Frame 47")
|
|
710
|
-
- Use consistent spacing tokens
|
|
711
|
-
- (responsive) Keep same component names across mobile/desktop frames for easier mapping
|
|
712
|
-
```
|
|
713
|
-
|
|
714
|
-
---
|
|
715
|
-
|
|
716
|
-
## Phase 9: Visual Verification Loop
|
|
717
|
-
|
|
718
|
-
코드 생성 완료 후, **Figma 원본 디자인과 생성된 UI를 비교**하여 완성도를 높이는 검증 루프.
|
|
719
|
-
|
|
720
|
-
### 9-1. 스크린샷 비교
|
|
721
|
-
|
|
722
|
-
```
|
|
723
|
-
1. Figma 원본 스크린샷: Phase 0에서 get_screenshot으로 획득한 이미지
|
|
724
|
-
2. 생성된 UI 스크린샷: /vibe.utils --preview 또는 브라우저 스크린샷
|
|
725
|
-
3. 두 이미지를 side-by-side로 비교
|
|
726
|
-
```
|
|
727
|
-
|
|
728
|
-
### 9-2. 차이점 검출
|
|
729
|
-
|
|
730
|
-
이미지를 비교하여 다음 항목 체크:
|
|
731
|
-
|
|
732
|
-
| 검증 항목 | 비교 방법 |
|
|
733
|
-
|----------|----------|
|
|
734
|
-
| **레이아웃** | 요소 배치, 정렬, 간격이 원본과 일치하는가 |
|
|
735
|
-
| **타이포그래피** | 폰트 크기, 굵기, 줄간격이 원본과 일치하는가 |
|
|
736
|
-
| **색상** | 배경, 텍스트, 버튼 색상이 원본과 일치하는가 |
|
|
737
|
-
| **이미지** | 배경 이미지, 에셋이 올바르게 표시되는가 |
|
|
738
|
-
| **간격/여백** | padding, margin, gap이 원본과 일치하는가 |
|
|
739
|
-
| **반응형** | (responsive mode) 모바일/데스크탑 각각 원본과 일치하는가 |
|
|
740
|
-
| **누락 요소** | 원본에 있는데 생성 코드에 없는 요소가 있는가 |
|
|
741
|
-
|
|
742
|
-
### 9-3. Diff Report 출력
|
|
743
|
-
|
|
744
|
-
```markdown
|
|
745
|
-
## Visual Diff Report
|
|
746
|
-
|
|
747
|
-
### Match Score: {N}% (목표: 95%+)
|
|
748
|
-
|
|
749
|
-
### 불일치 항목
|
|
750
|
-
| # | 섹션 | 항목 | Figma 원본 | 생성 코드 | 심각도 |
|
|
751
|
-
|---|------|------|-----------|----------|--------|
|
|
752
|
-
| 1 | Hero | 제목 font-size | 48px | 36px | P1 |
|
|
753
|
-
| 2 | Hero | 배경 이미지 | 표시됨 | 누락 | P1 |
|
|
754
|
-
| 3 | Card | border-radius | 12px | 8px | P2 |
|
|
755
|
-
|
|
756
|
-
### 수정 필요
|
|
757
|
-
- P1: {count}건 (반드시 수정)
|
|
758
|
-
- P2: {count}건 (권장 수정)
|
|
163
|
+
- Semantic tags: {list}
|
|
164
|
+
- Accessibility: {pass/fail}
|
|
759
165
|
```
|
|
760
166
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
```
|
|
764
|
-
P1 불일치가 있으면:
|
|
765
|
-
1. 해당 컴포넌트/스타일 파일을 수정
|
|
766
|
-
2. 다시 스크린샷 비교
|
|
767
|
-
3. P1이 0이 될 때까지 반복 (횟수 제한 없음)
|
|
768
|
-
|
|
769
|
-
수렴 감지:
|
|
770
|
-
- 이전 라운드와 동일한 P1 항목이 반복되면 → 접근 방식 변경
|
|
771
|
-
- 같은 항목이 3회 연속 미해결 → 해당 항목만 사용자에게 확인 요청 후 계속
|
|
772
|
-
```
|
|
167
|
+
## C-8. Refine Mode (`--refine`)
|
|
773
168
|
|
|
774
|
-
|
|
169
|
+
이전 생성 코드의 완성도가 부족할 때 사용. **새로 만들지 않고 기존 코드를 수정만.**
|
|
775
170
|
|
|
776
171
|
```
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
172
|
+
1. URL 재입력 (또는 "이전과 동일")
|
|
173
|
+
2. 기존 코드 스캔 (pages/, components/ 내 피처 폴더)
|
|
174
|
+
3. Figma 원본 재추출 (get_design_context + get_screenshot)
|
|
175
|
+
4. Side-by-side 비교 → Diff 기반 최소 수정
|
|
176
|
+
5. 검증 루프 (vibe-figma-rules R-6, P1=0 될 때까지)
|
|
781
177
|
```
|
|
782
178
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
## Tool Usage Rules
|
|
179
|
+
### Refine 수정 범위
|
|
786
180
|
|
|
787
|
-
|
|
|
788
|
-
|
|
789
|
-
|
|
|
790
|
-
|
|
|
791
|
-
|
|
|
792
|
-
| Read | Project config, existing components, design-context.json |
|
|
793
|
-
| Glob | Find existing components, theme files, design tokens |
|
|
794
|
-
| Grep | Search for existing color/spacing/typography definitions |
|
|
795
|
-
| Write | Create new component files and style files |
|
|
796
|
-
| Edit | Update existing theme/token files to add new tokens (default mode) |
|
|
181
|
+
| 수정함 | 수정 안 함 |
|
|
182
|
+
|--------|-----------|
|
|
183
|
+
| 누락 에셋, 레이아웃/타이포/색상 불일치 | 파일 구조 변경 |
|
|
184
|
+
| 누락 컴포넌트, 인터랙션 누락 | 컴포넌트 분리/통합 |
|
|
185
|
+
| 반응형 누락, 이미지 경로 | 토큰 파일 재생성 |
|
|
797
186
|
|
|
798
187
|
## Important
|
|
799
188
|
|
|
800
|
-
- **Never guess colors** — extract from layers.json or image analysis
|
|
801
|
-
- **Never invent spacing** — use extracted values mapped to token scale
|
|
802
|
-
- **Never hardcode values** — all visual properties reference token variables
|
|
803
|
-
- **Preserve existing patterns** — match the project's existing component style (default mode)
|
|
804
|
-
- **Image is truth** — when layer structure is confusing, trust what the image shows
|
|
805
|
-
- **Ask before overwriting** — if a component file already exists, ask the user first
|
|
806
|
-
- **No console.log** — never include debug logging in generated code
|
|
807
|
-
- **No div soup** — every element uses the correct semantic tag
|
|
808
|
-
- **Component size limit** — split components exceeding 50 lines
|
|
809
|
-
- **Style separation** — global tokens file + per-component style files, always
|
|
810
|
-
|
|
811
|
-
---
|
|
812
|
-
|
|
813
|
-
## Refine Mode (`--refine`)
|
|
814
|
-
|
|
815
|
-
이전 `/vibe.figma` 실행으로 생성된 코드의 완성도가 부족할 때 사용.
|
|
816
|
-
**새로 만들지 않고, 기존 코드를 Figma 원본과 재비교하여 수정만 한다.**
|
|
817
|
-
|
|
818
|
-
### Refine 플로우
|
|
819
|
-
|
|
820
|
-
```
|
|
821
|
-
Step 1: URL 재입력
|
|
822
|
-
→ 이전과 동일한 방식으로 스토리보드/디자인 URL 입력
|
|
823
|
-
→ 또는 "이전과 동일" 입력 시 이전 URL 재사용
|
|
824
|
-
|
|
825
|
-
Step 2: 기존 코드 스캔
|
|
826
|
-
→ 프로젝트에서 이전에 생성된 파일 탐색 (pages/, components/ 내 피처 폴더)
|
|
827
|
-
→ 생성된 컴포넌트 목록 파악
|
|
828
|
-
|
|
829
|
-
Step 3: Figma 원본 재추출
|
|
830
|
-
→ get_design_context + get_screenshot으로 최신 디자인 데이터 획득
|
|
831
|
-
|
|
832
|
-
Step 4: Side-by-side 비교
|
|
833
|
-
→ Figma 스크린샷 vs 기존 코드의 렌더링 결과
|
|
834
|
-
→ Phase 9 검증 루프와 동일한 비교 항목:
|
|
835
|
-
레이아웃, 타이포, 색상, 이미지, 간격, 누락 요소
|
|
836
|
-
|
|
837
|
-
Step 5: Diff 기반 수정
|
|
838
|
-
→ 변경이 필요한 파일만 Edit
|
|
839
|
-
→ 새 파일 생성 최소화
|
|
840
|
-
→ 수정 사유를 주석으로 남기지 않음 (코드만 수정)
|
|
841
|
-
|
|
842
|
-
Step 6: 재검증
|
|
843
|
-
→ Phase 9 검증 루프 실행 (P1=0 될 때까지)
|
|
844
|
-
```
|
|
845
|
-
|
|
846
|
-
### Refine에서 수정하는 항목
|
|
847
|
-
|
|
848
|
-
| 카테고리 | 수정 내용 |
|
|
849
|
-
|----------|----------|
|
|
850
|
-
| **누락 에셋** | 다운로드 안 된 이미지 재다운로드 + 경로 설정 |
|
|
851
|
-
| **레이아웃 불일치** | flex/grid 방향, 정렬, 간격 보정 |
|
|
852
|
-
| **타이포 불일치** | font-size, weight, line-height, letter-spacing 보정 |
|
|
853
|
-
| **색상 불일치** | 배경, 텍스트, 보더 색상 보정 |
|
|
854
|
-
| **누락 컴포넌트** | Figma에 있는데 코드에 없는 섹션 추가 |
|
|
855
|
-
| **인터랙션 누락** | 호버/포커스/액티브 상태 추가 |
|
|
856
|
-
| **반응형 누락** | 브레이크포인트 대응 미흡한 부분 보정 |
|
|
857
|
-
| **이미지 경로** | 임시 URL → 로컬 경로 교체, 누락 이미지 다운로드 |
|
|
858
|
-
|
|
859
|
-
### Refine에서 하지 않는 것
|
|
860
|
-
|
|
861
189
|
```
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
190
|
+
- Never guess colors — 추출 값만 사용
|
|
191
|
+
- Never invent spacing — 토큰 스케일에 매핑
|
|
192
|
+
- Never hardcode values — 토큰 변수 참조
|
|
193
|
+
- Image is truth — 레이어 구조 혼란 시 이미지 우선
|
|
194
|
+
- No console.log, No div soup
|
|
195
|
+
- Component 50줄 초과 시 분리
|
|
866
196
|
```
|