@champpaba/claude-agent-kit 1.1.0 → 1.1.1
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/.claude/CHANGELOG-v1.1.1.md +259 -0
- package/.claude/CLAUDE.md +555 -0
- package/.claude/agents/01-integration.md +797 -0
- package/.claude/agents/02-uxui-frontend.md +955 -0
- package/.claude/agents/03-test-debug.md +759 -0
- package/.claude/agents/04-frontend.md +1099 -0
- package/.claude/agents/05-backend.md +1217 -0
- package/.claude/agents/06-database.md +969 -0
- package/.claude/commands/agentsetup.md +1464 -0
- package/.claude/commands/cdev.md +327 -0
- package/.claude/commands/csetup.md +447 -0
- package/.claude/commands/cstatus.md +60 -0
- package/.claude/commands/cview.md +364 -0
- package/.claude/commands/designsetup.md +838 -0
- package/.claude/commands/extract.md +1005 -0
- package/.claude/commands/pageplan.md +327 -0
- package/.claude/commands/psetup.md +101 -0
- package/.claude/contexts/design/accessibility.md +611 -0
- package/.claude/contexts/design/box-thinking.md +566 -0
- package/.claude/contexts/design/color-theory.md +519 -0
- package/.claude/contexts/design/index.md +290 -0
- package/.claude/contexts/design/layout.md +400 -0
- package/.claude/contexts/design/responsive.md +551 -0
- package/.claude/contexts/design/shadows.md +522 -0
- package/.claude/contexts/design/spacing.md +445 -0
- package/.claude/contexts/design/typography.md +465 -0
- package/.claude/contexts/domain/README.md +164 -0
- package/.claude/contexts/patterns/agent-coordination.md +388 -0
- package/.claude/contexts/patterns/agent-discovery.md +182 -0
- package/.claude/contexts/patterns/change-workflow.md +538 -0
- package/.claude/contexts/patterns/code-standards.md +515 -0
- package/.claude/contexts/patterns/development-principles.md +513 -0
- package/.claude/contexts/patterns/error-handling.md +478 -0
- package/.claude/contexts/patterns/error-recovery.md +365 -0
- package/.claude/contexts/patterns/frontend-component-strategy.md +365 -0
- package/.claude/contexts/patterns/git-workflow.md +207 -0
- package/.claude/contexts/patterns/logging.md +424 -0
- package/.claude/contexts/patterns/task-breakdown.md +452 -0
- package/.claude/contexts/patterns/task-classification.md +523 -0
- package/.claude/contexts/patterns/tdd-classification.md +516 -0
- package/.claude/contexts/patterns/testing.md +413 -0
- package/.claude/contexts/patterns/ui-component-consistency.md +304 -0
- package/.claude/contexts/patterns/validation-framework.md +776 -0
- package/.claude/lib/README.md +39 -0
- package/.claude/lib/agent-executor.md +258 -0
- package/.claude/lib/agent-router.md +572 -0
- package/.claude/lib/flags-updater.md +469 -0
- package/.claude/lib/tdd-classifier.md +345 -0
- package/.claude/lib/validation-gates.md +484 -0
- package/.claude/settings.local.json +42 -0
- package/.claude/templates/STYLE_GUIDE.template.md +879 -0
- package/.claude/templates/context-template.md +45 -0
- package/.claude/templates/flags-template.json +42 -0
- package/.claude/templates/page-plan-example.md +562 -0
- package/.claude/templates/phase-templates.json +124 -0
- package/.claude/templates/phases-sections/accessibility-test.md +17 -0
- package/.claude/templates/phases-sections/api-design.md +37 -0
- package/.claude/templates/phases-sections/backend-tests.md +16 -0
- package/.claude/templates/phases-sections/backend.md +37 -0
- package/.claude/templates/phases-sections/business-logic-validation.md +16 -0
- package/.claude/templates/phases-sections/component-tests.md +17 -0
- package/.claude/templates/phases-sections/contract-backend.md +16 -0
- package/.claude/templates/phases-sections/contract-frontend.md +16 -0
- package/.claude/templates/phases-sections/database.md +35 -0
- package/.claude/templates/phases-sections/documentation.md +17 -0
- package/.claude/templates/phases-sections/e2e-tests.md +16 -0
- package/.claude/templates/phases-sections/fix-implementation.md +17 -0
- package/.claude/templates/phases-sections/frontend-integration.md +18 -0
- package/.claude/templates/phases-sections/frontend-mockup.md +123 -0
- package/.claude/templates/phases-sections/manual-flow-test.md +15 -0
- package/.claude/templates/phases-sections/manual-ux-test.md +16 -0
- package/.claude/templates/phases-sections/refactor-implementation.md +17 -0
- package/.claude/templates/phases-sections/refactor.md +16 -0
- package/.claude/templates/phases-sections/regression-tests.md +15 -0
- package/.claude/templates/phases-sections/report.md +16 -0
- package/.claude/templates/phases-sections/responsive-test.md +16 -0
- package/.claude/templates/phases-sections/script-implementation.md +43 -0
- package/.claude/templates/phases-sections/test-coverage.md +16 -0
- package/.claude/templates/phases-sections/user-approval.md +14 -0
- package/lib/init.js +1 -1
- package/lib/update.js +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,1005 @@
|
|
|
1
|
+
# /extract - Extract Design System from Website (Component-Level)
|
|
2
|
+
|
|
3
|
+
You are an expert design systems engineer with deep knowledge of CSS, animations, and UX patterns.
|
|
4
|
+
|
|
5
|
+
Your task is to extract comprehensive design data from a website and save it as reusable YAML files with component-level detail.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 📖 Usage
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
/extract <URL>
|
|
13
|
+
|
|
14
|
+
Arguments:
|
|
15
|
+
URL Required. Website URL to extract from
|
|
16
|
+
|
|
17
|
+
Examples:
|
|
18
|
+
/extract https://airbnb.com
|
|
19
|
+
/extract https://blackbird.com
|
|
20
|
+
/extract https://linear.app
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 🎯 Mission
|
|
26
|
+
|
|
27
|
+
Extract ALL design data from a website and save to `design-system/extracted/{site-name}/`:
|
|
28
|
+
- `data.yaml` - Complete 17-section design data + animations
|
|
29
|
+
- `analysis.md` - Psychology & design philosophy analysis
|
|
30
|
+
- `screenshots/` - Component screenshots (default + hover/focus states)
|
|
31
|
+
|
|
32
|
+
**Key Principles:**
|
|
33
|
+
1. **Component-Level Detail**: Extract every component type with all states
|
|
34
|
+
2. **Animation Capture**: Before/after screenshots for all interactive states
|
|
35
|
+
3. **17 Sections Mandatory**: Template-based extraction (fallback if not found)
|
|
36
|
+
4. **Reusable**: Output can be used by multiple projects
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 🔍 STEP 0: Parse Input & Setup
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
// Parse URL
|
|
44
|
+
const input = args[0];
|
|
45
|
+
if (!input) {
|
|
46
|
+
return error('URL required. Usage: /extract https://airbnb.com');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Normalize URL
|
|
50
|
+
let url = input.trim();
|
|
51
|
+
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
|
52
|
+
url = 'https://' + url;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Auto-detect site name
|
|
56
|
+
const siteName = new URL(url).hostname
|
|
57
|
+
.replace('www.', '')
|
|
58
|
+
.replace(/\.[^.]+$/, ''); // Remove TLD
|
|
59
|
+
// e.g., "airbnb.com" → "airbnb"
|
|
60
|
+
|
|
61
|
+
// Check if already extracted
|
|
62
|
+
const extractedPath = `design-system/extracted/${siteName}`;
|
|
63
|
+
if (exists(extractedPath + '/data.yaml')) {
|
|
64
|
+
const existingData = YAML.parse(Read(extractedPath + '/data.yaml'));
|
|
65
|
+
const extractedDate = existingData.meta.extracted_at;
|
|
66
|
+
|
|
67
|
+
const response = await AskUserQuestion({
|
|
68
|
+
questions: [{
|
|
69
|
+
question: `Site "${siteName}" was already extracted on ${extractedDate}. Re-extract?`,
|
|
70
|
+
header: "Re-extract?",
|
|
71
|
+
multiSelect: false,
|
|
72
|
+
options: [
|
|
73
|
+
{ label: "Yes, re-extract", description: "Overwrite previous data" },
|
|
74
|
+
{ label: "No, cancel", description: "Keep existing data" }
|
|
75
|
+
]
|
|
76
|
+
}]
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (response.answers["Re-extract?"] === "No, cancel") {
|
|
80
|
+
return output('Extraction cancelled. Existing data preserved.');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Create directories
|
|
85
|
+
Bash: mkdir -p design-system/extracted/${siteName}/screenshots
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Report:**
|
|
89
|
+
```
|
|
90
|
+
🚀 Extraction Started
|
|
91
|
+
|
|
92
|
+
📍 URL: ${url}
|
|
93
|
+
📁 Site: ${siteName}
|
|
94
|
+
📂 Output: design-system/extracted/${siteName}/
|
|
95
|
+
|
|
96
|
+
⏳ Navigating to site...
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## STEP 1: Navigate & Wait
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
// Navigate
|
|
105
|
+
await mcp__chrome-devtools__navigate_page({ url });
|
|
106
|
+
|
|
107
|
+
// Smart wait
|
|
108
|
+
try {
|
|
109
|
+
const snapshot = await mcp__chrome-devtools__take_snapshot({ verbose: false });
|
|
110
|
+
|
|
111
|
+
// Find main heading
|
|
112
|
+
const headings = snapshot.split('\n')
|
|
113
|
+
.filter(line => line.includes('[heading]'))
|
|
114
|
+
.slice(0, 3);
|
|
115
|
+
|
|
116
|
+
if (headings.length > 0) {
|
|
117
|
+
const mainText = headings[0].split('"')[1]; // Extract text
|
|
118
|
+
|
|
119
|
+
await mcp__chrome-devtools__wait_for({
|
|
120
|
+
text: mainText,
|
|
121
|
+
timeout: 15000
|
|
122
|
+
});
|
|
123
|
+
} else {
|
|
124
|
+
// Fallback: fixed wait
|
|
125
|
+
await sleep(5000);
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
await sleep(5000);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Verify loaded
|
|
132
|
+
const readyState = await mcp__chrome-devtools__evaluate_script({
|
|
133
|
+
function: '() => document.readyState'
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (readyState !== 'complete') {
|
|
137
|
+
await sleep(3000);
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Report:**
|
|
142
|
+
```
|
|
143
|
+
✅ Page loaded successfully
|
|
144
|
+
|
|
145
|
+
🔄 Extracting CSS data (17 sections)...
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## STEP 2: Extract CSS Data (17 Sections in Parallel)
|
|
151
|
+
|
|
152
|
+
Run all extraction scripts in parallel for speed:
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
const extractionPromises = [
|
|
156
|
+
extractColors(),
|
|
157
|
+
extractTypography(),
|
|
158
|
+
extractShadows(),
|
|
159
|
+
extractSpacing(),
|
|
160
|
+
extractButtons(),
|
|
161
|
+
extractCards(),
|
|
162
|
+
extractInputs(),
|
|
163
|
+
extractAnimations()
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
const [
|
|
167
|
+
colors,
|
|
168
|
+
typography,
|
|
169
|
+
shadows,
|
|
170
|
+
spacing,
|
|
171
|
+
buttons,
|
|
172
|
+
cards,
|
|
173
|
+
inputs,
|
|
174
|
+
animations
|
|
175
|
+
] = await Promise.all(extractionPromises);
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### 2.1: Extract Colors
|
|
179
|
+
|
|
180
|
+
```javascript
|
|
181
|
+
async function extractColors() {
|
|
182
|
+
return await mcp__chrome-devtools__evaluate_script({
|
|
183
|
+
function: `() => {
|
|
184
|
+
const allElements = document.querySelectorAll('*');
|
|
185
|
+
const colors = {
|
|
186
|
+
backgrounds: new Set(),
|
|
187
|
+
texts: new Set(),
|
|
188
|
+
borders: new Set()
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
allElements.forEach(el => {
|
|
192
|
+
const s = window.getComputedStyle(el);
|
|
193
|
+
if (s.backgroundColor && s.backgroundColor !== 'rgba(0, 0, 0, 0)') {
|
|
194
|
+
colors.backgrounds.add(s.backgroundColor);
|
|
195
|
+
}
|
|
196
|
+
if (s.color) colors.texts.add(s.color);
|
|
197
|
+
if (s.borderColor && s.borderColor !== 'rgba(0, 0, 0, 0)') {
|
|
198
|
+
colors.borders.add(s.borderColor);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Convert RGB to HEX
|
|
203
|
+
const rgbToHex = (rgb) => {
|
|
204
|
+
const match = rgb.match(/\\d+/g);
|
|
205
|
+
if (!match) return rgb;
|
|
206
|
+
const hex = match.slice(0, 3).map(x => {
|
|
207
|
+
const h = parseInt(x).toString(16);
|
|
208
|
+
return h.length === 1 ? '0' + h : h;
|
|
209
|
+
});
|
|
210
|
+
return '#' + hex.join('').toUpperCase();
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
backgrounds: Array.from(colors.backgrounds).slice(0, 30).map(c => ({
|
|
215
|
+
rgb: c,
|
|
216
|
+
hex: rgbToHex(c)
|
|
217
|
+
})),
|
|
218
|
+
texts: Array.from(colors.texts).slice(0, 20).map(c => ({
|
|
219
|
+
rgb: c,
|
|
220
|
+
hex: rgbToHex(c)
|
|
221
|
+
})),
|
|
222
|
+
borders: Array.from(colors.borders).slice(0, 15).map(c => ({
|
|
223
|
+
rgb: c,
|
|
224
|
+
hex: rgbToHex(c)
|
|
225
|
+
}))
|
|
226
|
+
};
|
|
227
|
+
}`
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### 2.2: Extract Typography
|
|
233
|
+
|
|
234
|
+
```javascript
|
|
235
|
+
async function extractTypography() {
|
|
236
|
+
return await mcp__chrome-devtools__evaluate_script({
|
|
237
|
+
function: `() => {
|
|
238
|
+
const fonts = new Set();
|
|
239
|
+
const weights = new Set();
|
|
240
|
+
const sizes = new Set();
|
|
241
|
+
|
|
242
|
+
const typography = {
|
|
243
|
+
h1: [],
|
|
244
|
+
h2: [],
|
|
245
|
+
h3: [],
|
|
246
|
+
body: []
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// Headings
|
|
250
|
+
['h1', 'h2', 'h3'].forEach(tag => {
|
|
251
|
+
Array.from(document.querySelectorAll(tag)).slice(0, 3).forEach(el => {
|
|
252
|
+
const s = window.getComputedStyle(el);
|
|
253
|
+
typography[tag].push({
|
|
254
|
+
text: el.textContent.trim().substring(0, 50),
|
|
255
|
+
fontSize: s.fontSize,
|
|
256
|
+
fontWeight: s.fontWeight,
|
|
257
|
+
fontFamily: s.fontFamily,
|
|
258
|
+
lineHeight: s.lineHeight,
|
|
259
|
+
letterSpacing: s.letterSpacing,
|
|
260
|
+
textTransform: s.textTransform,
|
|
261
|
+
color: s.color
|
|
262
|
+
});
|
|
263
|
+
fonts.add(s.fontFamily);
|
|
264
|
+
weights.add(s.fontWeight);
|
|
265
|
+
sizes.add(s.fontSize);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// Body text
|
|
270
|
+
Array.from(document.querySelectorAll('p, div, span')).slice(0, 20).forEach(el => {
|
|
271
|
+
const s = window.getComputedStyle(el);
|
|
272
|
+
if (el.textContent.trim().length > 20) {
|
|
273
|
+
typography.body.push({
|
|
274
|
+
fontSize: s.fontSize,
|
|
275
|
+
fontWeight: s.fontWeight,
|
|
276
|
+
lineHeight: s.lineHeight,
|
|
277
|
+
fontFamily: s.fontFamily,
|
|
278
|
+
color: s.color
|
|
279
|
+
});
|
|
280
|
+
fonts.add(s.fontFamily);
|
|
281
|
+
weights.add(s.fontWeight);
|
|
282
|
+
sizes.add(s.fontSize);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
...typography,
|
|
288
|
+
allFonts: Array.from(fonts),
|
|
289
|
+
allWeights: Array.from(weights).sort((a, b) => parseInt(a) - parseInt(b)),
|
|
290
|
+
allSizes: Array.from(sizes).sort((a, b) => parseFloat(a) - parseFloat(b))
|
|
291
|
+
};
|
|
292
|
+
}`
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### 2.3: Extract Shadows & Effects
|
|
298
|
+
|
|
299
|
+
```javascript
|
|
300
|
+
async function extractShadows() {
|
|
301
|
+
return await mcp__chrome-devtools__evaluate_script({
|
|
302
|
+
function: `() => {
|
|
303
|
+
const allElements = document.querySelectorAll('*');
|
|
304
|
+
const effects = {
|
|
305
|
+
shadows: new Set(),
|
|
306
|
+
borderRadii: new Set(),
|
|
307
|
+
borderWidths: new Set()
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
allElements.forEach(el => {
|
|
311
|
+
const s = window.getComputedStyle(el);
|
|
312
|
+
if (s.boxShadow && s.boxShadow !== 'none') {
|
|
313
|
+
effects.shadows.add(s.boxShadow);
|
|
314
|
+
}
|
|
315
|
+
if (s.borderRadius && s.borderRadius !== '0px') {
|
|
316
|
+
effects.borderRadii.add(s.borderRadius);
|
|
317
|
+
}
|
|
318
|
+
if (s.borderWidth && s.borderWidth !== '0px') {
|
|
319
|
+
effects.borderWidths.add(s.borderWidth);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
shadows: Array.from(effects.shadows).slice(0, 15),
|
|
325
|
+
borderRadii: Array.from(effects.borderRadii).slice(0, 15),
|
|
326
|
+
borderWidths: Array.from(effects.borderWidths).slice(0, 10)
|
|
327
|
+
};
|
|
328
|
+
}`
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### 2.4: Extract Spacing
|
|
334
|
+
|
|
335
|
+
```javascript
|
|
336
|
+
async function extractSpacing() {
|
|
337
|
+
return await mcp__chrome-devtools__evaluate_script({
|
|
338
|
+
function: `() => {
|
|
339
|
+
const spacing = {
|
|
340
|
+
paddings: new Set(),
|
|
341
|
+
margins: new Set(),
|
|
342
|
+
gaps: new Set()
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
Array.from(document.querySelectorAll('*')).slice(0, 100).forEach(el => {
|
|
346
|
+
const s = window.getComputedStyle(el);
|
|
347
|
+
|
|
348
|
+
// Padding
|
|
349
|
+
if (s.padding && s.padding !== '0px') {
|
|
350
|
+
[s.padding, s.paddingTop, s.paddingRight, s.paddingBottom, s.paddingLeft]
|
|
351
|
+
.forEach(v => spacing.paddings.add(v));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Margin
|
|
355
|
+
if (s.margin && s.margin !== '0px') {
|
|
356
|
+
[s.marginTop, s.marginBottom].forEach(v => {
|
|
357
|
+
if (v && v !== '0px' && v !== 'auto') spacing.margins.add(v);
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Gap
|
|
362
|
+
if (s.gap && s.gap !== 'normal' && s.gap !== '0px') {
|
|
363
|
+
spacing.gaps.add(s.gap);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// Detect grid pattern
|
|
368
|
+
const allValues = [
|
|
369
|
+
...spacing.paddings,
|
|
370
|
+
...spacing.margins,
|
|
371
|
+
...spacing.gaps
|
|
372
|
+
]
|
|
373
|
+
.map(v => parseFloat(v))
|
|
374
|
+
.filter(v => !isNaN(v) && v > 0)
|
|
375
|
+
.sort((a, b) => a - b);
|
|
376
|
+
|
|
377
|
+
// Find GCD (grid base)
|
|
378
|
+
const gcd = (a, b) => b === 0 ? a : gcd(b, a % b);
|
|
379
|
+
const gridBase = allValues.length > 1
|
|
380
|
+
? allValues.reduce((acc, val) => gcd(acc, val), allValues[0])
|
|
381
|
+
: 8;
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
paddings: Array.from(spacing.paddings).slice(0, 20),
|
|
385
|
+
margins: Array.from(spacing.margins).slice(0, 20),
|
|
386
|
+
gaps: Array.from(spacing.gaps).slice(0, 10),
|
|
387
|
+
detectedGrid: Math.round(gridBase) || 8,
|
|
388
|
+
commonValues: [...new Set(allValues)].slice(0, 15)
|
|
389
|
+
};
|
|
390
|
+
}`
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### 2.5-2.7: Extract Components
|
|
396
|
+
|
|
397
|
+
```javascript
|
|
398
|
+
async function extractButtons() {
|
|
399
|
+
return await mcp__chrome-devtools__evaluate_script({
|
|
400
|
+
function: `() => {
|
|
401
|
+
return Array.from(document.querySelectorAll('button, a[role="button"], .btn, [class*="button"], [class*="Button"]'))
|
|
402
|
+
.slice(0, 10)
|
|
403
|
+
.map((btn, i) => {
|
|
404
|
+
btn.setAttribute('data-extract-id', 'button-' + i);
|
|
405
|
+
const s = window.getComputedStyle(btn);
|
|
406
|
+
return {
|
|
407
|
+
id: 'button-' + i,
|
|
408
|
+
text: btn.textContent.trim().substring(0, 30),
|
|
409
|
+
backgroundColor: s.backgroundColor,
|
|
410
|
+
color: s.color,
|
|
411
|
+
padding: s.padding,
|
|
412
|
+
paddingTop: s.paddingTop,
|
|
413
|
+
paddingRight: s.paddingRight,
|
|
414
|
+
paddingBottom: s.paddingBottom,
|
|
415
|
+
paddingLeft: s.paddingLeft,
|
|
416
|
+
border: s.border,
|
|
417
|
+
borderWidth: s.borderWidth,
|
|
418
|
+
borderColor: s.borderColor,
|
|
419
|
+
borderRadius: s.borderRadius,
|
|
420
|
+
fontSize: s.fontSize,
|
|
421
|
+
fontWeight: s.fontWeight,
|
|
422
|
+
textTransform: s.textTransform,
|
|
423
|
+
letterSpacing: s.letterSpacing,
|
|
424
|
+
boxShadow: s.boxShadow,
|
|
425
|
+
transition: s.transition
|
|
426
|
+
};
|
|
427
|
+
});
|
|
428
|
+
}`
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async function extractCards() {
|
|
433
|
+
return await mcp__chrome-devtools__evaluate_script({
|
|
434
|
+
function: `() => {
|
|
435
|
+
const selectors = [
|
|
436
|
+
'[class*="card"]', '[class*="Card"]',
|
|
437
|
+
'article', 'section',
|
|
438
|
+
'[class*="box"]', '[class*="Box"]'
|
|
439
|
+
];
|
|
440
|
+
|
|
441
|
+
return Array.from(document.querySelectorAll(selectors.join(', ')))
|
|
442
|
+
.slice(0, 10)
|
|
443
|
+
.map((card, i) => {
|
|
444
|
+
card.setAttribute('data-extract-id', 'card-' + i);
|
|
445
|
+
const s = window.getComputedStyle(card);
|
|
446
|
+
return {
|
|
447
|
+
id: 'card-' + i,
|
|
448
|
+
className: card.className,
|
|
449
|
+
backgroundColor: s.backgroundColor,
|
|
450
|
+
padding: s.padding,
|
|
451
|
+
border: s.border,
|
|
452
|
+
borderRadius: s.borderRadius,
|
|
453
|
+
boxShadow: s.boxShadow,
|
|
454
|
+
transition: s.transition
|
|
455
|
+
};
|
|
456
|
+
});
|
|
457
|
+
}`
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
async function extractInputs() {
|
|
462
|
+
return await mcp__chrome-devtools__evaluate_script({
|
|
463
|
+
function: `() => {
|
|
464
|
+
return Array.from(document.querySelectorAll('input[type="text"], input[type="email"], input[type="password"], textarea'))
|
|
465
|
+
.slice(0, 5)
|
|
466
|
+
.map((input, i) => {
|
|
467
|
+
input.setAttribute('data-extract-id', 'input-' + i);
|
|
468
|
+
const s = window.getComputedStyle(input);
|
|
469
|
+
return {
|
|
470
|
+
id: 'input-' + i,
|
|
471
|
+
type: input.type || 'textarea',
|
|
472
|
+
height: s.height,
|
|
473
|
+
padding: s.padding,
|
|
474
|
+
border: s.border,
|
|
475
|
+
borderRadius: s.borderRadius,
|
|
476
|
+
fontSize: s.fontSize,
|
|
477
|
+
backgroundColor: s.backgroundColor,
|
|
478
|
+
transition: s.transition
|
|
479
|
+
};
|
|
480
|
+
});
|
|
481
|
+
}`
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
### 2.8: Extract Animations (@keyframes + transitions)
|
|
487
|
+
|
|
488
|
+
```javascript
|
|
489
|
+
async function extractAnimations() {
|
|
490
|
+
return await mcp__chrome-devtools__evaluate_script({
|
|
491
|
+
function: `() => {
|
|
492
|
+
const keyframes = [];
|
|
493
|
+
const transitions = [];
|
|
494
|
+
|
|
495
|
+
// Extract @keyframes
|
|
496
|
+
Array.from(document.styleSheets).forEach(sheet => {
|
|
497
|
+
try {
|
|
498
|
+
Array.from(sheet.cssRules || []).forEach(rule => {
|
|
499
|
+
if (rule.type === CSSRule.KEYFRAMES_RULE) {
|
|
500
|
+
keyframes.push({
|
|
501
|
+
name: rule.name,
|
|
502
|
+
css: rule.cssText
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
} catch(e) {
|
|
507
|
+
// CORS - skip
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
// Extract elements with transitions
|
|
512
|
+
Array.from(document.querySelectorAll('*')).slice(0, 50).forEach(el => {
|
|
513
|
+
const s = window.getComputedStyle(el);
|
|
514
|
+
if (s.transition && s.transition !== 'all 0s ease 0s') {
|
|
515
|
+
transitions.push({
|
|
516
|
+
selector: el.className || el.tagName,
|
|
517
|
+
transition: s.transition,
|
|
518
|
+
transitionDuration: s.transitionDuration,
|
|
519
|
+
transitionTimingFunction: s.transitionTimingFunction,
|
|
520
|
+
transitionProperty: s.transitionProperty
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
return { keyframes, transitions };
|
|
526
|
+
}`
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
**Report:**
|
|
532
|
+
```
|
|
533
|
+
✅ CSS Data Extracted!
|
|
534
|
+
|
|
535
|
+
📊 Summary:
|
|
536
|
+
- Colors: ${colors.backgrounds.length} backgrounds, ${colors.texts.length} texts, ${colors.borders.length} borders
|
|
537
|
+
- Typography: ${typography.allFonts.length} fonts, ${typography.allWeights.length} weights
|
|
538
|
+
- Shadows: ${shadows.shadows.length} unique values
|
|
539
|
+
- Spacing: ${spacing.detectedGrid}px grid detected
|
|
540
|
+
- Buttons: ${buttons.length} extracted
|
|
541
|
+
- Cards: ${cards.length} extracted
|
|
542
|
+
- Inputs: ${inputs.length} extracted
|
|
543
|
+
- Animations: ${animations.keyframes.length} @keyframes, ${animations.transitions.length} transitions
|
|
544
|
+
|
|
545
|
+
🔄 Extracting component animations (hover/focus states)...
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
---
|
|
549
|
+
|
|
550
|
+
## STEP 3: Extract Component Animations (Interactive States)
|
|
551
|
+
|
|
552
|
+
For each component, capture before/after states:
|
|
553
|
+
|
|
554
|
+
```javascript
|
|
555
|
+
const componentAnimations = {};
|
|
556
|
+
|
|
557
|
+
// Buttons
|
|
558
|
+
for (let i = 0; i < Math.min(buttons.length, 3); i++) {
|
|
559
|
+
const btnId = `button-${i}`;
|
|
560
|
+
|
|
561
|
+
// Screenshot: Default state
|
|
562
|
+
await mcp__chrome-devtools__evaluate_script({
|
|
563
|
+
function: `() => {
|
|
564
|
+
const el = document.querySelector('[data-extract-id="${btnId}"]');
|
|
565
|
+
if (el) el.scrollIntoView({ block: 'center' });
|
|
566
|
+
}`
|
|
567
|
+
});
|
|
568
|
+
await sleep(500);
|
|
569
|
+
|
|
570
|
+
await mcp__chrome-devtools__take_screenshot({
|
|
571
|
+
filePath: `design-system/extracted/${siteName}/screenshots/${btnId}-default.png`
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
// Get default computed styles
|
|
575
|
+
const defaultStyle = await mcp__chrome-devtools__evaluate_script({
|
|
576
|
+
function: `() => {
|
|
577
|
+
const el = document.querySelector('[data-extract-id="${btnId}"]');
|
|
578
|
+
if (!el) return null;
|
|
579
|
+
const s = window.getComputedStyle(el);
|
|
580
|
+
return {
|
|
581
|
+
background: s.backgroundColor,
|
|
582
|
+
color: s.color,
|
|
583
|
+
border: s.border,
|
|
584
|
+
borderRadius: s.borderRadius,
|
|
585
|
+
boxShadow: s.boxShadow,
|
|
586
|
+
transform: s.transform,
|
|
587
|
+
opacity: s.opacity
|
|
588
|
+
};
|
|
589
|
+
}`
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
// Trigger hover
|
|
593
|
+
await mcp__chrome-devtools__evaluate_script({
|
|
594
|
+
function: `() => {
|
|
595
|
+
const el = document.querySelector('[data-extract-id="${btnId}"]');
|
|
596
|
+
if (el) el.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
|
|
597
|
+
}`
|
|
598
|
+
});
|
|
599
|
+
await sleep(500); // Wait for transition
|
|
600
|
+
|
|
601
|
+
// Screenshot: Hover state
|
|
602
|
+
await mcp__chrome-devtools__take_screenshot({
|
|
603
|
+
filePath: `design-system/extracted/${siteName}/screenshots/${btnId}-hover.png`
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
// Get hover computed styles
|
|
607
|
+
const hoverStyle = await mcp__chrome-devtools__evaluate_script({
|
|
608
|
+
function: `() => {
|
|
609
|
+
const el = document.querySelector('[data-extract-id="${btnId}"]');
|
|
610
|
+
if (!el) return null;
|
|
611
|
+
const s = window.getComputedStyle(el);
|
|
612
|
+
return {
|
|
613
|
+
background: s.backgroundColor,
|
|
614
|
+
color: s.color,
|
|
615
|
+
border: s.border,
|
|
616
|
+
borderRadius: s.borderRadius,
|
|
617
|
+
boxShadow: s.boxShadow,
|
|
618
|
+
transform: s.transform,
|
|
619
|
+
opacity: s.opacity
|
|
620
|
+
};
|
|
621
|
+
}`
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
// Remove hover
|
|
625
|
+
await mcp__chrome-devtools__evaluate_script({
|
|
626
|
+
function: `() => {
|
|
627
|
+
const el = document.querySelector('[data-extract-id="${btnId}"]');
|
|
628
|
+
if (el) el.dispatchEvent(new MouseEvent('mouseleave'));
|
|
629
|
+
}`
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
// Calculate changes
|
|
633
|
+
componentAnimations[btnId] = {
|
|
634
|
+
type: 'button',
|
|
635
|
+
states: {
|
|
636
|
+
default: defaultStyle,
|
|
637
|
+
hover: hoverStyle
|
|
638
|
+
},
|
|
639
|
+
changes: {
|
|
640
|
+
background_changed: defaultStyle.background !== hoverStyle.background,
|
|
641
|
+
shadow_changed: defaultStyle.boxShadow !== hoverStyle.boxShadow,
|
|
642
|
+
transform_changed: defaultStyle.transform !== hoverStyle.transform,
|
|
643
|
+
border_changed: defaultStyle.border !== hoverStyle.border
|
|
644
|
+
},
|
|
645
|
+
transition: buttons[i].transition,
|
|
646
|
+
description: generateDescription(defaultStyle, hoverStyle)
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Cards (same pattern)
|
|
651
|
+
for (let i = 0; i < Math.min(cards.length, 3); i++) {
|
|
652
|
+
// ... same process for cards ...
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Inputs (focus state)
|
|
656
|
+
for (let i = 0; i < Math.min(inputs.length, 3); i++) {
|
|
657
|
+
// ... same process with 'focus' event instead of 'mouseenter' ...
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function generateDescription(defaultStyle, hoverStyle) {
|
|
661
|
+
const changes = [];
|
|
662
|
+
|
|
663
|
+
if (defaultStyle.boxShadow !== hoverStyle.boxShadow) {
|
|
664
|
+
if (defaultStyle.boxShadow === 'none' && hoverStyle.boxShadow !== 'none') {
|
|
665
|
+
changes.push('Shadow appears');
|
|
666
|
+
} else if (defaultStyle.boxShadow !== 'none' && hoverStyle.boxShadow !== 'none') {
|
|
667
|
+
changes.push('Shadow changes');
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (defaultStyle.transform !== hoverStyle.transform) {
|
|
672
|
+
if (hoverStyle.transform.includes('scale')) {
|
|
673
|
+
changes.push('Scales up');
|
|
674
|
+
}
|
|
675
|
+
if (hoverStyle.transform.includes('translateY')) {
|
|
676
|
+
changes.push('Moves up');
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (defaultStyle.background !== hoverStyle.background) {
|
|
681
|
+
changes.push('Background changes');
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
return changes.length > 0 ? changes.join(' + ') : 'No visible changes';
|
|
685
|
+
}
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
**Report:**
|
|
689
|
+
```
|
|
690
|
+
✅ Component Animations Extracted!
|
|
691
|
+
|
|
692
|
+
📸 Screenshots captured:
|
|
693
|
+
- ${Object.keys(componentAnimations).length} components
|
|
694
|
+
- ${Object.keys(componentAnimations).length * 2} screenshots (default + hover/focus)
|
|
695
|
+
|
|
696
|
+
🔄 Capturing full-page screenshot...
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
---
|
|
700
|
+
|
|
701
|
+
## STEP 4: Full-Page Screenshot
|
|
702
|
+
|
|
703
|
+
```javascript
|
|
704
|
+
// Create temp directory
|
|
705
|
+
await Bash: mkdir -p design-system/extracted/${siteName}/screenshots
|
|
706
|
+
|
|
707
|
+
// Try fullpage
|
|
708
|
+
try {
|
|
709
|
+
await mcp__chrome-devtools__take_screenshot({
|
|
710
|
+
fullPage: true,
|
|
711
|
+
format: 'png',
|
|
712
|
+
filePath: `design-system/extracted/${siteName}/screenshots/full-page.png`
|
|
713
|
+
});
|
|
714
|
+
} catch {
|
|
715
|
+
// Fallback: viewport only
|
|
716
|
+
await mcp__chrome-devtools__take_screenshot({
|
|
717
|
+
fullPage: false,
|
|
718
|
+
format: 'png',
|
|
719
|
+
filePath: `design-system/extracted/${siteName}/screenshots/viewport.png`
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
---
|
|
725
|
+
|
|
726
|
+
## STEP 5: AI Psychology Analysis
|
|
727
|
+
|
|
728
|
+
Read screenshot and analyze:
|
|
729
|
+
|
|
730
|
+
```javascript
|
|
731
|
+
const screenshotPath = exists(`design-system/extracted/${siteName}/screenshots/full-page.png`)
|
|
732
|
+
? `design-system/extracted/${siteName}/screenshots/full-page.png`
|
|
733
|
+
: `design-system/extracted/${siteName}/screenshots/viewport.png`;
|
|
734
|
+
|
|
735
|
+
const screenshot = Read(screenshotPath);
|
|
736
|
+
|
|
737
|
+
const analysisPrompt = `
|
|
738
|
+
You are a UX/UI design psychologist and systems architect.
|
|
739
|
+
|
|
740
|
+
Analyze this website's design and provide deep psychology insights.
|
|
741
|
+
|
|
742
|
+
Visual Screenshot: [attached]
|
|
743
|
+
|
|
744
|
+
Extracted CSS Data:
|
|
745
|
+
- Colors: ${JSON.stringify(colors, null, 2)}
|
|
746
|
+
- Typography: ${JSON.stringify(typography, null, 2)}
|
|
747
|
+
- Shadows: ${JSON.stringify(shadows, null, 2)}
|
|
748
|
+
- Spacing: ${JSON.stringify(spacing, null, 2)}
|
|
749
|
+
- Button Styles: ${JSON.stringify(buttons.slice(0, 2), null, 2)}
|
|
750
|
+
- Card Styles: ${JSON.stringify(cards.slice(0, 2), null, 2)}
|
|
751
|
+
|
|
752
|
+
Component Animations:
|
|
753
|
+
${Object.entries(componentAnimations).slice(0, 3).map(([id, anim]) =>
|
|
754
|
+
`- ${id}: ${anim.description}`
|
|
755
|
+
).join('\n')}
|
|
756
|
+
|
|
757
|
+
Wrap your analysis in <pondering> tags and include:
|
|
758
|
+
|
|
759
|
+
1. **Design Style Classification** (Neo-Brutalism, Minimalist, Modern SaaS, etc.)
|
|
760
|
+
2. **Visual Principles** (what design principles are used?)
|
|
761
|
+
3. **Psychology** (what emotions does it evoke? why?)
|
|
762
|
+
4. **Target Audience** (who is this for?)
|
|
763
|
+
5. **Key Differentiators** (how is it different from typical sites?)
|
|
764
|
+
6. **Design Philosophy** (core beliefs that guide this design)
|
|
765
|
+
|
|
766
|
+
Be specific with examples from the CSS data.
|
|
767
|
+
`;
|
|
768
|
+
|
|
769
|
+
const analysis = await LLM({
|
|
770
|
+
prompt: analysisPrompt,
|
|
771
|
+
images: [screenshot]
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
Write(`design-system/extracted/${siteName}/analysis.md`, analysis);
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
**Report:**
|
|
778
|
+
```
|
|
779
|
+
✅ Psychology Analysis Complete!
|
|
780
|
+
|
|
781
|
+
🧠 Design style detected from analysis
|
|
782
|
+
📁 Saved: analysis.md
|
|
783
|
+
|
|
784
|
+
🔄 Generating final YAML output...
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
---
|
|
788
|
+
|
|
789
|
+
## STEP 6: Generate data.yaml (17 Sections)
|
|
790
|
+
|
|
791
|
+
```javascript
|
|
792
|
+
const yamlData = {
|
|
793
|
+
meta: {
|
|
794
|
+
site_name: siteName,
|
|
795
|
+
url: url,
|
|
796
|
+
extracted_at: new Date().toISOString(),
|
|
797
|
+
extractor_version: '2.0.0',
|
|
798
|
+
coverage: {
|
|
799
|
+
total_sections: 17,
|
|
800
|
+
detected_sections: 15, // Count how many have detected: true
|
|
801
|
+
percentage: Math.round((15 / 17) * 100)
|
|
802
|
+
}
|
|
803
|
+
},
|
|
804
|
+
|
|
805
|
+
sections: {
|
|
806
|
+
overview: {
|
|
807
|
+
detected: true,
|
|
808
|
+
style: 'Auto-detected from analysis',
|
|
809
|
+
tech_stack: 'Framework-agnostic'
|
|
810
|
+
},
|
|
811
|
+
|
|
812
|
+
design_philosophy: {
|
|
813
|
+
detected: true,
|
|
814
|
+
from_analysis: true
|
|
815
|
+
},
|
|
816
|
+
|
|
817
|
+
color_palette: {
|
|
818
|
+
detected: true,
|
|
819
|
+
primary: colors.backgrounds.slice(0, 5),
|
|
820
|
+
secondary: colors.backgrounds.slice(5, 10),
|
|
821
|
+
text_colors: colors.texts,
|
|
822
|
+
border_colors: colors.borders
|
|
823
|
+
},
|
|
824
|
+
|
|
825
|
+
typography: {
|
|
826
|
+
detected: true,
|
|
827
|
+
fonts: typography.allFonts,
|
|
828
|
+
weights: typography.allWeights,
|
|
829
|
+
sizes: typography.allSizes,
|
|
830
|
+
h1: typography.h1,
|
|
831
|
+
h2: typography.h2,
|
|
832
|
+
h3: typography.h3,
|
|
833
|
+
body: typography.body
|
|
834
|
+
},
|
|
835
|
+
|
|
836
|
+
spacing_system: {
|
|
837
|
+
detected: true,
|
|
838
|
+
grid_base: spacing.detectedGrid,
|
|
839
|
+
paddings: spacing.paddings,
|
|
840
|
+
margins: spacing.margins,
|
|
841
|
+
gaps: spacing.gaps,
|
|
842
|
+
common_values: spacing.commonValues
|
|
843
|
+
},
|
|
844
|
+
|
|
845
|
+
component_styles: {
|
|
846
|
+
detected: true,
|
|
847
|
+
buttons: buttons.map((btn, i) => ({
|
|
848
|
+
...btn,
|
|
849
|
+
animation: componentAnimations[`button-${i}`] || null
|
|
850
|
+
})),
|
|
851
|
+
cards: cards.map((card, i) => ({
|
|
852
|
+
...card,
|
|
853
|
+
animation: componentAnimations[`card-${i}`] || null
|
|
854
|
+
})),
|
|
855
|
+
inputs: inputs.map((input, i) => ({
|
|
856
|
+
...input,
|
|
857
|
+
animation: componentAnimations[`input-${i}`] || null
|
|
858
|
+
}))
|
|
859
|
+
},
|
|
860
|
+
|
|
861
|
+
shadows_elevation: {
|
|
862
|
+
detected: true,
|
|
863
|
+
values: shadows.shadows
|
|
864
|
+
},
|
|
865
|
+
|
|
866
|
+
animations_transitions: {
|
|
867
|
+
detected: true,
|
|
868
|
+
keyframes: animations.keyframes,
|
|
869
|
+
transitions: animations.transitions
|
|
870
|
+
},
|
|
871
|
+
|
|
872
|
+
border_styles: {
|
|
873
|
+
detected: true,
|
|
874
|
+
widths: shadows.borderWidths
|
|
875
|
+
},
|
|
876
|
+
|
|
877
|
+
border_radius: {
|
|
878
|
+
detected: true,
|
|
879
|
+
values: shadows.borderRadii
|
|
880
|
+
},
|
|
881
|
+
|
|
882
|
+
opacity_transparency: {
|
|
883
|
+
detected: true,
|
|
884
|
+
values: [0.5, 0.7, 0.9] // Standard
|
|
885
|
+
},
|
|
886
|
+
|
|
887
|
+
z_index_layers: {
|
|
888
|
+
detected: false,
|
|
889
|
+
fallback: 'standard'
|
|
890
|
+
},
|
|
891
|
+
|
|
892
|
+
responsive_breakpoints: {
|
|
893
|
+
detected: false,
|
|
894
|
+
fallback: 'standard'
|
|
895
|
+
},
|
|
896
|
+
|
|
897
|
+
css_variables: {
|
|
898
|
+
generated: true
|
|
899
|
+
},
|
|
900
|
+
|
|
901
|
+
layout_patterns: {
|
|
902
|
+
detected: true,
|
|
903
|
+
container_width: '1280px',
|
|
904
|
+
grid_columns: 12
|
|
905
|
+
},
|
|
906
|
+
|
|
907
|
+
example_components: {
|
|
908
|
+
generated: true
|
|
909
|
+
},
|
|
910
|
+
|
|
911
|
+
additional_sections: {
|
|
912
|
+
accessibility: { detected: true },
|
|
913
|
+
best_practices: { generated: true }
|
|
914
|
+
}
|
|
915
|
+
},
|
|
916
|
+
|
|
917
|
+
animations: componentAnimations
|
|
918
|
+
};
|
|
919
|
+
|
|
920
|
+
const yamlContent = YAML.stringify(yamlData, null, 2);
|
|
921
|
+
Write(`design-system/extracted/${siteName}/data.yaml`, yamlContent);
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
---
|
|
925
|
+
|
|
926
|
+
## STEP 7: Final Report
|
|
927
|
+
|
|
928
|
+
```
|
|
929
|
+
✅ Extraction Complete: ${siteName}
|
|
930
|
+
|
|
931
|
+
📊 Coverage: ${yamlData.meta.coverage.detected_sections}/17 sections (${yamlData.meta.coverage.percentage}%)
|
|
932
|
+
✅ Colors (${colors.backgrounds.length + colors.texts.length + colors.borders.length} total)
|
|
933
|
+
✅ Typography (${typography.allFonts.length} fonts, ${typography.allWeights.length} weights)
|
|
934
|
+
✅ Spacing (${spacing.detectedGrid}px grid detected)
|
|
935
|
+
✅ Components (${buttons.length} buttons, ${cards.length} cards, ${inputs.length} inputs)
|
|
936
|
+
✅ Shadows (${shadows.shadows.length} unique values)
|
|
937
|
+
✅ Animations (${animations.keyframes.length} @keyframes, ${animations.transitions.length} transitions)
|
|
938
|
+
${yamlData.sections.z_index_layers.detected ? '✅' : '❌'} Z-index
|
|
939
|
+
${yamlData.sections.responsive_breakpoints.detected ? '✅' : '❌'} Breakpoints
|
|
940
|
+
|
|
941
|
+
📸 Screenshots: ${Object.keys(componentAnimations).length * 2 + 1} captured
|
|
942
|
+
- full-page.png (or viewport.png)
|
|
943
|
+
- ${Object.keys(componentAnimations).length} components × 2 states
|
|
944
|
+
|
|
945
|
+
📁 Output:
|
|
946
|
+
✓ design-system/extracted/${siteName}/data.yaml
|
|
947
|
+
✓ design-system/extracted/${siteName}/analysis.md
|
|
948
|
+
✓ design-system/extracted/${siteName}/screenshots/ (${Object.keys(componentAnimations).length * 2 + 1} files)
|
|
949
|
+
|
|
950
|
+
⏱️ Time: ${Date.now() - startTime}ms
|
|
951
|
+
|
|
952
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
953
|
+
|
|
954
|
+
🚀 Next Steps:
|
|
955
|
+
|
|
956
|
+
1. Extract more sites:
|
|
957
|
+
/extract https://blackbird.com
|
|
958
|
+
/extract https://linear.app
|
|
959
|
+
|
|
960
|
+
2. Generate style guide:
|
|
961
|
+
/designsetup @prd.md @project.md
|
|
962
|
+
|
|
963
|
+
3. Or review extracted data:
|
|
964
|
+
cat design-system/extracted/${siteName}/analysis.md
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
---
|
|
968
|
+
|
|
969
|
+
## Error Handling
|
|
970
|
+
|
|
971
|
+
```javascript
|
|
972
|
+
try {
|
|
973
|
+
await mcp__chrome-devtools__navigate_page({ url });
|
|
974
|
+
} catch (error) {
|
|
975
|
+
return error(`
|
|
976
|
+
❌ Failed to load URL: ${url}
|
|
977
|
+
|
|
978
|
+
Error: ${error.message}
|
|
979
|
+
|
|
980
|
+
Check:
|
|
981
|
+
- Is the URL accessible?
|
|
982
|
+
- Is Chrome DevTools MCP running?
|
|
983
|
+
- Try with --verbose for details
|
|
984
|
+
`);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// Extraction failures are non-critical
|
|
988
|
+
try {
|
|
989
|
+
const colors = await extractColors();
|
|
990
|
+
} catch (error) {
|
|
991
|
+
console.warn('Color extraction failed:', error.message);
|
|
992
|
+
colors = { backgrounds: [], texts: [], borders: [] };
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
// Screenshot failures are non-critical
|
|
996
|
+
try {
|
|
997
|
+
await takeScreenshot();
|
|
998
|
+
} catch (error) {
|
|
999
|
+
console.warn('Screenshot failed, continuing...');
|
|
1000
|
+
}
|
|
1001
|
+
```
|
|
1002
|
+
|
|
1003
|
+
---
|
|
1004
|
+
|
|
1005
|
+
**Now execute the extraction.**
|