real-prototypes-skill 0.1.0
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/skills/agent-browser-skill/SKILL.md +252 -0
- package/.claude/skills/real-prototypes-skill/.gitignore +188 -0
- package/.claude/skills/real-prototypes-skill/ACCESSIBILITY.md +668 -0
- package/.claude/skills/real-prototypes-skill/INSTALL.md +259 -0
- package/.claude/skills/real-prototypes-skill/LICENSE +21 -0
- package/.claude/skills/real-prototypes-skill/PUBLISH.md +310 -0
- package/.claude/skills/real-prototypes-skill/QUICKSTART.md +240 -0
- package/.claude/skills/real-prototypes-skill/README.md +442 -0
- package/.claude/skills/real-prototypes-skill/SKILL.md +375 -0
- package/.claude/skills/real-prototypes-skill/capture/capture-engine.js +1153 -0
- package/.claude/skills/real-prototypes-skill/capture/config.schema.json +170 -0
- package/.claude/skills/real-prototypes-skill/cli.js +596 -0
- package/.claude/skills/real-prototypes-skill/docs/TROUBLESHOOTING.md +278 -0
- package/.claude/skills/real-prototypes-skill/docs/schemas/capture-config.md +167 -0
- package/.claude/skills/real-prototypes-skill/docs/schemas/design-tokens.md +183 -0
- package/.claude/skills/real-prototypes-skill/docs/schemas/manifest.md +169 -0
- package/.claude/skills/real-prototypes-skill/examples/CLAUDE.md.example +73 -0
- package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/CLAUDE.md +136 -0
- package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/FEATURES.md +222 -0
- package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/README.md +82 -0
- package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/references/design-tokens.json +87 -0
- package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/references/screenshots/homepage-viewport.png +0 -0
- package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/references/screenshots/prototype-chatbot-final.png +0 -0
- package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/references/screenshots/prototype-fullpage-v2.png +0 -0
- package/.claude/skills/real-prototypes-skill/references/accessibility-fixes.md +298 -0
- package/.claude/skills/real-prototypes-skill/references/accessibility-report.json +253 -0
- package/.claude/skills/real-prototypes-skill/scripts/CAPTURE-ENHANCEMENTS.md +344 -0
- package/.claude/skills/real-prototypes-skill/scripts/IMPLEMENTATION-SUMMARY.md +517 -0
- package/.claude/skills/real-prototypes-skill/scripts/QUICK-START.md +229 -0
- package/.claude/skills/real-prototypes-skill/scripts/QUICKSTART-layout-analysis.md +148 -0
- package/.claude/skills/real-prototypes-skill/scripts/README-analyze-layout.md +407 -0
- package/.claude/skills/real-prototypes-skill/scripts/analyze-layout.js +880 -0
- package/.claude/skills/real-prototypes-skill/scripts/capture-platform.js +203 -0
- package/.claude/skills/real-prototypes-skill/scripts/comprehensive-capture.js +597 -0
- package/.claude/skills/real-prototypes-skill/scripts/create-manifest.js +338 -0
- package/.claude/skills/real-prototypes-skill/scripts/enterprise-pipeline.js +428 -0
- package/.claude/skills/real-prototypes-skill/scripts/extract-tokens.js +468 -0
- package/.claude/skills/real-prototypes-skill/scripts/full-site-capture.js +738 -0
- package/.claude/skills/real-prototypes-skill/scripts/generate-tailwind-config.js +296 -0
- package/.claude/skills/real-prototypes-skill/scripts/integrate-accessibility.sh +161 -0
- package/.claude/skills/real-prototypes-skill/scripts/manifest-schema.json +302 -0
- package/.claude/skills/real-prototypes-skill/scripts/setup-prototype.sh +167 -0
- package/.claude/skills/real-prototypes-skill/scripts/test-analyze-layout.js +338 -0
- package/.claude/skills/real-prototypes-skill/scripts/test-validation.js +307 -0
- package/.claude/skills/real-prototypes-skill/scripts/validate-accessibility.js +598 -0
- package/.claude/skills/real-prototypes-skill/scripts/validate-manifest.js +499 -0
- package/.claude/skills/real-prototypes-skill/scripts/validate-output.js +361 -0
- package/.claude/skills/real-prototypes-skill/scripts/validate-prerequisites.js +319 -0
- package/.claude/skills/real-prototypes-skill/scripts/verify-layout-analysis.sh +77 -0
- package/.claude/skills/real-prototypes-skill/templates/dashboard-widget.tsx.template +91 -0
- package/.claude/skills/real-prototypes-skill/templates/data-table.tsx.template +193 -0
- package/.claude/skills/real-prototypes-skill/templates/form-section.tsx.template +250 -0
- package/.claude/skills/real-prototypes-skill/templates/modal-dialog.tsx.template +239 -0
- package/.claude/skills/real-prototypes-skill/templates/nav-item.tsx.template +265 -0
- package/.claude/skills/real-prototypes-skill/validation/validation-engine.js +559 -0
- package/.env.example +74 -0
- package/LICENSE +21 -0
- package/README.md +444 -0
- package/bin/cli.js +319 -0
- package/package.json +59 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Design Token Extractor
|
|
5
|
+
*
|
|
6
|
+
* Parses HTML (and optionally CSS) files to extract design tokens including:
|
|
7
|
+
* - Colors (hex, rgb, rgba, hsl)
|
|
8
|
+
* - Font families
|
|
9
|
+
* - Font sizes
|
|
10
|
+
* - Spacing values (margin, padding)
|
|
11
|
+
* - Border radius values
|
|
12
|
+
*
|
|
13
|
+
* Usage: node extract-tokens.js <html-file> [css-file]
|
|
14
|
+
* Output: JSON object with categorized design tokens
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
// Regular expressions for extracting values
|
|
21
|
+
const PATTERNS = {
|
|
22
|
+
// Color patterns
|
|
23
|
+
hex: /#([0-9a-fA-F]{3,8})\b/g,
|
|
24
|
+
rgb: /rgba?\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})(?:\s*,\s*([\d.]+))?\s*\)/gi,
|
|
25
|
+
hsl: /hsla?\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%(?:\s*,\s*([\d.]+))?\s*\)/gi,
|
|
26
|
+
|
|
27
|
+
// Font patterns
|
|
28
|
+
fontFamily: /font-family\s*:\s*([^;}"']+)/gi,
|
|
29
|
+
fontSize: /font-size\s*:\s*([^;}"']+)/gi,
|
|
30
|
+
|
|
31
|
+
// Spacing patterns
|
|
32
|
+
margin: /margin(?:-(?:top|right|bottom|left))?\s*:\s*([^;}"']+)/gi,
|
|
33
|
+
padding: /padding(?:-(?:top|right|bottom|left))?\s*:\s*([^;}"']+)/gi,
|
|
34
|
+
gap: /gap\s*:\s*([^;}"']+)/gi,
|
|
35
|
+
|
|
36
|
+
// Border radius patterns
|
|
37
|
+
borderRadius: /border-radius\s*:\s*([^;}"']+)/gi,
|
|
38
|
+
|
|
39
|
+
// Style attribute pattern
|
|
40
|
+
styleAttr: /style\s*=\s*["']([^"']+)["']/gi,
|
|
41
|
+
|
|
42
|
+
// Inline style tag content
|
|
43
|
+
styleTag: /<style[^>]*>([\s\S]*?)<\/style>/gi
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Extract all style content from HTML
|
|
48
|
+
*/
|
|
49
|
+
function extractStyles(htmlContent, cssContent = '') {
|
|
50
|
+
let allStyles = cssContent;
|
|
51
|
+
|
|
52
|
+
// Extract inline style tags
|
|
53
|
+
let match;
|
|
54
|
+
while ((match = PATTERNS.styleTag.exec(htmlContent)) !== null) {
|
|
55
|
+
allStyles += '\n' + match[1];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Extract style attributes
|
|
59
|
+
PATTERNS.styleAttr.lastIndex = 0;
|
|
60
|
+
while ((match = PATTERNS.styleAttr.exec(htmlContent)) !== null) {
|
|
61
|
+
allStyles += '\n' + match[1];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return allStyles;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Extract color values from styles
|
|
69
|
+
*/
|
|
70
|
+
function extractColors(styles) {
|
|
71
|
+
const colors = new Set();
|
|
72
|
+
|
|
73
|
+
// Extract hex colors
|
|
74
|
+
let match;
|
|
75
|
+
PATTERNS.hex.lastIndex = 0;
|
|
76
|
+
while ((match = PATTERNS.hex.exec(styles)) !== null) {
|
|
77
|
+
let hex = match[0].toLowerCase();
|
|
78
|
+
// Normalize 3-digit hex to 6-digit
|
|
79
|
+
if (hex.length === 4) {
|
|
80
|
+
hex = '#' + hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3];
|
|
81
|
+
}
|
|
82
|
+
colors.add(hex);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Extract rgb/rgba colors
|
|
86
|
+
PATTERNS.rgb.lastIndex = 0;
|
|
87
|
+
while ((match = PATTERNS.rgb.exec(styles)) !== null) {
|
|
88
|
+
colors.add(match[0].toLowerCase().replace(/\s+/g, ''));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Extract hsl/hsla colors
|
|
92
|
+
PATTERNS.hsl.lastIndex = 0;
|
|
93
|
+
while ((match = PATTERNS.hsl.exec(styles)) !== null) {
|
|
94
|
+
colors.add(match[0].toLowerCase().replace(/\s+/g, ''));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return Array.from(colors);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Extract font families from styles
|
|
102
|
+
*/
|
|
103
|
+
function extractFontFamilies(styles) {
|
|
104
|
+
const fonts = new Set();
|
|
105
|
+
let match;
|
|
106
|
+
|
|
107
|
+
PATTERNS.fontFamily.lastIndex = 0;
|
|
108
|
+
while ((match = PATTERNS.fontFamily.exec(styles)) !== null) {
|
|
109
|
+
const fontValue = match[1].trim();
|
|
110
|
+
// Split by comma and get primary font
|
|
111
|
+
const primaryFont = fontValue.split(',')[0].trim().replace(/["']/g, '');
|
|
112
|
+
if (primaryFont && !primaryFont.startsWith('var(')) {
|
|
113
|
+
fonts.add(primaryFont);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return Array.from(fonts);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Extract font sizes from styles
|
|
122
|
+
*/
|
|
123
|
+
function extractFontSizes(styles) {
|
|
124
|
+
const sizes = new Set();
|
|
125
|
+
let match;
|
|
126
|
+
|
|
127
|
+
PATTERNS.fontSize.lastIndex = 0;
|
|
128
|
+
while ((match = PATTERNS.fontSize.exec(styles)) !== null) {
|
|
129
|
+
const sizeValue = match[1].trim();
|
|
130
|
+
if (!sizeValue.startsWith('var(')) {
|
|
131
|
+
sizes.add(sizeValue);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return Array.from(sizes);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Extract spacing values from styles
|
|
140
|
+
*/
|
|
141
|
+
function extractSpacing(styles) {
|
|
142
|
+
const spacing = new Set();
|
|
143
|
+
let match;
|
|
144
|
+
|
|
145
|
+
// Extract margin values
|
|
146
|
+
PATTERNS.margin.lastIndex = 0;
|
|
147
|
+
while ((match = PATTERNS.margin.exec(styles)) !== null) {
|
|
148
|
+
parseSpacingValue(match[1]).forEach(v => spacing.add(v));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Extract padding values
|
|
152
|
+
PATTERNS.padding.lastIndex = 0;
|
|
153
|
+
while ((match = PATTERNS.padding.exec(styles)) !== null) {
|
|
154
|
+
parseSpacingValue(match[1]).forEach(v => spacing.add(v));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Extract gap values
|
|
158
|
+
PATTERNS.gap.lastIndex = 0;
|
|
159
|
+
while ((match = PATTERNS.gap.exec(styles)) !== null) {
|
|
160
|
+
parseSpacingValue(match[1]).forEach(v => spacing.add(v));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return Array.from(spacing).filter(v => v !== '0' && v !== 'auto' && !v.startsWith('var('));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Parse spacing value (handles shorthand like "10px 20px 10px 20px")
|
|
168
|
+
*/
|
|
169
|
+
function parseSpacingValue(value) {
|
|
170
|
+
const values = value.trim().split(/\s+/);
|
|
171
|
+
return values.filter(v => v && v !== '0' && v !== 'auto' && !v.startsWith('var('));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Extract border radius values from styles
|
|
176
|
+
*/
|
|
177
|
+
function extractBorderRadius(styles) {
|
|
178
|
+
const radii = new Set();
|
|
179
|
+
let match;
|
|
180
|
+
|
|
181
|
+
PATTERNS.borderRadius.lastIndex = 0;
|
|
182
|
+
while ((match = PATTERNS.borderRadius.exec(styles)) !== null) {
|
|
183
|
+
const radiusValue = match[1].trim();
|
|
184
|
+
if (!radiusValue.startsWith('var(')) {
|
|
185
|
+
// Handle shorthand notation
|
|
186
|
+
const values = radiusValue.split(/\s+/);
|
|
187
|
+
values.forEach(v => {
|
|
188
|
+
if (v && v !== '0') {
|
|
189
|
+
radii.add(v);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return Array.from(radii);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Convert numeric value to comparable number (in px)
|
|
200
|
+
*/
|
|
201
|
+
function toPixels(value) {
|
|
202
|
+
if (!value) return 0;
|
|
203
|
+
const num = parseFloat(value);
|
|
204
|
+
if (value.includes('rem')) return num * 16;
|
|
205
|
+
if (value.includes('em')) return num * 16;
|
|
206
|
+
if (value.includes('px')) return num;
|
|
207
|
+
if (value.includes('%')) return num;
|
|
208
|
+
return num;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Sort values by size
|
|
213
|
+
*/
|
|
214
|
+
function sortBySize(values) {
|
|
215
|
+
return [...values].sort((a, b) => toPixels(a) - toPixels(b));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Categorize colors based on lightness/usage patterns
|
|
220
|
+
*/
|
|
221
|
+
function categorizeColors(colors) {
|
|
222
|
+
const result = {};
|
|
223
|
+
|
|
224
|
+
if (colors.length === 0) return result;
|
|
225
|
+
|
|
226
|
+
// Sort colors by luminance
|
|
227
|
+
const sortedColors = [...colors].sort((a, b) => {
|
|
228
|
+
return getLuminance(a) - getLuminance(b);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Assign categories based on position
|
|
232
|
+
if (sortedColors.length >= 1) {
|
|
233
|
+
result.text = sortedColors[0]; // Darkest
|
|
234
|
+
}
|
|
235
|
+
if (sortedColors.length >= 2) {
|
|
236
|
+
result.background = sortedColors[sortedColors.length - 1]; // Lightest
|
|
237
|
+
}
|
|
238
|
+
if (sortedColors.length >= 3) {
|
|
239
|
+
const midIndex = Math.floor(sortedColors.length / 2);
|
|
240
|
+
result.primary = sortedColors[midIndex];
|
|
241
|
+
}
|
|
242
|
+
if (sortedColors.length >= 4) {
|
|
243
|
+
const quarterIndex = Math.floor(sortedColors.length / 4);
|
|
244
|
+
result.secondary = sortedColors[quarterIndex];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Add remaining colors as numbered entries
|
|
248
|
+
sortedColors.forEach((color, index) => {
|
|
249
|
+
if (!Object.values(result).includes(color)) {
|
|
250
|
+
result[`color${index + 1}`] = color;
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Get luminance of a color for sorting
|
|
259
|
+
*/
|
|
260
|
+
function getLuminance(color) {
|
|
261
|
+
let r, g, b;
|
|
262
|
+
|
|
263
|
+
if (color.startsWith('#')) {
|
|
264
|
+
const hex = color.slice(1);
|
|
265
|
+
if (hex.length === 3) {
|
|
266
|
+
r = parseInt(hex[0] + hex[0], 16);
|
|
267
|
+
g = parseInt(hex[1] + hex[1], 16);
|
|
268
|
+
b = parseInt(hex[2] + hex[2], 16);
|
|
269
|
+
} else {
|
|
270
|
+
r = parseInt(hex.slice(0, 2), 16);
|
|
271
|
+
g = parseInt(hex.slice(2, 4), 16);
|
|
272
|
+
b = parseInt(hex.slice(4, 6), 16);
|
|
273
|
+
}
|
|
274
|
+
} else if (color.startsWith('rgb')) {
|
|
275
|
+
const match = color.match(/(\d+)/g);
|
|
276
|
+
if (match) {
|
|
277
|
+
[r, g, b] = match.map(Number);
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
return 0.5; // Default for hsl and others
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Calculate relative luminance
|
|
284
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Categorize fonts
|
|
289
|
+
*/
|
|
290
|
+
function categorizeFonts(fonts) {
|
|
291
|
+
const result = {};
|
|
292
|
+
|
|
293
|
+
if (fonts.length === 0) return result;
|
|
294
|
+
|
|
295
|
+
// Common heading font indicators
|
|
296
|
+
const headingKeywords = ['heading', 'display', 'title', 'serif', 'playfair', 'merriweather', 'georgia'];
|
|
297
|
+
const bodyKeywords = ['body', 'sans', 'inter', 'roboto', 'arial', 'helvetica', 'system'];
|
|
298
|
+
|
|
299
|
+
fonts.forEach(font => {
|
|
300
|
+
const lowerFont = font.toLowerCase();
|
|
301
|
+
|
|
302
|
+
if (!result.heading && headingKeywords.some(kw => lowerFont.includes(kw))) {
|
|
303
|
+
result.heading = font;
|
|
304
|
+
} else if (!result.body && bodyKeywords.some(kw => lowerFont.includes(kw))) {
|
|
305
|
+
result.body = font;
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Fallback assignments
|
|
310
|
+
if (!result.body && fonts.length >= 1) {
|
|
311
|
+
result.body = fonts[0];
|
|
312
|
+
}
|
|
313
|
+
if (!result.heading && fonts.length >= 2) {
|
|
314
|
+
result.heading = fonts[1];
|
|
315
|
+
} else if (!result.heading && fonts.length >= 1) {
|
|
316
|
+
result.heading = fonts[0];
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Add remaining fonts
|
|
320
|
+
fonts.forEach((font, index) => {
|
|
321
|
+
if (!Object.values(result).includes(font)) {
|
|
322
|
+
result[`font${index + 1}`] = font;
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Categorize spacing values
|
|
331
|
+
*/
|
|
332
|
+
function categorizeSpacing(spacingValues) {
|
|
333
|
+
const result = {};
|
|
334
|
+
|
|
335
|
+
if (spacingValues.length === 0) return result;
|
|
336
|
+
|
|
337
|
+
const sorted = sortBySize(spacingValues);
|
|
338
|
+
const categories = ['xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl', '4xl'];
|
|
339
|
+
|
|
340
|
+
// Distribute values across categories
|
|
341
|
+
const step = Math.max(1, Math.ceil(sorted.length / categories.length));
|
|
342
|
+
|
|
343
|
+
sorted.forEach((value, index) => {
|
|
344
|
+
const categoryIndex = Math.min(Math.floor(index / step), categories.length - 1);
|
|
345
|
+
const category = categories[categoryIndex];
|
|
346
|
+
if (!result[category]) {
|
|
347
|
+
result[category] = value;
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
return result;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Categorize border radius values
|
|
356
|
+
*/
|
|
357
|
+
function categorizeBorderRadius(radii) {
|
|
358
|
+
const result = {};
|
|
359
|
+
|
|
360
|
+
if (radii.length === 0) return result;
|
|
361
|
+
|
|
362
|
+
const sorted = sortBySize(radii);
|
|
363
|
+
const categories = ['sm', 'md', 'lg', 'xl', 'full'];
|
|
364
|
+
|
|
365
|
+
// Check for full/rounded values
|
|
366
|
+
const fullIndex = sorted.findIndex(v =>
|
|
367
|
+
v === '50%' || v === '9999px' || v === '100%' || parseFloat(v) >= 999
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
if (fullIndex !== -1) {
|
|
371
|
+
result.full = sorted[fullIndex];
|
|
372
|
+
sorted.splice(fullIndex, 1);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Distribute remaining values
|
|
376
|
+
const remainingCategories = categories.filter(c => c !== 'full');
|
|
377
|
+
const step = Math.max(1, Math.ceil(sorted.length / remainingCategories.length));
|
|
378
|
+
|
|
379
|
+
sorted.forEach((value, index) => {
|
|
380
|
+
const categoryIndex = Math.min(Math.floor(index / step), remainingCategories.length - 1);
|
|
381
|
+
const category = remainingCategories[categoryIndex];
|
|
382
|
+
if (!result[category]) {
|
|
383
|
+
result[category] = value;
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
return result;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Main extraction function
|
|
392
|
+
*/
|
|
393
|
+
function extractTokens(htmlPath, cssPath = null) {
|
|
394
|
+
// Read HTML file
|
|
395
|
+
if (!fs.existsSync(htmlPath)) {
|
|
396
|
+
console.error(`Error: HTML file not found: ${htmlPath}`);
|
|
397
|
+
process.exit(1);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const htmlContent = fs.readFileSync(htmlPath, 'utf8');
|
|
401
|
+
|
|
402
|
+
// Read CSS file if provided
|
|
403
|
+
let cssContent = '';
|
|
404
|
+
if (cssPath) {
|
|
405
|
+
if (!fs.existsSync(cssPath)) {
|
|
406
|
+
console.error(`Warning: CSS file not found: ${cssPath}`);
|
|
407
|
+
} else {
|
|
408
|
+
cssContent = fs.readFileSync(cssPath, 'utf8');
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Extract all styles
|
|
413
|
+
const allStyles = extractStyles(htmlContent, cssContent);
|
|
414
|
+
|
|
415
|
+
// Extract raw values
|
|
416
|
+
const rawColors = extractColors(allStyles);
|
|
417
|
+
const rawFonts = extractFontFamilies(allStyles);
|
|
418
|
+
const rawFontSizes = extractFontSizes(allStyles);
|
|
419
|
+
const rawSpacing = extractSpacing(allStyles);
|
|
420
|
+
const rawBorderRadius = extractBorderRadius(allStyles);
|
|
421
|
+
|
|
422
|
+
// Build tokens object
|
|
423
|
+
const tokens = {
|
|
424
|
+
colors: categorizeColors(rawColors),
|
|
425
|
+
fonts: categorizeFonts(rawFonts),
|
|
426
|
+
fontSizes: categorizeSpacing(rawFontSizes),
|
|
427
|
+
spacing: categorizeSpacing(rawSpacing),
|
|
428
|
+
borderRadius: categorizeBorderRadius(rawBorderRadius)
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
// Add raw values for reference
|
|
432
|
+
tokens._raw = {
|
|
433
|
+
colors: rawColors,
|
|
434
|
+
fonts: rawFonts,
|
|
435
|
+
fontSizes: rawFontSizes,
|
|
436
|
+
spacing: rawSpacing,
|
|
437
|
+
borderRadius: rawBorderRadius
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
return tokens;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// CLI execution
|
|
444
|
+
if (require.main === module) {
|
|
445
|
+
const args = process.argv.slice(2);
|
|
446
|
+
|
|
447
|
+
if (args.length === 0) {
|
|
448
|
+
console.log('Usage: node extract-tokens.js <html-file> [css-file]');
|
|
449
|
+
console.log('');
|
|
450
|
+
console.log('Extracts design tokens from HTML and CSS files.');
|
|
451
|
+
console.log('');
|
|
452
|
+
console.log('Arguments:');
|
|
453
|
+
console.log(' html-file Path to the HTML file to parse');
|
|
454
|
+
console.log(' css-file Optional path to additional CSS file');
|
|
455
|
+
console.log('');
|
|
456
|
+
console.log('Output: JSON object with categorized design tokens');
|
|
457
|
+
process.exit(0);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const htmlPath = path.resolve(args[0]);
|
|
461
|
+
const cssPath = args[1] ? path.resolve(args[1]) : null;
|
|
462
|
+
|
|
463
|
+
const tokens = extractTokens(htmlPath, cssPath);
|
|
464
|
+
|
|
465
|
+
console.log(JSON.stringify(tokens, null, 2));
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
module.exports = { extractTokens };
|