skillui 1.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.
Files changed (59) hide show
  1. package/dist/cli.d.ts +3 -0
  2. package/dist/cli.js +202 -0
  3. package/dist/extractors/components.d.ts +11 -0
  4. package/dist/extractors/components.js +455 -0
  5. package/dist/extractors/framework.d.ts +4 -0
  6. package/dist/extractors/framework.js +126 -0
  7. package/dist/extractors/tokens/computed.d.ts +7 -0
  8. package/dist/extractors/tokens/computed.js +249 -0
  9. package/dist/extractors/tokens/css.d.ts +3 -0
  10. package/dist/extractors/tokens/css.js +510 -0
  11. package/dist/extractors/tokens/http-css.d.ts +14 -0
  12. package/dist/extractors/tokens/http-css.js +1689 -0
  13. package/dist/extractors/tokens/tailwind.d.ts +3 -0
  14. package/dist/extractors/tokens/tailwind.js +353 -0
  15. package/dist/extractors/tokens/tokens-file.d.ts +3 -0
  16. package/dist/extractors/tokens/tokens-file.js +229 -0
  17. package/dist/extractors/ultra/animations.d.ts +21 -0
  18. package/dist/extractors/ultra/animations.js +530 -0
  19. package/dist/extractors/ultra/components-dom.d.ts +13 -0
  20. package/dist/extractors/ultra/components-dom.js +152 -0
  21. package/dist/extractors/ultra/interactions.d.ts +14 -0
  22. package/dist/extractors/ultra/interactions.js +225 -0
  23. package/dist/extractors/ultra/layout.d.ts +14 -0
  24. package/dist/extractors/ultra/layout.js +126 -0
  25. package/dist/extractors/ultra/pages.d.ts +16 -0
  26. package/dist/extractors/ultra/pages.js +231 -0
  27. package/dist/font-resolver.d.ts +10 -0
  28. package/dist/font-resolver.js +280 -0
  29. package/dist/modes/dir.d.ts +6 -0
  30. package/dist/modes/dir.js +213 -0
  31. package/dist/modes/repo.d.ts +6 -0
  32. package/dist/modes/repo.js +76 -0
  33. package/dist/modes/ultra.d.ts +22 -0
  34. package/dist/modes/ultra.js +285 -0
  35. package/dist/modes/url.d.ts +14 -0
  36. package/dist/modes/url.js +161 -0
  37. package/dist/normalizer.d.ts +11 -0
  38. package/dist/normalizer.js +867 -0
  39. package/dist/screenshot.d.ts +9 -0
  40. package/dist/screenshot.js +94 -0
  41. package/dist/types-ultra.d.ts +157 -0
  42. package/dist/types-ultra.js +4 -0
  43. package/dist/types.d.ts +182 -0
  44. package/dist/types.js +4 -0
  45. package/dist/writers/animations-md.d.ts +17 -0
  46. package/dist/writers/animations-md.js +313 -0
  47. package/dist/writers/components-md.d.ts +8 -0
  48. package/dist/writers/components-md.js +151 -0
  49. package/dist/writers/design-md.d.ts +7 -0
  50. package/dist/writers/design-md.js +704 -0
  51. package/dist/writers/interactions-md.d.ts +8 -0
  52. package/dist/writers/interactions-md.js +146 -0
  53. package/dist/writers/layout-md.d.ts +8 -0
  54. package/dist/writers/layout-md.js +120 -0
  55. package/dist/writers/skill.d.ts +12 -0
  56. package/dist/writers/skill.js +1006 -0
  57. package/dist/writers/tokens-json.d.ts +11 -0
  58. package/dist/writers/tokens-json.js +164 -0
  59. package/package.json +78 -0
@@ -0,0 +1,704 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateDesignMd = generateDesignMd;
4
+ /**
5
+ * Generate DESIGN.md in Google Stitch / awesome-design-md format.
6
+ * Pure template-driven — no AI.
7
+ */
8
+ function generateDesignMd(profile, screenshotPath) {
9
+ const sections = [
10
+ generateHeader(profile, screenshotPath),
11
+ generateVisualTheme(profile),
12
+ generateColorPalette(profile),
13
+ generateTypography(profile),
14
+ generateComponentStylings(profile),
15
+ generateLayoutPrinciples(profile),
16
+ generateDepthElevation(profile),
17
+ generateAnimationMotion(profile),
18
+ generateDosAndDonts(profile),
19
+ generateResponsiveBehavior(profile),
20
+ generateAgentPromptGuide(profile),
21
+ ];
22
+ return sections.filter(s => s).join('\n\n---\n\n');
23
+ }
24
+ // ── Header ────────────────────────────────────────────────────────────
25
+ function generateHeader(profile, screenshotPath) {
26
+ const frameworkList = profile.frameworks.map(f => f.version ? `${f.name} ${f.version}` : f.name).join(' + ');
27
+ const fonts = [...new Set(profile.typography.map(t => t.fontFamily))].filter(f => f);
28
+ let header = `# ${profile.projectName} DESIGN.md
29
+
30
+ > Auto-generated design system — reverse-engineered via static analysis by skillui.
31
+ > Frameworks: ${frameworkList || 'None detected'}
32
+ > Colors: ${profile.colors.length} · Fonts: ${fonts.length} · Components: ${profile.components.length}
33
+ > Icon library: ${profile.iconLibrary || 'not detected'} · State: ${profile.stateLibrary || 'not detected'}
34
+ > Primary theme: ${profile.designTraits.isDark ? 'dark' : 'light'} · Dark mode toggle: ${profile.designTraits.hasDarkMode ? 'yes' : 'no'} · Motion: ${profile.designTraits.motionStyle}`;
35
+ if (screenshotPath) {
36
+ header += `\n\n## Visual Reference\n\n**Match this design exactly** — study colors, fonts, spacing, and component shapes before writing any UI code.\n\n![${profile.projectName} Homepage](../${screenshotPath})`;
37
+ }
38
+ return header;
39
+ }
40
+ // ── Section 1: Visual Theme & Atmosphere ──────────────────────────────
41
+ function generateVisualTheme(profile) {
42
+ const traits = profile.designTraits;
43
+ const bg = profile.colors.find(c => c.role === 'background');
44
+ const accent = profile.colors.find(c => c.role === 'accent');
45
+ const fonts = [...new Set(profile.typography.map(t => t.fontFamily))].filter(f => f);
46
+ const isMono = (f) => /mono|consolas|courier|fira code|jetbrains|sf mono|menlo/i.test(f);
47
+ const displayFonts = fonts.filter(f => !isMono(f));
48
+ const primaryFont = displayFonts[0] || fonts[0] || 'sans-serif';
49
+ const theme = traits.isDark ? 'dark' : 'light';
50
+ const lines = [];
51
+ // Overall vibe
52
+ if (traits.isDark && !traits.hasShadows) {
53
+ lines.push(`This is a **${theme}-themed** interface with a flat, ${traits.primaryColorTemp} visual language. Elevation is achieved through color and border shifts rather than shadows — a clean, industrial aesthetic.`);
54
+ }
55
+ else if (traits.isDark) {
56
+ lines.push(`This is a **${theme}-themed** interface with a ${traits.primaryColorTemp} tone. Depth is expressed through layered shadows and subtle surface color variation.`);
57
+ }
58
+ else {
59
+ lines.push(`This is a **${theme}-themed** interface with a ${traits.primaryColorTemp}, approachable feel. The light background emphasizes content clarity.`);
60
+ }
61
+ // Typography character
62
+ if (displayFonts.length >= 2) {
63
+ const displayFont = displayFonts.find(f => f !== primaryFont) || displayFonts[1];
64
+ lines.push(`Typography pairs **${displayFont}** for display/headings with **${primaryFont}** for body text, creating clear visual hierarchy through type contrast.`);
65
+ }
66
+ else {
67
+ const fontDesc = traits.fontStyle === 'monospace' ? 'technical, developer-focused'
68
+ : traits.fontStyle === 'serif' ? 'editorial, refined'
69
+ : 'clean, modern';
70
+ lines.push(`Typography uses **${primaryFont}** throughout — a ${fontDesc} choice that maintains consistency.`);
71
+ }
72
+ // Spacing + density
73
+ lines.push(`Spacing follows a **${profile.spacing.base}px base grid** (${traits.density} density), with scale: ${profile.spacing.values.slice(0, 8).join(', ')}px.`);
74
+ // Accent + color strategy
75
+ if (accent) {
76
+ const colorCount = profile.colors.filter(c => c.role !== 'unknown').length;
77
+ const neutralCount = profile.colors.filter(c => c.role === 'background' || c.role === 'surface' || c.role === 'text-primary' || c.role === 'text-muted' || c.role === 'border').length;
78
+ if (neutralCount >= 3 && colorCount - neutralCount <= 3) {
79
+ lines.push(`The palette is predominantly monochromatic with **${accent.hex}** as the single accent color — used sparingly for interactive elements and emphasis.`);
80
+ }
81
+ else {
82
+ lines.push(`The accent color **${accent.hex}** anchors interactive elements (buttons, links, focus rings).`);
83
+ }
84
+ }
85
+ // Motion summary
86
+ if (traits.motionStyle === 'expressive') {
87
+ lines.push('Motion is expressive — spring physics, layout animations, and staggered reveals are part of the visual language.');
88
+ }
89
+ else if (traits.motionStyle === 'subtle') {
90
+ lines.push('Motion is subtle — smooth transitions (150-300ms) ease state changes without drawing attention.');
91
+ }
92
+ return `## 1. Visual Theme & Atmosphere
93
+
94
+ ${lines.join(' ')}`;
95
+ }
96
+ // ── Section 2: Color Palette & Roles ──────────────────────────────────
97
+ function generateColorPalette(profile) {
98
+ if (profile.colors.length === 0) {
99
+ return `## 2. Color Palette & Roles
100
+
101
+ No colors detected in the project.`;
102
+ }
103
+ const roleOrder = ['background', 'surface', 'text-primary', 'text-muted', 'border', 'accent', 'danger', 'success', 'warning', 'info', 'unknown'];
104
+ const sorted = [...profile.colors].sort((a, b) => {
105
+ const ai = roleOrder.indexOf(a.role);
106
+ const bi = roleOrder.indexOf(b.role);
107
+ return ai - bi;
108
+ });
109
+ let table = '| Token | Hex | Role | Use |\n|---|---|---|---|\n';
110
+ for (const color of sorted) {
111
+ const name = color.name || color.role;
112
+ const roleDesc = getColorRoleDescription(color.role);
113
+ table += `| ${name} | \`${color.hex}\` | ${color.role} | ${roleDesc} |\n`;
114
+ }
115
+ // Dark/light mode variable mapping
116
+ let modeSection = '';
117
+ if (profile.darkModeVars.length > 0) {
118
+ modeSection = '\n### Dark Mode Token Mapping\n\n';
119
+ modeSection += '| Variable | Light | Dark |\n|---|---|---|\n';
120
+ for (const v of profile.darkModeVars.slice(0, 20)) {
121
+ modeSection += `| \`${v.variable}\` | \`${v.lightValue}\` | \`${v.darkValue}\` |\n`;
122
+ }
123
+ }
124
+ // CSS variable tokens
125
+ const modeVars = profile.cssVariables.filter(v => /foreground|background|primary|secondary|muted|accent|destructive|border|card|popover/i.test(v.name) &&
126
+ v.value.length < 40);
127
+ let cssVarSection = '';
128
+ if (modeVars.length > 0) {
129
+ cssVarSection = '\n### CSS Variable Tokens\n\n';
130
+ cssVarSection += '```css\n';
131
+ for (const v of modeVars.slice(0, 20)) {
132
+ cssVarSection += `${v.name}: ${v.value};\n`;
133
+ }
134
+ cssVarSection += '```\n';
135
+ }
136
+ return `## 2. Color Palette & Roles
137
+
138
+ ${table}${modeSection}${cssVarSection}`;
139
+ }
140
+ function getColorRoleDescription(role) {
141
+ const descriptions = {
142
+ 'background': 'Page background, darkest surface',
143
+ 'surface': 'Card and panel backgrounds',
144
+ 'text-primary': 'Headings and body text',
145
+ 'text-muted': 'Captions, placeholders, secondary info',
146
+ 'accent': 'CTAs, links, focus rings, active states',
147
+ 'border': 'Dividers, card borders, outlines',
148
+ 'danger': 'Error states, destructive actions',
149
+ 'success': 'Success states, positive indicators',
150
+ 'warning': 'Warning states, caution indicators',
151
+ 'info': 'Informational highlights',
152
+ 'unknown': 'Palette color',
153
+ };
154
+ return descriptions[role] || 'Palette color';
155
+ }
156
+ // ── Section 3: Typography Rules ───────────────────────────────────────
157
+ function generateTypography(profile) {
158
+ if (profile.typography.length === 0) {
159
+ return `## 3. Typography Rules
160
+
161
+ No typography tokens detected.`;
162
+ }
163
+ const fonts = [...new Set(profile.typography.map(t => t.fontFamily))].filter(f => f);
164
+ let fontStack = '';
165
+ if (fonts.length > 0) {
166
+ fontStack = '**Font Stack:**\n';
167
+ for (const font of fonts) {
168
+ const roles = profile.typography.filter(t => t.fontFamily === font).map(t => formatTypographyRole(t.role));
169
+ fontStack += `- **${font}** — ${roles.join(', ')}\n`;
170
+ }
171
+ fontStack += '\n';
172
+ }
173
+ // Font source declarations
174
+ let fontSources = '';
175
+ if (profile.fontSources.length > 0) {
176
+ fontSources = '**Font Sources:**\n\n```css\n';
177
+ const families = [...new Set(profile.fontSources.map(fs => fs.family))];
178
+ // Collect weights actually used in the type scale
179
+ const usedWeights = new Set(profile.typography.map(t => t.fontWeight || '400'));
180
+ usedWeights.add('400');
181
+ usedWeights.add('700');
182
+ for (const family of families) {
183
+ const sources = profile.fontSources.filter(fs => fs.family === family);
184
+ // Check for variable fonts first
185
+ const variableSource = sources.find(s => s.weight === 'variable');
186
+ if (variableSource) {
187
+ fontSources += `@font-face {\n font-family: "${family}";\n src: url("${variableSource.src}")${variableSource.format ? ` format("${variableSource.format}")` : ''};\n font-weight: 100 900;\n}\n`;
188
+ }
189
+ else {
190
+ const byWeight = new Map();
191
+ for (const src of sources) {
192
+ const w = src.weight || '400';
193
+ if (!usedWeights.has(w))
194
+ continue;
195
+ const existing = byWeight.get(w);
196
+ if (!existing || (src.format === 'woff2' && existing.format !== 'woff2')) {
197
+ byWeight.set(w, src);
198
+ }
199
+ }
200
+ if (byWeight.size === 0 && sources.length > 0) {
201
+ byWeight.set(sources[0].weight || '400', sources[0]);
202
+ }
203
+ for (const [weight, src] of byWeight) {
204
+ fontSources += `@font-face {\n font-family: "${family}";\n src: url("${src.src}")${src.format ? ` format("${src.format}")` : ''};\n font-weight: ${weight};\n}\n`;
205
+ }
206
+ }
207
+ }
208
+ fontSources += '```\n\n';
209
+ }
210
+ let table = '| Role | Font | Size | Weight |\n|---|---|---|---|\n';
211
+ for (const t of profile.typography) {
212
+ const roleName = formatTypographyRole(t.role);
213
+ table += `| ${roleName} | ${t.fontFamily} | ${t.fontSize || 'inherit'} | ${t.fontWeight || 'inherit'} |\n`;
214
+ }
215
+ const isMono = (f) => /mono|consolas|courier|fira code|jetbrains|sf mono|menlo/i.test(f);
216
+ const displayFonts = fonts.filter(f => !isMono(f));
217
+ let rules = '\n**Typographic Rules:**\n';
218
+ if (displayFonts.length <= 1) {
219
+ rules += `- Use **${displayFonts[0] || fonts[0]}** for all text — do not mix font families\n`;
220
+ }
221
+ else {
222
+ rules += `- Limit to ${fonts.length} font families max per screen\n`;
223
+ rules += `- Use **${displayFonts[0]}** for body/UI text, **${displayFonts[1]}** for display/headings\n`;
224
+ }
225
+ rules += '- Maintain consistent hierarchy: no more than 3-4 font sizes per screen\n';
226
+ rules += '- Headings use bold (600-700), body uses regular (400)\n';
227
+ rules += '- Line height: 1.5 for body text, 1.2 for headings\n';
228
+ rules += '- Use color and opacity for secondary hierarchy, not additional font sizes\n';
229
+ return `## 3. Typography Rules
230
+
231
+ ${fontStack}${fontSources}${table}${rules}`;
232
+ }
233
+ function formatTypographyRole(role) {
234
+ return role.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
235
+ }
236
+ // ── Section 4: Component Stylings ─────────────────────────────────────
237
+ function generateComponentStylings(profile) {
238
+ if (profile.components.length === 0) {
239
+ return `## 4. Component Stylings
240
+
241
+ No components detected. Scan \`src/components/\` or \`components/\` to populate this section.`;
242
+ }
243
+ let content = '';
244
+ // Group by category
245
+ const categories = ['layout', 'navigation', 'data-display', 'data-input', 'feedback', 'overlay', 'typography', 'media', 'other'];
246
+ const categoryNames = {
247
+ 'layout': 'Layout',
248
+ 'navigation': 'Navigation',
249
+ 'data-display': 'Data Display',
250
+ 'data-input': 'Data Input',
251
+ 'feedback': 'Feedback',
252
+ 'overlay': 'Overlay',
253
+ 'typography': 'Typography',
254
+ 'media': 'Media',
255
+ 'other': 'Other',
256
+ };
257
+ for (const cat of categories) {
258
+ const comps = profile.components.filter(c => c.category === cat);
259
+ if (comps.length === 0)
260
+ continue;
261
+ content += `### ${categoryNames[cat]} (${comps.length})\n\n`;
262
+ for (const comp of comps.slice(0, 8)) {
263
+ content += `**${comp.name}** — \`${comp.filePath}\`\n`;
264
+ if (comp.variants.length > 0) {
265
+ content += `- Variants: ${comp.variants.map(v => `\`${v}\``).join(', ')}\n`;
266
+ }
267
+ if (comp.props.length > 0) {
268
+ content += `- Props: ${comp.props.slice(0, 8).map(p => `\`${p}\``).join(', ')}`;
269
+ if (comp.props.length > 8)
270
+ content += ` (+${comp.props.length - 8} more)`;
271
+ content += '\n';
272
+ }
273
+ const visualProps = extractVisualProperties(comp);
274
+ if (visualProps.length > 0) {
275
+ content += `- Key Styles: ${visualProps.join(', ')}\n`;
276
+ }
277
+ if (comp.hasAnimation) {
278
+ content += `- Animation: ${comp.animationDetails.slice(0, 3).join(', ')}\n`;
279
+ }
280
+ if (comp.statePatterns.length > 0) {
281
+ content += `- State: ${comp.statePatterns.join(', ')}\n`;
282
+ }
283
+ // Show JSX snippet (compact)
284
+ if (comp.jsxSnippet) {
285
+ const cleanSnippet = comp.jsxSnippet.split('\n').slice(0, 12).join('\n').trim();
286
+ if (cleanSnippet) {
287
+ content += `\n\`\`\`tsx\n${cleanSnippet}\n\`\`\`\n`;
288
+ }
289
+ }
290
+ content += '\n';
291
+ }
292
+ if (comps.length > 8) {
293
+ content += `*...and ${comps.length - 8} more ${categoryNames[cat].toLowerCase()} components.*\n\n`;
294
+ }
295
+ }
296
+ return `## 4. Component Stylings
297
+
298
+ ${content}`;
299
+ }
300
+ function extractVisualProperties(comp) {
301
+ const props = [];
302
+ const tp = comp.tailwindPatterns;
303
+ // Pick the most representative class from each category
304
+ if (tp.borders.length > 0) {
305
+ const rounded = tp.borders.find(c => c.startsWith('rounded'));
306
+ if (rounded)
307
+ props.push(`\`${rounded}\``);
308
+ const borderColor = tp.borders.find(c => c.startsWith('border-') && !c.startsWith('border-t') && !c.startsWith('border-b'));
309
+ if (borderColor)
310
+ props.push(`\`${borderColor}\``);
311
+ }
312
+ if (tp.backgrounds.length > 0) {
313
+ props.push(`\`${tp.backgrounds[0]}\``);
314
+ }
315
+ if (tp.spacing.length > 0) {
316
+ props.push(`\`${tp.spacing[0]}\``);
317
+ }
318
+ if (tp.typography.length > 0) {
319
+ const textSize = tp.typography.find(c => /text-(xs|sm|base|lg|xl|2xl|3xl|4xl)/.test(c));
320
+ if (textSize)
321
+ props.push(`\`${textSize}\``);
322
+ const font = tp.typography.find(c => c.startsWith('font-'));
323
+ if (font)
324
+ props.push(`\`${font}\``);
325
+ }
326
+ if (tp.effects.length > 0) {
327
+ props.push(`\`${tp.effects[0]}\``);
328
+ }
329
+ if (tp.interactive.length > 0) {
330
+ props.push(`\`${tp.interactive[0]}\``);
331
+ }
332
+ return props;
333
+ }
334
+ // ── Section 5: Layout Principles ──────────────────────────────────────
335
+ function generateLayoutPrinciples(profile) {
336
+ const sp = profile.spacing;
337
+ const scaleStr = sp.values.slice(0, 12).join(', ');
338
+ let content = `- **Base spacing unit:** ${sp.base}px\n`;
339
+ content += `- **Spacing scale:** ${scaleStr}\n`;
340
+ // Border radius
341
+ if (profile.borderRadius.length > 0) {
342
+ const radii = profile.borderRadius.filter(r => !r.includes('9999'));
343
+ if (radii.length > 0) {
344
+ content += `- **Border radius:** ${radii.join(', ')}\n`;
345
+ }
346
+ }
347
+ // Container
348
+ if (profile.containerMaxWidth) {
349
+ content += `- **Max content width:** ${profile.containerMaxWidth}\n`;
350
+ }
351
+ // Grid usage from component classes
352
+ const gridClasses = new Set();
353
+ for (const comp of profile.components) {
354
+ for (const cls of comp.tailwindPatterns.layout) {
355
+ if (/grid-cols-\d+|col-span|columns-\d+/.test(cls)) {
356
+ gridClasses.add(cls);
357
+ }
358
+ }
359
+ }
360
+ if (gridClasses.size > 0) {
361
+ content += `- **Grid usage:** ${Array.from(gridClasses).slice(0, 5).map(c => `\`${c}\``).join(', ')}\n`;
362
+ }
363
+ const hasTailwind = profile.frameworks.some(f => f.id === 'tailwind');
364
+ if (hasTailwind) {
365
+ content += `- **Container:** Tailwind \`container\` class with responsive padding\n`;
366
+ }
367
+ // Spacing philosophy
368
+ content += `\n**Spacing as Meaning:**\n`;
369
+ content += `| Spacing | Use |\n|---|---|\n`;
370
+ if (sp.base <= 4) {
371
+ content += `| ${sp.base}-${sp.base * 2}px | Tight: related items within a group |\n`;
372
+ content += `| ${sp.base * 3}-${sp.base * 4}px | Medium: between groups |\n`;
373
+ content += `| ${sp.base * 6}-${sp.base * 8}px | Wide: between sections |\n`;
374
+ content += `| ${sp.base * 12}px+ | Vast: major section breaks |\n`;
375
+ }
376
+ else {
377
+ content += `| ${sp.base / 2}-${sp.base}px | Tight: related items within a group |\n`;
378
+ content += `| ${sp.base * 2}px | Medium: between groups |\n`;
379
+ content += `| ${sp.base * 3}-${sp.base * 4}px | Wide: between sections |\n`;
380
+ content += `| ${sp.base * 6}px+ | Vast: major section breaks |\n`;
381
+ }
382
+ return `## 5. Layout Principles
383
+
384
+ ${content}`;
385
+ }
386
+ // ── Section 6: Depth & Elevation ──────────────────────────────────────
387
+ function generateDepthElevation(profile) {
388
+ if (profile.shadows.length === 0) {
389
+ let content = '';
390
+ if (profile.designTraits.isDark) {
391
+ content += 'No box-shadow values detected. The design uses a **flat visual style** — elevation is conveyed through background color shifts and borders rather than shadows.\n\n';
392
+ content += '**Elevation Strategy:**\n';
393
+ content += '| Level | Technique | Use |\n|---|---|---|\n';
394
+ content += '| 0 — Base | Background color | Page background |\n';
395
+ content += '| 1 — Raised | Lighter surface + subtle border | Cards, panels |\n';
396
+ content += '| 2 — Floating | Even lighter surface + stronger border | Dropdowns, popovers |\n';
397
+ content += '| 3 — Overlay | Backdrop + modal surface | Modals, dialogs |\n';
398
+ }
399
+ else {
400
+ content += 'No box-shadow values detected. The design appears to use a flat visual style.\n';
401
+ }
402
+ // z-index
403
+ if (profile.zIndexScale.length > 0) {
404
+ content += `\n**Z-Index Scale:** \`${profile.zIndexScale.join(', ')}\`\n`;
405
+ }
406
+ return `## 6. Depth & Elevation
407
+
408
+ ${content}`;
409
+ }
410
+ let content = '';
411
+ const levels = {};
412
+ for (const shadow of profile.shadows) {
413
+ if (!levels[shadow.level])
414
+ levels[shadow.level] = [];
415
+ levels[shadow.level].push(shadow);
416
+ }
417
+ const levelDescriptions = {
418
+ flat: 'Flat — subtle depth hints',
419
+ raised: 'Raised — cards, buttons, interactive elements',
420
+ floating: 'Floating — dropdowns, popovers, modals',
421
+ overlay: 'Overlay — full-screen overlays, top-level dialogs',
422
+ };
423
+ for (const [level, shadows] of Object.entries(levels)) {
424
+ content += `### ${levelDescriptions[level] || level}\n\n`;
425
+ for (const s of shadows.slice(0, 3)) {
426
+ const label = s.name ? `**${s.name}:** ` : '';
427
+ content += `- ${label}\`${s.value}\`\n`;
428
+ }
429
+ content += '\n';
430
+ }
431
+ // z-index
432
+ if (profile.zIndexScale.length > 0) {
433
+ content += `### Z-Index Scale\n\n`;
434
+ content += `\`${profile.zIndexScale.join(', ')}\`\n\n`;
435
+ }
436
+ return `## 6. Depth & Elevation
437
+
438
+ ${content}`;
439
+ }
440
+ // ── Section 7: Animation & Motion ─────────────────────────────────────
441
+ function generateAnimationMotion(profile) {
442
+ if (!profile.designTraits.hasAnimations && profile.designTraits.motionStyle === 'none') {
443
+ return ''; // Skip section entirely
444
+ }
445
+ let content = '';
446
+ if (profile.designTraits.motionStyle === 'expressive') {
447
+ content += 'This project uses **expressive motion**. Animations are an integral part of the experience.\n\n';
448
+ }
449
+ else {
450
+ content += 'This project uses **subtle motion**. Transitions smooth state changes without demanding attention.\n\n';
451
+ }
452
+ // Framer Motion
453
+ const hasFramerMotion = profile.animations.some(a => a.type === 'framer-motion');
454
+ if (hasFramerMotion) {
455
+ content += '### Framer Motion Patterns\n\n';
456
+ content += '```tsx\n';
457
+ content += '// Standard enter animation\n';
458
+ content += '<motion.div\n';
459
+ content += ' initial={{ opacity: 0, y: 8 }}\n';
460
+ content += ' animate={{ opacity: 1, y: 0 }}\n';
461
+ content += ' transition={{ duration: 0.3, ease: "easeOut" }}\n';
462
+ content += '/>\n\n';
463
+ content += '// List stagger\n';
464
+ content += 'const container = {\n';
465
+ content += ' hidden: {},\n';
466
+ content += ' show: { transition: { staggerChildren: 0.05 } }\n';
467
+ content += '}\n';
468
+ content += 'const item = {\n';
469
+ content += ' hidden: { opacity: 0, y: 8 },\n';
470
+ content += ' show: { opacity: 1, y: 0 }\n';
471
+ content += '}\n';
472
+ content += '```\n\n';
473
+ }
474
+ // CSS keyframes
475
+ const keyframes = profile.animations.filter(a => a.type === 'css-keyframe');
476
+ if (keyframes.length > 0) {
477
+ content += '### CSS Animations\n\n';
478
+ for (const kf of keyframes.slice(0, 8)) {
479
+ content += `- \`@keyframes ${kf.name}\`\n`;
480
+ }
481
+ content += '\n';
482
+ }
483
+ // Animation components
484
+ const animatedComps = profile.components.filter(c => c.hasAnimation).slice(0, 5);
485
+ if (animatedComps.length > 0) {
486
+ content += '### Animated Components\n\n';
487
+ for (const comp of animatedComps) {
488
+ content += `- **${comp.name}**: ${comp.animationDetails.slice(0, 3).join(', ')}\n`;
489
+ }
490
+ content += '\n';
491
+ }
492
+ content += '### Motion Guidelines\n\n';
493
+ content += '- Duration: 150-300ms for micro-interactions, 300-500ms for page transitions\n';
494
+ content += '- Easing: `ease-out` for enters, `ease-in` for exits\n';
495
+ content += '- Always respect `prefers-reduced-motion`\n';
496
+ return `## 7. Animation & Motion
497
+
498
+ ${content}`;
499
+ }
500
+ // ── Section 8: Do's and Don'ts ────────────────────────────────────────
501
+ function generateDosAndDonts(profile) {
502
+ const dos = [];
503
+ const donts = [];
504
+ const traits = profile.designTraits;
505
+ const accent = profile.colors.find(c => c.role === 'accent');
506
+ const bg = profile.colors.find(c => c.role === 'background');
507
+ const fonts = [...new Set(profile.typography.map(t => t.fontFamily))];
508
+ const isMono = (f) => /mono|consolas|courier|fira code|jetbrains|sf mono|menlo/i.test(f);
509
+ const dFonts = fonts.filter(f => !isMono(f));
510
+ if (accent) {
511
+ dos.push(`Use \`${accent.hex}\` for interactive elements (buttons, links, focus rings)`);
512
+ }
513
+ if (bg) {
514
+ dos.push(`Use \`${bg.hex}\` as the primary page background`);
515
+ }
516
+ donts.push("Don't introduce colors outside this palette — extend the design tokens first");
517
+ if (dFonts.length <= 1 && dFonts[0]) {
518
+ dos.push(`Use **${dFonts[0]}** for all UI text`);
519
+ donts.push(`Don't mix font families — use ${dFonts[0]} consistently`);
520
+ }
521
+ else if (dFonts.length >= 2) {
522
+ dos.push(`Pair **${dFonts[0]}** (body) with **${dFonts[1]}** (display) — these are the only allowed fonts`);
523
+ donts.push(`Don't introduce additional font families beyond ${fonts.join(' and ')}`);
524
+ }
525
+ dos.push(`Follow the **${profile.spacing.base}px** spacing grid for all margins, padding, and gaps`);
526
+ donts.push(`Don't use arbitrary spacing values — stick to multiples of ${profile.spacing.base}px`);
527
+ if (!traits.hasShadows) {
528
+ dos.push('Use border and background shifts for elevation — not shadows');
529
+ donts.push("Don't add box-shadow — this design system uses flat elevation");
530
+ }
531
+ else {
532
+ dos.push('Use the defined shadow tokens for elevation — see Section 6');
533
+ donts.push("Don't create custom box-shadow values outside the system tokens");
534
+ }
535
+ if (!traits.hasGradients) {
536
+ donts.push("Don't use gradients — the design uses solid colors only");
537
+ }
538
+ if (profile.borderRadius.length > 0) {
539
+ dos.push(`Use border-radius from the scale: ${profile.borderRadius.filter(r => !r.includes('9999')).slice(0, 5).join(', ')}`);
540
+ donts.push(`Don't use arbitrary border-radius values — pick from the defined scale`);
541
+ }
542
+ if (profile.components.length > 0) {
543
+ dos.push('Reuse existing components from Section 4 before creating new ones');
544
+ donts.push("Don't duplicate component patterns — check Section 4 first");
545
+ }
546
+ if (profile.iconLibrary) {
547
+ dos.push(`Use **${profile.iconLibrary}** for all icons`);
548
+ donts.push("Don't mix icon libraries — consistency matters");
549
+ }
550
+ if (profile.antiPatterns.includes('no-blur')) {
551
+ donts.push("Don't use backdrop-blur or blur effects");
552
+ }
553
+ if (profile.designTraits.hasDarkMode) {
554
+ dos.push('Always use CSS variables for colors — never hardcode hex');
555
+ dos.push('Test both light and dark modes for contrast');
556
+ }
557
+ let content = "### Do's\n\n";
558
+ for (const d of dos)
559
+ content += `- ${d}\n`;
560
+ content += "\n### Don'ts\n\n";
561
+ for (const d of donts)
562
+ content += `- ${d}\n`;
563
+ // Detected anti-patterns
564
+ const explicitAntiPatterns = [];
565
+ if (profile.antiPatterns.includes('no-shadows'))
566
+ explicitAntiPatterns.push('No box-shadow on any element');
567
+ if (profile.antiPatterns.includes('no-gradients'))
568
+ explicitAntiPatterns.push('No gradient backgrounds');
569
+ if (profile.antiPatterns.includes('no-blur'))
570
+ explicitAntiPatterns.push('No blur or backdrop-blur effects');
571
+ if (profile.antiPatterns.includes('no-zebra-striping'))
572
+ explicitAntiPatterns.push('No zebra striping on tables/lists');
573
+ if (explicitAntiPatterns.length >= 2) {
574
+ content += '\n### Anti-Patterns (detected from codebase)\n\n';
575
+ for (const ap of explicitAntiPatterns) {
576
+ content += `- ${ap}\n`;
577
+ }
578
+ }
579
+ return `## 8. Do's and Don'ts
580
+
581
+ ${content}`;
582
+ }
583
+ // ── Section 9: Responsive Behavior ────────────────────────────────────
584
+ function generateResponsiveBehavior(profile) {
585
+ if (profile.breakpoints.length === 0) {
586
+ return `## 9. Responsive Behavior
587
+
588
+ No breakpoints detected. Consider adding responsive breakpoints to the design system.`;
589
+ }
590
+ let table = '| Name | Value | Source |\n|---|---|---|\n';
591
+ for (const bp of profile.breakpoints) {
592
+ table += `| ${bp.name} | ${bp.value} | ${bp.source} |\n`;
593
+ }
594
+ const hasTailwind = profile.frameworks.some(f => f.id === 'tailwind');
595
+ let content = table;
596
+ if (hasTailwind) {
597
+ content += `\n**Approach:** Mobile-first using Tailwind responsive prefixes (\`sm:\`, \`md:\`, \`lg:\`, \`xl:\`, \`2xl:\`).\n`;
598
+ content += 'Always design for mobile first, then layer on responsive overrides.\n';
599
+ }
600
+ else {
601
+ content += '\n**Approach:** Use `@media (min-width: ...)` queries matching the breakpoints above.\n';
602
+ }
603
+ return `## 9. Responsive Behavior
604
+
605
+ ${content}`;
606
+ }
607
+ // ── Section 10: Agent Prompt Guide ────────────────────────────────────
608
+ function generateAgentPromptGuide(profile) {
609
+ const bg = profile.colors.find(c => c.role === 'background');
610
+ const surface = profile.colors.find(c => c.role === 'surface');
611
+ const accent = profile.colors.find(c => c.role === 'accent');
612
+ const text = profile.colors.find(c => c.role === 'text-primary');
613
+ const textMuted = profile.colors.find(c => c.role === 'text-muted');
614
+ const border = profile.colors.find(c => c.role === 'border');
615
+ const fonts = [...new Set(profile.typography.map(t => t.fontFamily))].filter(f => f);
616
+ const primaryFont = fonts[0] || 'sans-serif';
617
+ const borderRadius = findBorderRadius(profile);
618
+ const shadowNote = profile.designTraits.hasShadows
619
+ ? 'Use shadow tokens from Section 6.'
620
+ : 'No shadows — use borders and surface colors for depth.';
621
+ let content = 'Use these as starting points when building new UI:\n\n';
622
+ // Helper: use extracted token or a CSS variable reference, never a hardcoded hex
623
+ const col = (token, varName) => token?.hex || `var(--${varName})`;
624
+ // Card prompt
625
+ content += '### Build a Card\n\n';
626
+ content += '```\n';
627
+ content += `Background: ${col(surface || bg, 'surface')}\n`;
628
+ content += `Border: 1px solid ${col(border, 'border')}\n`;
629
+ content += `Radius: ${borderRadius}\n`;
630
+ content += `Padding: ${pickSpacing(profile.spacing, 4)}px\n`;
631
+ content += `Font: ${primaryFont}\n`;
632
+ content += `${shadowNote}\n`;
633
+ content += '```\n\n';
634
+ // Button prompt
635
+ content += '### Build a Button\n\n';
636
+ content += '```\n';
637
+ content += `Primary: bg ${col(accent, 'accent')}, text white\n`;
638
+ content += `Ghost: bg transparent, border ${col(border, 'border')}\n`;
639
+ content += `Padding: ${pickSpacing(profile.spacing, 2)}px ${pickSpacing(profile.spacing, 4)}px\n`;
640
+ content += `Radius: ${borderRadius}\n`;
641
+ content += `Hover: opacity 0.9 or lighter shade\n`;
642
+ content += `Focus: ring with ${col(accent, 'accent')}\n`;
643
+ content += '```\n\n';
644
+ // Page layout prompt
645
+ content += '### Build a Page Layout\n\n';
646
+ content += '```\n';
647
+ content += `Background: ${col(bg, 'background')}\n`;
648
+ content += `Max-width: ${profile.containerMaxWidth || '1280px'}, centered\n`;
649
+ content += `Grid: ${profile.spacing.base}px base\n`;
650
+ content += `Responsive: mobile-first, breakpoints from Section 9\n`;
651
+ content += '```\n\n';
652
+ // Stats / Data card
653
+ content += '### Build a Stats Card\n\n';
654
+ content += '```\n';
655
+ content += `Surface: ${col(surface || bg, 'surface')}\n`;
656
+ content += `Label: ${col(textMuted, 'text-muted')} (muted, 12px, uppercase)\n`;
657
+ content += `Value: ${col(text, 'text-primary')} (primary, 24-32px, bold)\n`;
658
+ content += `Status: use success/warning/danger from Section 2\n`;
659
+ content += '```\n\n';
660
+ // Form
661
+ content += '### Build a Form\n\n';
662
+ content += '```\n';
663
+ content += `Input bg: ${col(bg, 'background')}\n`;
664
+ content += `Input border: 1px solid ${col(border, 'border')}\n`;
665
+ content += `Focus: border-color ${col(accent, 'accent')}\n`;
666
+ content += `Label: ${col(textMuted, 'text-muted')} 12px\n`;
667
+ content += `Spacing: ${pickSpacing(profile.spacing, 4)}px between fields\n`;
668
+ content += `Radius: ${borderRadius}\n`;
669
+ content += '```\n\n';
670
+ // General
671
+ content += '### General Component\n\n';
672
+ content += '```\n';
673
+ content += `1. Read DESIGN.md Sections 2-6 for tokens\n`;
674
+ content += `2. Colors: only from palette\n`;
675
+ content += `3. Font: ${primaryFont}, type scale from Section 3\n`;
676
+ content += `4. Spacing: ${profile.spacing.base}px grid\n`;
677
+ content += `5. Components: match patterns from Section 4\n`;
678
+ content += `6. Elevation: ${profile.designTraits.hasShadows ? 'shadow tokens' : 'flat, surface shifts'}\n`;
679
+ content += '```\n';
680
+ return `## 10. Agent Prompt Guide
681
+
682
+ ${content}`;
683
+ }
684
+ function findBorderRadius(profile) {
685
+ const radii = profile.borderRadius.filter(r => !r.includes('9999'));
686
+ if (radii.length > 0) {
687
+ return radii[Math.floor(radii.length / 2)];
688
+ }
689
+ const radiusVar = profile.cssVariables.find(v => /radius/i.test(v.name));
690
+ if (radiusVar)
691
+ return radiusVar.value;
692
+ return '8px';
693
+ }
694
+ // ── Helpers ───────────────────────────────────────────────────────────
695
+ function pickSpacing(spacing, multiplier) {
696
+ const target = spacing.base * multiplier;
697
+ const clamped = Math.min(target, 32);
698
+ if (spacing.values.length > 0) {
699
+ const closest = spacing.values.reduce((prev, curr) => Math.abs(curr - clamped) < Math.abs(prev - clamped) ? curr : prev);
700
+ return closest;
701
+ }
702
+ return clamped;
703
+ }
704
+ //# sourceMappingURL=design-md.js.map