@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
|
@@ -8,576 +8,207 @@ tier: standard
|
|
|
8
8
|
# Skill: vibe-figma-style — 스타일 아키텍처
|
|
9
9
|
|
|
10
10
|
토큰 포맷, SCSS 규칙, 클래스 네이밍, 반응형 토큰 계산.
|
|
11
|
+
디렉토리 감지는 **vibe-figma-rules R-2.2** 참조.
|
|
11
12
|
|
|
12
13
|
---
|
|
13
14
|
|
|
14
|
-
##
|
|
15
|
+
## S-1. 토큰 Resolution Priority
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
**기본 모드 (프로젝트 통합):**
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
1. **MASTER.md** (`.claude/vibe/design-system/{project}/MASTER.md`) — 최우선
|
|
20
|
+
2. **design-context.json** (`.claude/vibe/design-context.json`) — 보조
|
|
21
|
+
3. **프로젝트 테마 파일** (tailwind.config, CSS variables 등) — 폴백
|
|
22
|
+
4. **새 figma-tokens 생성** — 마지막 수단
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
2. **design-context.json tokens** — if `detectedStack.fonts`, `aesthetic.colorMood` exist, align with these
|
|
22
|
-
3. **New figma-tokens** — only for values that have no existing match
|
|
24
|
+
**--new 모드:** 자체 완결 토큰 파일 생성 (MASTER.md 의존 없음).
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
#### Output Structure (both modes — project structure 준수)
|
|
27
|
-
|
|
28
|
-
프로젝트의 기존 디렉토리 구조를 감지하여 올바른 위치에 파일 생성.
|
|
29
|
-
|
|
30
|
-
**Step 1: 디렉토리 감지**
|
|
31
|
-
|
|
32
|
-
```
|
|
33
|
-
페이지 디렉토리:
|
|
34
|
-
Next.js → pages/ or app/
|
|
35
|
-
Nuxt → pages/
|
|
36
|
-
React → src/pages/ or src/views/
|
|
37
|
-
Vue → src/views/
|
|
38
|
-
|
|
39
|
-
컴포넌트 디렉토리:
|
|
40
|
-
Next.js → components/ or src/components/
|
|
41
|
-
Nuxt → components/
|
|
42
|
-
React → src/components/
|
|
43
|
-
Vue → src/components/
|
|
44
|
-
|
|
45
|
-
스타일 디렉토리:
|
|
46
|
-
SCSS → assets/scss/ or src/scss/ or src/styles/
|
|
47
|
-
CSS → src/styles/ or styles/
|
|
48
|
-
Tailwind → tailwind.config.* (extend)
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
**Step 2: 스타일 구조 — 모드별 분리**
|
|
52
|
-
|
|
53
|
-
Figma 파일명에서 피처명 자동 추출 → kebab-case 변환.
|
|
54
|
-
|
|
55
|
-
### 기본 모드 (기존 프로젝트에 추가)
|
|
56
|
-
|
|
57
|
-
```
|
|
58
|
-
⚠️ 기존 스타일 구조를 먼저 분석하고 그대로 따른다.
|
|
59
|
-
1. Glob/Grep으로 기존 스타일 파일 패턴 탐색:
|
|
60
|
-
- 디렉토리 구조 (styles/, scss/, css/)
|
|
61
|
-
- 파일 네이밍 (BEM, camelCase, kebab-case)
|
|
62
|
-
- import 방식 (@use, @import, CSS Modules)
|
|
63
|
-
- 기존 변수/mixin 파일 위치
|
|
64
|
-
2. 기존 토큰/변수/mixin을 최대한 재사용
|
|
65
|
-
3. 새 컴포넌트 스타일만 기존 패턴대로 추가
|
|
66
|
-
4. 기존 스타일 파일을 수정하지 않음 (사이드이펙트 방지)
|
|
67
|
-
|
|
68
|
-
예: 기존 프로젝트가 assets/scss/ 구조를 쓰면:
|
|
69
|
-
assets/scss/_variables.scss ← 기존 (수정 안 함)
|
|
70
|
-
assets/scss/_mixins.scss ← 기존 (재사용)
|
|
71
|
-
assets/scss/pages/
|
|
72
|
-
_winter-pcbang.scss ← 새 피처 스타일 (기존 패턴 따름)
|
|
73
|
-
|
|
74
|
-
예: 기존 프로젝트가 CSS Modules를 쓰면:
|
|
75
|
-
components/winter-pcbang/
|
|
76
|
-
HeroSection.module.scss ← 기존 패턴대로
|
|
77
|
-
DailyCheckIn.module.scss
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### --new 모드 (새 피처, 자체 완결)
|
|
81
|
-
|
|
82
|
-
```
|
|
83
|
-
피처 전용 스타일 폴더를 생성하고, 글로벌 + 컴포넌트별 2-tier 구조:
|
|
84
|
-
|
|
85
|
-
styles/{feature-name}/
|
|
86
|
-
index.scss ← 진입점
|
|
87
|
-
|
|
88
|
-
// ── foundation (토큰, mixin, 기반) ──
|
|
89
|
-
_tokens.scss ← 피처 전용 토큰
|
|
90
|
-
_mixins.scss ← 피처 전용 mixin
|
|
91
|
-
_base.scss ← 피처 공통 reset/폰트
|
|
92
|
-
|
|
93
|
-
// ── layout (섹션 배치, 구조) ──
|
|
94
|
-
layout/
|
|
95
|
-
_page.scss ← 페이지 전체 레이아웃
|
|
96
|
-
_hero.scss ← HeroSection 배치/구조
|
|
97
|
-
_daily-checkin.scss ← DailyCheckInSection 배치/구조
|
|
98
|
-
_play-time-mission.scss ← PlayTimeMissionSection 배치/구조
|
|
99
|
-
_token-exchange.scss ← TokenExchangeSection 배치/구조
|
|
100
|
-
_token-raffle.scss ← TokenRaffleSection 배치/구조
|
|
101
|
-
_caution.scss ← CautionSection 배치/구조
|
|
102
|
-
|
|
103
|
-
// ── components (재사용 UI 디자인) ──
|
|
104
|
-
components/
|
|
105
|
-
_card.scss ← 카드 디자인 (보상, 상품, 아이템)
|
|
106
|
-
_button.scss ← 버튼 디자인 (CTA, 출석, 교환)
|
|
107
|
-
_badge.scss ← 배지 디자인 (완료, 진행중, 잠금)
|
|
108
|
-
_progress.scss ← 프로그레스 바, 게이지
|
|
109
|
-
_popups.scss ← 팝업/모달 디자인
|
|
110
|
-
_tooltip.scss ← 툴팁, 안내 말풍선
|
|
111
|
-
_form.scss ← 입력 폼 (이메일, 검색)
|
|
112
|
-
|
|
113
|
-
index.scss 내용:
|
|
114
|
-
// foundation
|
|
115
|
-
@use 'tokens';
|
|
116
|
-
@use 'mixins';
|
|
117
|
-
@use 'base';
|
|
118
|
-
|
|
119
|
-
// layout
|
|
120
|
-
@use 'layout/page';
|
|
121
|
-
@use 'layout/hero';
|
|
122
|
-
@use 'layout/daily-checkin';
|
|
123
|
-
@use 'layout/play-time-mission';
|
|
124
|
-
@use 'layout/token-exchange';
|
|
125
|
-
@use 'layout/token-raffle';
|
|
126
|
-
@use 'layout/caution';
|
|
127
|
-
|
|
128
|
-
// components
|
|
129
|
-
@use 'components/card';
|
|
130
|
-
@use 'components/button';
|
|
131
|
-
@use 'components/badge';
|
|
132
|
-
@use 'components/progress';
|
|
133
|
-
@use 'components/popups';
|
|
134
|
-
@use 'components/tooltip';
|
|
135
|
-
@use 'components/form';
|
|
136
|
-
|
|
137
|
-
layout vs components 구분 기준:
|
|
138
|
-
layout/ → 섹션의 위치, 크기, 배치, 간격, 배경
|
|
139
|
-
(position, display, flex/grid, padding, margin, background-image)
|
|
140
|
-
components/ → UI 요소의 모양, 색상, 타이포, 인터랙션 상태
|
|
141
|
-
(color, font, border, border-radius, shadow, hover, focus)
|
|
142
|
-
|
|
143
|
-
각 컴포넌트 스타일 파일 규칙:
|
|
144
|
-
- @use '../tokens' as t; 로 토큰 참조
|
|
145
|
-
- @use '../mixins' as m; 로 mixin 참조
|
|
146
|
-
- 해당 섹션의 클래스만 정의 (다른 섹션 스타일 금지)
|
|
147
|
-
- 역할 기반 클래스 네이밍 (Phase 4-4 규칙 적용)
|
|
148
|
-
- 배경 이미지는 별도 클래스 (Multi-Layer 패턴)
|
|
149
|
-
|
|
150
|
-
_tokens.scss 내용:
|
|
151
|
-
$feature-primary: #xxx;
|
|
152
|
-
$feature-text: #xxx;
|
|
153
|
-
$feature-bp: 1024px;
|
|
154
|
-
@function fluid($min, $max) { ... }
|
|
155
|
-
// 자체 완결 — 외부 의존 없음
|
|
156
|
-
|
|
157
|
-
이 폴더째 다른 프로젝트에 복사 가능.
|
|
158
|
-
```
|
|
26
|
+
### Mapping Rules
|
|
159
27
|
|
|
160
|
-
|
|
28
|
+
- Figma color ≈ 기존 토큰 (ΔE < 5) → 기존 토큰명 사용
|
|
29
|
+
- Figma spacing ≈ 기존 토큰 (±2px) → 기존 토큰명 사용
|
|
30
|
+
- 매칭 없는 값 → `figma-tokens` 보충 토큰으로 추가
|
|
161
31
|
|
|
162
|
-
|
|
163
|
-
두 종류의 스타일 파일이 필요:
|
|
164
|
-
|
|
165
|
-
1. 섹션 스타일 — 페이지의 각 영역
|
|
166
|
-
| 컴포넌트 | --new 모드 | 기본 모드 |
|
|
167
|
-
|---------|-----------|---------|
|
|
168
|
-
| HeroSection.vue | _hero.scss | 기존 패턴 따름 |
|
|
169
|
-
| DailyCheckIn.vue | _daily-checkin.scss | 기존 패턴 따름 |
|
|
170
|
-
| CautionSection.vue | _caution.scss | 기존 패턴 따름 |
|
|
171
|
-
|
|
172
|
-
2. 재사용 컴포넌트 스타일 — 여러 섹션에서 공통 사용
|
|
173
|
-
| 패턴 | --new 모드 | 용도 |
|
|
174
|
-
|------|-----------|------|
|
|
175
|
-
| 카드 (보상, 상품, 아이템) | _card.scss | 그리드 아이템, 리스트 아이템 |
|
|
176
|
-
| 버튼 (CTA, 출석, 교환) | _button.scss | 액션 트리거 |
|
|
177
|
-
| 배지 (상태 표시) | _badge.scss | 완료/진행중/잠금 표시 |
|
|
178
|
-
| 프로그레스 | _progress.scss | 게이지, 달성도 |
|
|
179
|
-
| 팝업/모달 | _popups.scss | 확인, 상세, 입력 |
|
|
180
|
-
|
|
181
|
-
재사용 컴포넌트 감지 방법:
|
|
182
|
-
→ 디자인 프레임에서 2회 이상 반복되는 시각 패턴
|
|
183
|
-
→ 같은 구조 + 다른 데이터 → 하나의 스타일 파일 + variant
|
|
184
|
-
|
|
185
|
-
vibe-figma-frame에서 프레임별 추출 시:
|
|
186
|
-
→ 섹션 스타일 파일에 해당 섹션 스타일 작성
|
|
187
|
-
→ 반복 패턴 발견 시 재사용 컴포넌트 스타일 파일로 분리
|
|
188
|
-
→ 토큰에 새 값 추가 (중복 시 기존 토큰 재사용)
|
|
189
|
-
```
|
|
32
|
+
---
|
|
190
33
|
|
|
191
|
-
|
|
34
|
+
## S-2. 토큰 파일 포맷
|
|
192
35
|
|
|
193
|
-
|
|
36
|
+
### CSS Custom Properties (기본)
|
|
194
37
|
|
|
195
38
|
```css
|
|
196
|
-
/* figma-tokens.css — Auto-generated from Figma. Do not edit manually. */
|
|
197
|
-
/* Source: https://www.figma.com/design/{fileKey} */
|
|
198
|
-
|
|
199
39
|
:root {
|
|
200
40
|
/* Colors */
|
|
201
41
|
--figma-primary: #3B82F6;
|
|
202
|
-
--figma-primary-hover: #2563EB;
|
|
203
42
|
--figma-surface: #FFFFFF;
|
|
204
|
-
--figma-surface-secondary: #F9FAFB;
|
|
205
43
|
--figma-text-primary: #111827;
|
|
206
|
-
--figma-text-secondary: #6B7280;
|
|
207
44
|
--figma-border: #E5E7EB;
|
|
208
45
|
|
|
209
46
|
/* Typography */
|
|
210
47
|
--figma-font-family: 'Inter', system-ui, sans-serif;
|
|
211
|
-
--figma-text-
|
|
212
|
-
--figma-text-
|
|
213
|
-
--figma-text-
|
|
214
|
-
--figma-text-lg: 1.125rem; /* 18px */
|
|
215
|
-
--figma-text-xl: 1.25rem; /* 20px */
|
|
216
|
-
--figma-leading-tight: 1.25;
|
|
217
|
-
--figma-leading-normal: 1.5;
|
|
48
|
+
--figma-text-sm: 0.875rem;
|
|
49
|
+
--figma-text-base: 1rem;
|
|
50
|
+
--figma-text-xl: 1.25rem;
|
|
218
51
|
|
|
219
52
|
/* Spacing */
|
|
220
|
-
--figma-space-
|
|
221
|
-
--figma-space-
|
|
222
|
-
--figma-space-
|
|
223
|
-
--figma-space-4: 1rem; /* 16px */
|
|
224
|
-
--figma-space-6: 1.5rem; /* 24px */
|
|
225
|
-
--figma-space-8: 2rem; /* 32px */
|
|
226
|
-
|
|
227
|
-
/* Shadows */
|
|
228
|
-
--figma-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
229
|
-
--figma-shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
|
|
53
|
+
--figma-space-2: 0.5rem;
|
|
54
|
+
--figma-space-4: 1rem;
|
|
55
|
+
--figma-space-8: 2rem;
|
|
230
56
|
|
|
231
|
-
/*
|
|
232
|
-
--figma-
|
|
233
|
-
--figma-radius-md: 0.5rem;
|
|
234
|
-
--figma-radius-lg: 0.75rem; /* 12px */
|
|
235
|
-
--figma-radius-full: 9999px;
|
|
57
|
+
/* Shadows, Radius */
|
|
58
|
+
--figma-shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
|
|
59
|
+
--figma-radius-md: 0.5rem;
|
|
236
60
|
}
|
|
237
61
|
```
|
|
238
62
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
```js
|
|
242
|
-
// figma.config.ts — merge into tailwind.config.ts theme.extend
|
|
243
|
-
export const figmaTokens = {
|
|
244
|
-
colors: {
|
|
245
|
-
figma: {
|
|
246
|
-
primary: '#3B82F6',
|
|
247
|
-
'primary-hover': '#2563EB',
|
|
248
|
-
// ...
|
|
249
|
-
},
|
|
250
|
-
},
|
|
251
|
-
spacing: { /* ... */ },
|
|
252
|
-
borderRadius: { /* ... */ },
|
|
253
|
-
};
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
**SCSS (if `*.scss` or `sass` detected):**
|
|
63
|
+
### SCSS (SCSS 감지 시)
|
|
257
64
|
|
|
258
65
|
```scss
|
|
259
|
-
// _figma-tokens.scss
|
|
66
|
+
// _figma-tokens.scss
|
|
67
|
+
@use 'sass:math';
|
|
260
68
|
|
|
261
|
-
// ── Variables ──
|
|
262
69
|
$figma-primary: #3B82F6;
|
|
263
|
-
$figma-primary-hover: #2563EB;
|
|
264
|
-
$figma-surface: #FFFFFF;
|
|
265
70
|
$figma-text-primary: #111827;
|
|
266
|
-
$figma-text-secondary: #6B7280;
|
|
267
|
-
$figma-border: #E5E7EB;
|
|
268
|
-
|
|
269
71
|
$figma-font-family: 'Inter', system-ui, sans-serif;
|
|
270
|
-
$figma-text-xs: 0.75rem;
|
|
271
|
-
$figma-text-sm: 0.875rem;
|
|
272
72
|
$figma-text-base: 1rem;
|
|
273
|
-
$figma-text-lg: 1.125rem;
|
|
274
|
-
$figma-text-xl: 1.25rem;
|
|
275
|
-
|
|
276
|
-
$figma-space-1: 0.25rem;
|
|
277
|
-
$figma-space-2: 0.5rem;
|
|
278
73
|
$figma-space-4: 1rem;
|
|
279
|
-
$figma-space-6: 1.5rem;
|
|
280
|
-
$figma-space-8: 2rem;
|
|
281
|
-
|
|
282
|
-
$figma-radius-sm: 0.25rem;
|
|
283
74
|
$figma-radius-md: 0.5rem;
|
|
284
|
-
$figma-radius-lg: 0.75rem;
|
|
285
|
-
|
|
286
|
-
$figma-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
287
|
-
$figma-shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
|
|
288
75
|
|
|
289
|
-
//
|
|
76
|
+
// Breakpoints
|
|
290
77
|
$figma-bp: 1024px;
|
|
291
78
|
$figma-bp-mobile-min: 360px;
|
|
292
79
|
$figma-bp-pc-target: 1920px;
|
|
293
80
|
|
|
294
|
-
//
|
|
295
|
-
@mixin figma-pc {
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
@mixin figma-mobile-only {
|
|
300
|
-
@media (max-width: $figma-bp - 1px) { @content; }
|
|
301
|
-
}
|
|
81
|
+
// Mixins
|
|
82
|
+
@mixin figma-pc { @media (min-width: $figma-bp) { @content; } }
|
|
83
|
+
@mixin figma-mobile-only { @media (max-width: $figma-bp - 1px) { @content; } }
|
|
302
84
|
|
|
303
|
-
//
|
|
85
|
+
// Fluid function (clamp 자동화)
|
|
304
86
|
@function figma-fluid($mobile, $desktop, $min-vw: $figma-bp-mobile-min, $max-vw: $figma-bp-pc-target) {
|
|
305
|
-
$slope: ($desktop - $mobile
|
|
87
|
+
$slope: math.div($desktop - $mobile, $max-vw - $min-vw);
|
|
306
88
|
$intercept: $mobile - $slope * $min-vw;
|
|
307
89
|
@return clamp(#{$mobile}, #{$intercept} + #{$slope * 100}vw, #{$desktop});
|
|
308
90
|
}
|
|
309
|
-
|
|
310
|
-
// Usage: font-size: figma-fluid(1rem, 2rem);
|
|
311
91
|
```
|
|
312
92
|
|
|
313
|
-
**SCSS 사용 시
|
|
314
|
-
-
|
|
315
|
-
- `@
|
|
316
|
-
- `figma-fluid()`
|
|
317
|
-
- 파일명: `_figma-tokens.scss` (partial, `_` prefix)
|
|
318
|
-
- `@use 'figma-tokens' as figma;` 로 네임스페이스 import
|
|
319
|
-
|
|
320
|
-
### 4-3. Responsive Token Format (responsive mode only)
|
|
93
|
+
**SCSS 사용 시 필수 규칙:**
|
|
94
|
+
- `@use 'figma-tokens' as figma;` 네임스페이스 import
|
|
95
|
+
- `@include figma.figma-pc { }` — `@media` 직접 사용 금지
|
|
96
|
+
- `figma-fluid($mobile, $desktop)` — 수동 clamp() 금지
|
|
321
97
|
|
|
322
|
-
|
|
323
|
-
Tokens that are **identical** across viewports remain static.
|
|
324
|
-
|
|
325
|
-
**clamp() range uses breakpoints from Phase 3-3:**
|
|
98
|
+
### Tailwind (Tailwind 감지 시)
|
|
326
99
|
|
|
100
|
+
```js
|
|
101
|
+
export const figmaTokens = {
|
|
102
|
+
colors: { figma: { primary: '#3B82F6' } },
|
|
103
|
+
spacing: { /* ... */ },
|
|
104
|
+
borderRadius: { /* ... */ },
|
|
105
|
+
};
|
|
327
106
|
```
|
|
328
|
-
minVw = mobileMinimum (default: 360px)
|
|
329
|
-
maxVw = pcTarget (default: 1920px)
|
|
330
|
-
breakpoint = breakpoint (default: 1024px) ← used for @media
|
|
331
107
|
|
|
332
|
-
|
|
333
|
-
PC Figma value × (pcTarget / designPc) = target PC value
|
|
334
|
-
Mobile Figma value × (mobilePortrait / designMobile) = target mobile value
|
|
335
|
-
```
|
|
108
|
+
---
|
|
336
109
|
|
|
337
|
-
|
|
110
|
+
## S-3. 반응형 토큰 (responsive mode)
|
|
338
111
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
/* clamp range: {mobileMinimum}px → {pcTarget}px */
|
|
342
|
-
/* Breakpoint: {breakpoint}px (PC↔Mobile) */
|
|
343
|
-
/* Design scale: PC {designPc}→{pcTarget}, Mobile {designMobile}→{mobilePortrait} */
|
|
112
|
+
뷰포트 간 값이 다른 토큰은 `clamp()`로 fluid 스케일링.
|
|
113
|
+
동일한 값은 static 유지. 계산 공식은 **vibe-figma-rules R-3** 참조.
|
|
344
114
|
|
|
115
|
+
```css
|
|
345
116
|
:root {
|
|
346
|
-
/*
|
|
117
|
+
/* Shared (뷰포트 간 동일) */
|
|
347
118
|
--figma-primary: #3B82F6;
|
|
348
|
-
--figma-font-family: 'Inter', system-ui, sans-serif;
|
|
349
119
|
--figma-radius-md: 0.5rem;
|
|
350
|
-
--figma-shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
|
|
351
|
-
|
|
352
|
-
/* === Fluid Typography (scales with viewport) === */
|
|
353
|
-
/* Figma PC 96px → target 36px, Figma Mobile 48px → target 32px */
|
|
354
|
-
--figma-text-h1: clamp(2rem, {intercept}rem + {slope}vw, 2.25rem);
|
|
355
|
-
--figma-text-body: clamp(0.875rem, {intercept}rem + {slope}vw, 1rem);
|
|
356
120
|
|
|
357
|
-
/*
|
|
358
|
-
--figma-
|
|
359
|
-
--figma-space-
|
|
360
|
-
|
|
361
|
-
/* === Breakpoint (from config, user-customizable) === */
|
|
362
|
-
--figma-bp: 1024px;
|
|
121
|
+
/* Fluid (뷰포트 간 다름) */
|
|
122
|
+
--figma-text-h1: clamp(2rem, 1.423rem + 2.564vw, 4.5rem);
|
|
123
|
+
--figma-space-section: clamp(1rem, 0.248rem + 3.286vw, 3rem);
|
|
363
124
|
}
|
|
364
125
|
```
|
|
365
126
|
|
|
366
|
-
**clamp() calculation formula:**
|
|
367
|
-
|
|
368
|
-
```
|
|
369
|
-
Step 1: Scale Figma values to target viewport
|
|
370
|
-
targetMobile = figmaMobileValue × (mobilePortrait / designMobile)
|
|
371
|
-
targetPc = figmaPcValue × (pcTarget / designPc)
|
|
372
|
-
|
|
373
|
-
Example (defaults): Figma PC h1=96px, Figma Mobile h1=48px
|
|
374
|
-
targetPc = 96 × (1920 / 2560) = 72px
|
|
375
|
-
targetMobile = 48 × (480 / 720) = 32px
|
|
376
|
-
|
|
377
|
-
Step 2: Calculate clamp()
|
|
378
|
-
minVw = mobileMinimum (360)
|
|
379
|
-
maxVw = pcTarget (1920)
|
|
380
|
-
min = targetMobile, max = targetPc
|
|
381
|
-
|
|
382
|
-
slope = (max - min) / (maxVw - minVw)
|
|
383
|
-
intercept = min - slope * minVw
|
|
384
|
-
→ clamp({min/16}rem, {intercept/16}rem + {slope*100}vw, {max/16}rem)
|
|
385
|
-
|
|
386
|
-
Example:
|
|
387
|
-
slope = (72 - 32) / (1920 - 360) = 0.02564
|
|
388
|
-
intercept = 32 - 0.02564 × 360 = 22.77
|
|
389
|
-
→ clamp(2rem, 1.423rem + 2.564vw, 4.5rem)
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
**Tailwind (responsive — if Tailwind detected):**
|
|
393
|
-
|
|
394
|
-
Use Tailwind's responsive prefixes instead of clamp() for layout, clamp() for typography/spacing:
|
|
395
|
-
|
|
396
|
-
```js
|
|
397
|
-
export const figmaTokens = {
|
|
398
|
-
fontSize: {
|
|
399
|
-
'figma-h1': ['clamp(1.5rem, 1.076rem + 1.878vw, 3rem)', { lineHeight: '1.2' }],
|
|
400
|
-
'figma-body': ['clamp(0.875rem, 0.828rem + 0.188vw, 1rem)', { lineHeight: '1.5' }],
|
|
401
|
-
},
|
|
402
|
-
spacing: {
|
|
403
|
-
'figma-section': 'clamp(1rem, 0.248rem + 3.286vw, 3rem)',
|
|
404
|
-
},
|
|
405
|
-
};
|
|
406
|
-
```
|
|
407
|
-
|
|
408
|
-
**SCSS (responsive):**
|
|
409
|
-
|
|
410
127
|
```scss
|
|
411
|
-
//
|
|
412
|
-
@use 'sass:math';
|
|
413
|
-
|
|
414
|
-
$figma-bp: 1024px;
|
|
415
|
-
$figma-bp-mobile-min: 360px;
|
|
416
|
-
$figma-bp-pc-target: 1920px;
|
|
417
|
-
|
|
418
|
-
@function figma-fluid($mobile, $desktop, $min-vw: $figma-bp-mobile-min, $max-vw: $figma-bp-pc-target) {
|
|
419
|
-
$slope: math.div($desktop - $mobile, $max-vw - $min-vw);
|
|
420
|
-
$intercept: $mobile - $slope * $min-vw;
|
|
421
|
-
@return clamp(#{$mobile}, #{$intercept} + #{$slope * 100}vw, #{$desktop});
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
@mixin figma-pc { @media (min-width: $figma-bp) { @content; } }
|
|
425
|
-
|
|
426
|
-
// Token 사용
|
|
128
|
+
// SCSS — figma-fluid() 사용
|
|
427
129
|
$figma-text-h1: figma-fluid(2rem, 4.5rem);
|
|
428
|
-
$figma-text-body: figma-fluid(0.875rem, 1rem);
|
|
429
130
|
$figma-space-section: figma-fluid(1rem, 3rem);
|
|
430
131
|
```
|
|
431
132
|
|
|
432
|
-
|
|
433
|
-
// Component.module.scss — 사용 예시
|
|
434
|
-
@use 'figma-tokens' as figma;
|
|
435
|
-
|
|
436
|
-
.heroSection {
|
|
437
|
-
padding: figma.$figma-space-section;
|
|
438
|
-
}
|
|
133
|
+
---
|
|
439
134
|
|
|
440
|
-
.
|
|
441
|
-
font-size: figma.$figma-text-h1;
|
|
442
|
-
}
|
|
135
|
+
## S-4. 스타일 파일 구조
|
|
443
136
|
|
|
444
|
-
|
|
445
|
-
display: grid;
|
|
446
|
-
grid-template-columns: 1fr;
|
|
137
|
+
### --new 모드 (자체 완결)
|
|
447
138
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
139
|
+
```
|
|
140
|
+
styles/{feature-name}/
|
|
141
|
+
index.scss ← 진입점
|
|
142
|
+
_tokens.scss ← 피처 전용 토큰
|
|
143
|
+
_mixins.scss ← 피처 전용 mixin
|
|
144
|
+
_base.scss ← 피처 공통 reset/폰트
|
|
145
|
+
layout/ ← 섹션 배치/구조 (position, flex/grid, padding, background)
|
|
146
|
+
_page.scss
|
|
147
|
+
_hero.scss
|
|
148
|
+
_feature-a.scss
|
|
149
|
+
components/ ← UI 요소 모양 (color, font, border, shadow, hover)
|
|
150
|
+
_card.scss
|
|
151
|
+
_button.scss
|
|
152
|
+
_popups.scss
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**layout vs components 구분:**
|
|
156
|
+
- `layout/` → 섹션의 위치, 크기, 배치, 간격, 배경
|
|
157
|
+
- `components/` → UI 요소의 모양, 색상, 타이포, 인터랙션 상태
|
|
158
|
+
|
|
159
|
+
### 기본 모드 (기존 프로젝트 추가)
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
기존 스타일 구조를 분석하고 그대로 따른다:
|
|
163
|
+
1. Glob/Grep으로 기존 패턴 탐색 (디렉토리, 네이밍, import 방식)
|
|
164
|
+
2. 기존 토큰/변수/mixin 재사용
|
|
165
|
+
3. 새 컴포넌트 스타일만 기존 패턴대로 추가
|
|
166
|
+
4. 기존 스타일 파일은 수정하지 않음
|
|
452
167
|
```
|
|
453
168
|
|
|
454
|
-
|
|
169
|
+
---
|
|
455
170
|
|
|
456
|
-
|
|
171
|
+
## S-5. 클래스 네이밍 규칙
|
|
457
172
|
|
|
458
|
-
|
|
173
|
+
클래스 이름은 **역할(role)**을 드러내야 하며, 구조나 스타일 속성을 이름에 넣지 않는다.
|
|
459
174
|
|
|
460
175
|
| 원칙 | 좋은 예 | 나쁜 예 |
|
|
461
176
|
|------|--------|--------|
|
|
462
|
-
|
|
|
463
|
-
|
|
|
464
|
-
|
|
|
465
|
-
|
|
|
466
|
-
| **축약 금지** | `.navigationMenu`, `.backgroundImage` | `.navMnu`, `.bgImg` |
|
|
177
|
+
| 역할 기반 | `.heroSection`, `.productCard` | `.section1`, `.card` |
|
|
178
|
+
| 용도 명시 | `.heroBg`, `.cardThumbnail` | `.bg`, `.img` |
|
|
179
|
+
| 관계 표현 | `.heroTitle`, `.heroDescription` | `.title`, `.text` |
|
|
180
|
+
| 축약 금지 | `.navigationMenu` | `.navMnu` |
|
|
467
181
|
|
|
468
|
-
|
|
182
|
+
### 구체적 규칙
|
|
469
183
|
|
|
470
184
|
```
|
|
471
|
-
1. 컴포넌트 루트: 섹션/컴포넌트 이름
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
.heroTitle, .heroDescription, .heroCta
|
|
476
|
-
.loginFormInput, .loginFormSubmit
|
|
477
|
-
|
|
478
|
-
3. 이미지 클래스: 반드시 용도를 명시
|
|
479
|
-
.heroBg ← 히어로 배경 이미지
|
|
480
|
-
.heroBgOverlay ← 배경 위 오버레이
|
|
481
|
-
.productPhoto ← 상품 사진
|
|
482
|
-
.brandLogo ← 브랜드 로고
|
|
483
|
-
|
|
484
|
-
4. 상태 변형: variant/state 접미사
|
|
485
|
-
.buttonPrimary, .buttonDisabled
|
|
486
|
-
.cardHighlight, .cardCompact
|
|
185
|
+
1. 컴포넌트 루트: 섹션/컴포넌트 이름 → .loginForm, .heroSection
|
|
186
|
+
2. 자식 요소: 부모이름 + 역할 → .heroTitle, .heroCta
|
|
187
|
+
3. 이미지 클래스: 용도 명시 → .heroBg, .productPhoto, .brandLogo
|
|
188
|
+
4. 상태 변형: variant/state 접미사 → .buttonPrimary, .cardHighlight
|
|
487
189
|
```
|
|
488
190
|
|
|
489
|
-
|
|
191
|
+
### Anti-Patterns
|
|
490
192
|
|
|
491
193
|
```css
|
|
492
|
-
/* WRONG
|
|
493
|
-
.wrapper { }
|
|
494
|
-
.inner { }
|
|
495
|
-
.box { }
|
|
496
|
-
.item { }
|
|
497
|
-
.text1 { }
|
|
498
|
-
|
|
499
|
-
/* CORRECT: 역할이 드러나는 이름 */
|
|
500
|
-
.eventSection { }
|
|
501
|
-
.eventContent { }
|
|
502
|
-
.rewardCard { }
|
|
503
|
-
.rewardItem { }
|
|
504
|
-
.eventDescription { }
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
**Component style file MUST reference global tokens:**
|
|
508
|
-
|
|
509
|
-
```css
|
|
510
|
-
/* LoginForm.module.css */
|
|
511
|
-
.loginForm {
|
|
512
|
-
padding: var(--figma-space-6);
|
|
513
|
-
background: var(--figma-surface);
|
|
514
|
-
border-radius: var(--figma-radius-lg);
|
|
515
|
-
box-shadow: var(--figma-shadow-md);
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
.loginFormTitle {
|
|
519
|
-
font-size: var(--figma-text-xl);
|
|
520
|
-
font-weight: 600;
|
|
521
|
-
color: var(--figma-text-primary);
|
|
522
|
-
line-height: var(--figma-leading-tight);
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
.loginFormSubmit {
|
|
526
|
-
background: var(--figma-primary);
|
|
527
|
-
color: var(--figma-surface);
|
|
528
|
-
border-radius: var(--figma-radius-md);
|
|
529
|
-
padding: var(--figma-space-2) var(--figma-space-4);
|
|
530
|
-
transition: background 150ms ease;
|
|
531
|
-
}
|
|
194
|
+
/* WRONG */
|
|
195
|
+
.wrapper { } .inner { } .box { } .item { } .text1 { }
|
|
532
196
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
}
|
|
197
|
+
/* CORRECT */
|
|
198
|
+
.eventSection { } .eventContent { } .rewardCard { } .eventDescription { }
|
|
536
199
|
```
|
|
537
200
|
|
|
538
201
|
---
|
|
539
202
|
|
|
540
|
-
##
|
|
541
|
-
|
|
542
|
-
**Only in default (project integration) mode.** Map extracted Figma tokens to the project's existing token system.
|
|
543
|
-
|
|
544
|
-
### Token Source Priority
|
|
545
|
-
|
|
546
|
-
```
|
|
547
|
-
1. MASTER.md (.claude/vibe/design-system/{project}/MASTER.md) ← 최우선
|
|
548
|
-
2. design-context.json (.claude/vibe/design-context.json) ← 보조
|
|
549
|
-
3. Project theme files (tailwind.config, CSS variables, etc.) ← 폴백
|
|
550
|
-
4. Generate new figma-tokens ← 마지막 수단
|
|
551
|
-
```
|
|
552
|
-
|
|
553
|
-
### Mapping Rules
|
|
554
|
-
|
|
555
|
-
1. **MASTER.md exists** → authoritative token source
|
|
556
|
-
- Figma color ≈ MASTER.md color (ΔE < 5) → use MASTER.md token name
|
|
557
|
-
- Figma spacing ≈ MASTER.md spacing (±2px) → use MASTER.md token name
|
|
558
|
-
- Figma font ≈ MASTER.md font → use MASTER.md token name
|
|
559
|
-
- Unmatched Figma values → add to `figma-tokens.css` as supplementary tokens
|
|
560
|
-
|
|
561
|
-
2. **No MASTER.md, but design-context.json exists** →
|
|
562
|
-
- Use `detectedStack` info for naming convention
|
|
563
|
-
- Use `aesthetic.colorMood` to validate token naming (e.g., warm palette → warm- prefix)
|
|
564
|
-
- Generate `figma-tokens.css` grouped by category
|
|
565
|
-
|
|
566
|
-
3. **No design system at all** →
|
|
567
|
-
- Generate `figma-tokens.css` (or Tailwind extend)
|
|
568
|
-
- Group tokens by category (color, typography, spacing, shadow)
|
|
569
|
-
|
|
570
|
-
### Output Mapping Comment
|
|
203
|
+
## S-6. 토큰 매핑 코멘트 (기본 모드)
|
|
571
204
|
|
|
572
|
-
|
|
205
|
+
토큰 파일 상단에 매핑 결과를 코멘트로 출력:
|
|
573
206
|
|
|
574
207
|
```
|
|
575
208
|
/* Figma Token Mapping:
|
|
576
|
-
* Figma "Primary
|
|
209
|
+
* Figma "Primary" → var(--figma-primary) = #3B82F6
|
|
577
210
|
* ✅ Matched: var(--color-blue-500) from MASTER.md
|
|
578
|
-
* Figma "
|
|
579
|
-
*
|
|
580
|
-
* Figma "Accent/Glow" → var(--figma-accent-glow) = #7C3AED
|
|
581
|
-
* ⚠️ New token: no existing match
|
|
211
|
+
* Figma "Accent" → var(--figma-accent) = #7C3AED
|
|
212
|
+
* New token: no existing match
|
|
582
213
|
*/
|
|
583
214
|
```
|