skillui 1.1.2 → 1.1.4
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/README.md +20 -15
- package/dist/cli.js +105073 -194
- package/package.json +15 -6
- package/dist/cli.d.ts +0 -3
- package/dist/extractors/components.d.ts +0 -11
- package/dist/extractors/components.js +0 -455
- package/dist/extractors/framework.d.ts +0 -4
- package/dist/extractors/framework.js +0 -126
- package/dist/extractors/tokens/computed.d.ts +0 -7
- package/dist/extractors/tokens/computed.js +0 -249
- package/dist/extractors/tokens/css.d.ts +0 -3
- package/dist/extractors/tokens/css.js +0 -510
- package/dist/extractors/tokens/http-css.d.ts +0 -14
- package/dist/extractors/tokens/http-css.js +0 -1689
- package/dist/extractors/tokens/tailwind.d.ts +0 -3
- package/dist/extractors/tokens/tailwind.js +0 -353
- package/dist/extractors/tokens/tokens-file.d.ts +0 -3
- package/dist/extractors/tokens/tokens-file.js +0 -229
- package/dist/extractors/ultra/animations.d.ts +0 -21
- package/dist/extractors/ultra/animations.js +0 -527
- package/dist/extractors/ultra/components-dom.d.ts +0 -13
- package/dist/extractors/ultra/components-dom.js +0 -149
- package/dist/extractors/ultra/interactions.d.ts +0 -14
- package/dist/extractors/ultra/interactions.js +0 -222
- package/dist/extractors/ultra/layout.d.ts +0 -14
- package/dist/extractors/ultra/layout.js +0 -123
- package/dist/extractors/ultra/pages.d.ts +0 -16
- package/dist/extractors/ultra/pages.js +0 -228
- package/dist/font-resolver.d.ts +0 -10
- package/dist/font-resolver.js +0 -280
- package/dist/modes/dir.d.ts +0 -6
- package/dist/modes/dir.js +0 -213
- package/dist/modes/repo.d.ts +0 -6
- package/dist/modes/repo.js +0 -76
- package/dist/modes/ultra.d.ts +0 -22
- package/dist/modes/ultra.js +0 -281
- package/dist/modes/url.d.ts +0 -14
- package/dist/modes/url.js +0 -161
- package/dist/normalizer.d.ts +0 -11
- package/dist/normalizer.js +0 -867
- package/dist/playwright-loader.d.ts +0 -10
- package/dist/playwright-loader.js +0 -71
- package/dist/screenshot.d.ts +0 -9
- package/dist/screenshot.js +0 -94
- package/dist/types-ultra.d.ts +0 -157
- package/dist/types-ultra.js +0 -4
- package/dist/types.d.ts +0 -182
- package/dist/types.js +0 -4
- package/dist/writers/animations-md.d.ts +0 -17
- package/dist/writers/animations-md.js +0 -313
- package/dist/writers/components-md.d.ts +0 -8
- package/dist/writers/components-md.js +0 -151
- package/dist/writers/design-md.d.ts +0 -7
- package/dist/writers/design-md.js +0 -704
- package/dist/writers/interactions-md.d.ts +0 -8
- package/dist/writers/interactions-md.js +0 -146
- package/dist/writers/layout-md.d.ts +0 -8
- package/dist/writers/layout-md.js +0 -120
- package/dist/writers/skill.d.ts +0 -12
- package/dist/writers/skill.js +0 -1006
- package/dist/writers/tokens-json.d.ts +0 -11
- package/dist/writers/tokens-json.js +0 -164
|
@@ -1,704 +0,0 @@
|
|
|
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`;
|
|
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
|