extraktor 1.0.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/LICENSE +21 -0
- package/README.md +174 -0
- package/dist/chunk-5IH5TLAQ.js +91 -0
- package/dist/chunk-PHMSK7VD.js +6411 -0
- package/dist/chunk-VLLFGYUN.js +2773 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +3261 -0
- package/dist/design-md-generator-YMQOE2IW.js +502 -0
- package/dist/index.d.ts +2774 -0
- package/dist/index.js +58 -0
- package/dist/server-DR7RCM5S.js +328 -0
- package/dist/style-applier-BMHP6V57.js +1032 -0
- package/dist/theme-package-generator-E55BBBZN.js +412 -0
- package/package.json +99 -0
- package/skills/analyze-design.md +20 -0
- package/skills/apply-style.md +24 -0
- package/skills/clone-site.md +29 -0
- package/skills/design-md.md +29 -0
- package/skills/devtools-extract.md +30 -0
- package/skills/extract-tokens.md +21 -0
- package/skills/genome.md +31 -0
- package/skills/regen.md +32 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
// src/genome/design-md-generator.ts
|
|
2
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
3
|
+
import fs from "fs-extra";
|
|
4
|
+
var DesignMdGenerator = class {
|
|
5
|
+
client;
|
|
6
|
+
model;
|
|
7
|
+
logger;
|
|
8
|
+
constructor(options) {
|
|
9
|
+
const apiKey = options.apiKey || process.env.ANTHROPIC_API_KEY;
|
|
10
|
+
this.client = apiKey ? new Anthropic({ apiKey }) : null;
|
|
11
|
+
this.model = options.model || "claude-sonnet-4-6-20250514";
|
|
12
|
+
this.logger = options.logger;
|
|
13
|
+
}
|
|
14
|
+
async generate(result, outputPath, sourceUrl) {
|
|
15
|
+
this.logger.info("Generating DESIGN.md...");
|
|
16
|
+
const siteName = this.getSiteName(sourceUrl);
|
|
17
|
+
const layoutArch = result.layoutArchitecture;
|
|
18
|
+
const sections = [
|
|
19
|
+
this.section1_VisualTheme(result, siteName, sourceUrl),
|
|
20
|
+
this.section2_ColorPalette(result),
|
|
21
|
+
this.section3_Typography(result),
|
|
22
|
+
this.section4_Components(result),
|
|
23
|
+
this.section5_Layout(result, layoutArch),
|
|
24
|
+
this.section6_Depth(result),
|
|
25
|
+
this.section7_DosAndDonts(result),
|
|
26
|
+
this.section8_Responsive(result, layoutArch),
|
|
27
|
+
this.section9_AgentPrompt(result, siteName)
|
|
28
|
+
];
|
|
29
|
+
let md = `# Design System: ${siteName}
|
|
30
|
+
|
|
31
|
+
`;
|
|
32
|
+
md += sections.join("\n\n");
|
|
33
|
+
if (this.client) {
|
|
34
|
+
md = await this.enhanceWithAI(md, result, sourceUrl);
|
|
35
|
+
}
|
|
36
|
+
await fs.writeFile(outputPath, md, "utf-8");
|
|
37
|
+
this.logger.info(`DESIGN.md written to ${outputPath}`);
|
|
38
|
+
return md;
|
|
39
|
+
}
|
|
40
|
+
// ─── Section 1: Visual Theme & Atmosphere ──────────────────
|
|
41
|
+
section1_VisualTheme(result, siteName, url) {
|
|
42
|
+
const fonts = result.typography.fontFamilies;
|
|
43
|
+
const primaryFont = fonts[0]?.name || "system-ui";
|
|
44
|
+
const monoFont = fonts.find((f) => f.category === "mono")?.name;
|
|
45
|
+
const palette = result.colors.palette;
|
|
46
|
+
const bgColors = palette.filter((c) => c.usage?.includes("background"));
|
|
47
|
+
const textColors = palette.filter((c) => c.usage?.includes("text"));
|
|
48
|
+
const isDark = bgColors.some((c) => {
|
|
49
|
+
const hex = c.value.replace("#", "");
|
|
50
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
51
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
52
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
53
|
+
return (r + g + b) / 3 < 80;
|
|
54
|
+
});
|
|
55
|
+
let md = `## 1. Visual Theme & Atmosphere
|
|
56
|
+
|
|
57
|
+
`;
|
|
58
|
+
md += `**Site:** ${url}
|
|
59
|
+
|
|
60
|
+
`;
|
|
61
|
+
md += `**Key Characteristics:**
|
|
62
|
+
`;
|
|
63
|
+
md += `- ${isDark ? "Dark-mode-first" : "Light-mode-first"} design
|
|
64
|
+
`;
|
|
65
|
+
md += `- Primary typeface: ${primaryFont}${fonts[0]?.fallbacks?.length ? ` with fallbacks: ${fonts[0].fallbacks.join(", ")}` : ""}
|
|
66
|
+
`;
|
|
67
|
+
if (monoFont) md += `- Monospace: ${monoFont}
|
|
68
|
+
`;
|
|
69
|
+
md += `- ${palette.length} colors in palette, ${result.colors.gradients.length} gradients
|
|
70
|
+
`;
|
|
71
|
+
md += `- ${result.animations.keyframes.length} keyframe animations, ${result.animations.transitions.length} transitions
|
|
72
|
+
`;
|
|
73
|
+
const accentColors = palette.filter(
|
|
74
|
+
(c) => c.usage?.includes("accent") || c.usage?.includes("border")
|
|
75
|
+
);
|
|
76
|
+
if (accentColors.length > 0) {
|
|
77
|
+
md += `- Accent colors: ${accentColors.slice(0, 3).map((c) => `\`${c.value}\``).join(", ")}
|
|
78
|
+
`;
|
|
79
|
+
}
|
|
80
|
+
return md;
|
|
81
|
+
}
|
|
82
|
+
// ─── Section 2: Color Palette & Roles ──────────────────────
|
|
83
|
+
section2_ColorPalette(result) {
|
|
84
|
+
let md = `## 2. Color Palette & Roles
|
|
85
|
+
|
|
86
|
+
`;
|
|
87
|
+
const categories = {};
|
|
88
|
+
for (const color of result.colors.palette) {
|
|
89
|
+
const category = this.categorizeColor(color);
|
|
90
|
+
if (!categories[category]) categories[category] = [];
|
|
91
|
+
categories[category].push(color);
|
|
92
|
+
}
|
|
93
|
+
for (const [category, colors] of Object.entries(categories)) {
|
|
94
|
+
md += `### ${category}
|
|
95
|
+
`;
|
|
96
|
+
for (const c of colors.slice(0, 8)) {
|
|
97
|
+
md += `- **${c.name}** (\`${c.value}\`): Used ${c.frequency}x${c.usage?.length ? ` - ${c.usage.join(", ")}` : ""}
|
|
98
|
+
`;
|
|
99
|
+
}
|
|
100
|
+
md += "\n";
|
|
101
|
+
}
|
|
102
|
+
if (result.colors.gradients.length > 0) {
|
|
103
|
+
md += `### Gradients
|
|
104
|
+
`;
|
|
105
|
+
for (const g of result.colors.gradients.slice(0, 5)) {
|
|
106
|
+
md += `- \`${g.value.slice(0, 80)}${g.value.length > 80 ? "..." : ""}\`
|
|
107
|
+
`;
|
|
108
|
+
}
|
|
109
|
+
md += "\n";
|
|
110
|
+
}
|
|
111
|
+
return md;
|
|
112
|
+
}
|
|
113
|
+
// ─── Section 3: Typography Rules ──────────────────────────
|
|
114
|
+
section3_Typography(result) {
|
|
115
|
+
let md = `## 3. Typography Rules
|
|
116
|
+
|
|
117
|
+
`;
|
|
118
|
+
md += `### Font Families
|
|
119
|
+
`;
|
|
120
|
+
for (const font of result.typography.fontFamilies) {
|
|
121
|
+
const fallbacks = font.fallbacks?.length ? `: \`${font.value}\`, fallbacks: ${font.fallbacks.join(", ")}` : "";
|
|
122
|
+
md += `- **${font.category || "Primary"}**: ${font.name}${fallbacks}
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
125
|
+
md += "\n";
|
|
126
|
+
if (result.typography.fontSizes.length > 0) {
|
|
127
|
+
md += `### Type Scale
|
|
128
|
+
|
|
129
|
+
`;
|
|
130
|
+
md += `| Role | Size | Weight | Line Height |
|
|
131
|
+
`;
|
|
132
|
+
md += `|------|------|--------|-------------|
|
|
133
|
+
`;
|
|
134
|
+
for (const size of result.typography.fontSizes.slice(0, 15)) {
|
|
135
|
+
const weight = result.typography.fontWeights.find(
|
|
136
|
+
(w) => w.frequency > 5
|
|
137
|
+
)?.value || "400";
|
|
138
|
+
md += `| ${size.name || "-"} | ${size.value} | ${weight} | - |
|
|
139
|
+
`;
|
|
140
|
+
}
|
|
141
|
+
md += "\n";
|
|
142
|
+
}
|
|
143
|
+
if (result.typography.fontWeights.length > 0) {
|
|
144
|
+
md += `### Font Weights
|
|
145
|
+
`;
|
|
146
|
+
for (const w of result.typography.fontWeights) {
|
|
147
|
+
md += `- **${w.name || w.value}** (${w.value}): Used ${w.frequency}x
|
|
148
|
+
`;
|
|
149
|
+
}
|
|
150
|
+
md += "\n";
|
|
151
|
+
}
|
|
152
|
+
return md;
|
|
153
|
+
}
|
|
154
|
+
// ─── Section 4: Component Stylings ─────────────────────────
|
|
155
|
+
section4_Components(result) {
|
|
156
|
+
let md = `## 4. Component Stylings
|
|
157
|
+
|
|
158
|
+
`;
|
|
159
|
+
md += `### Buttons
|
|
160
|
+
`;
|
|
161
|
+
const radii = result.effects.borderRadii;
|
|
162
|
+
const buttonRadius = radii.find((r) => parseFloat(r.value) >= 4 && parseFloat(r.value) <= 8);
|
|
163
|
+
const pillRadius = radii.find((r) => parseFloat(r.value) >= 9e3);
|
|
164
|
+
if (buttonRadius) {
|
|
165
|
+
md += `- Standard radius: \`${buttonRadius.value}\`
|
|
166
|
+
`;
|
|
167
|
+
}
|
|
168
|
+
if (pillRadius) {
|
|
169
|
+
md += `- Pill radius: \`${pillRadius.value}\`
|
|
170
|
+
`;
|
|
171
|
+
}
|
|
172
|
+
const btnTransition = result.animations.transitions.find(
|
|
173
|
+
(t) => t.property === "all" || t.property === "background"
|
|
174
|
+
);
|
|
175
|
+
if (btnTransition) {
|
|
176
|
+
md += `- Transition: \`${btnTransition.value || `${btnTransition.duration} ${btnTransition.timingFunction}`}\`
|
|
177
|
+
`;
|
|
178
|
+
}
|
|
179
|
+
md += "\n";
|
|
180
|
+
md += `### Cards & Containers
|
|
181
|
+
`;
|
|
182
|
+
const cardRadius = radii.find((r) => parseFloat(r.value) >= 8 && parseFloat(r.value) <= 16);
|
|
183
|
+
if (cardRadius) md += `- Radius: \`${cardRadius.value}\`
|
|
184
|
+
`;
|
|
185
|
+
if (result.effects.shadows.length > 0) {
|
|
186
|
+
md += `- Shadow: \`${result.effects.shadows[0].value}\`
|
|
187
|
+
`;
|
|
188
|
+
}
|
|
189
|
+
md += "\n";
|
|
190
|
+
md += `### Navigation
|
|
191
|
+
`;
|
|
192
|
+
md += `- Font: ${result.typography.fontFamilies[0]?.name || "system"}
|
|
193
|
+
`;
|
|
194
|
+
const navSize = result.typography.fontSizes.find(
|
|
195
|
+
(s) => parseFloat(s.value) >= 13 && parseFloat(s.value) <= 15
|
|
196
|
+
);
|
|
197
|
+
if (navSize) md += `- Size: \`${navSize.value}\`
|
|
198
|
+
`;
|
|
199
|
+
md += "\n";
|
|
200
|
+
return md;
|
|
201
|
+
}
|
|
202
|
+
// ─── Section 5: Layout Principles ──────────────────────────
|
|
203
|
+
section5_Layout(result, arch) {
|
|
204
|
+
let md = `## 5. Layout Principles
|
|
205
|
+
|
|
206
|
+
`;
|
|
207
|
+
md += `### Spacing System
|
|
208
|
+
`;
|
|
209
|
+
const spacingValues = result.spacing.scale.slice(0, 12).map((s) => s.value);
|
|
210
|
+
if (spacingValues.length > 0) {
|
|
211
|
+
md += `- Scale: ${spacingValues.join(", ")}
|
|
212
|
+
`;
|
|
213
|
+
const pxValues = result.spacing.scale.map((s) => s.pxValue).filter((v) => v > 0).sort((a, b) => a - b);
|
|
214
|
+
if (pxValues.length >= 2) {
|
|
215
|
+
const diffs = pxValues.slice(1).map((v, i) => v - pxValues[i]);
|
|
216
|
+
const gcd = diffs.reduce((a, b) => this.gcd(a, b));
|
|
217
|
+
if (gcd >= 4) md += `- Base unit: ${gcd}px
|
|
218
|
+
`;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
md += "\n";
|
|
222
|
+
md += `### Grid & Container
|
|
223
|
+
`;
|
|
224
|
+
if (result.layout.containers.length > 0) {
|
|
225
|
+
const container = result.layout.containers[0];
|
|
226
|
+
if (container.maxWidth) md += `- Max width: \`${container.maxWidth}\`
|
|
227
|
+
`;
|
|
228
|
+
}
|
|
229
|
+
if (result.layout.gridPatterns.length > 0) {
|
|
230
|
+
for (const grid of result.layout.gridPatterns.slice(0, 3)) {
|
|
231
|
+
md += `- Grid: ${grid.columns} columns, gap \`${grid.gap}\`
|
|
232
|
+
`;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
md += "\n";
|
|
236
|
+
if (arch?.layoutType) {
|
|
237
|
+
md += `### Layout Type
|
|
238
|
+
`;
|
|
239
|
+
md += `- Pattern: \`${arch.layoutType}\`
|
|
240
|
+
`;
|
|
241
|
+
md += `- Sections: ${arch.sections?.length || 0}
|
|
242
|
+
`;
|
|
243
|
+
}
|
|
244
|
+
if (result.effects.borderRadii.length > 0) {
|
|
245
|
+
md += `
|
|
246
|
+
### Border Radius Scale
|
|
247
|
+
`;
|
|
248
|
+
for (const r of result.effects.borderRadii.slice(0, 8)) {
|
|
249
|
+
md += `- ${r.name || "-"}: \`${r.value}\`
|
|
250
|
+
`;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
md += "\n";
|
|
254
|
+
return md;
|
|
255
|
+
}
|
|
256
|
+
// ─── Section 6: Depth & Elevation ──────────────────────────
|
|
257
|
+
section6_Depth(result) {
|
|
258
|
+
let md = `## 6. Depth & Elevation
|
|
259
|
+
|
|
260
|
+
`;
|
|
261
|
+
if (result.effects.shadows.length > 0) {
|
|
262
|
+
md += `| Level | Shadow | Usage |
|
|
263
|
+
`;
|
|
264
|
+
md += `|-------|--------|-------|
|
|
265
|
+
`;
|
|
266
|
+
for (let i = 0; i < Math.min(result.effects.shadows.length, 6); i++) {
|
|
267
|
+
const s = result.effects.shadows[i];
|
|
268
|
+
const value = s.value.length > 60 ? s.value.slice(0, 60) + "..." : s.value;
|
|
269
|
+
md += `| Level ${i + 1} | \`${value}\` | ${s.name || "-"} |
|
|
270
|
+
`;
|
|
271
|
+
}
|
|
272
|
+
md += "\n";
|
|
273
|
+
}
|
|
274
|
+
if (result.effects.opacity.length > 0) {
|
|
275
|
+
md += `### Opacity Values
|
|
276
|
+
`;
|
|
277
|
+
for (const o of result.effects.opacity.slice(0, 5)) {
|
|
278
|
+
md += `- ${o.name || "-"}: \`${o.value}\`
|
|
279
|
+
`;
|
|
280
|
+
}
|
|
281
|
+
md += "\n";
|
|
282
|
+
}
|
|
283
|
+
return md;
|
|
284
|
+
}
|
|
285
|
+
// ─── Section 7: Do's and Don'ts ────────────────────────────
|
|
286
|
+
section7_DosAndDonts(result) {
|
|
287
|
+
let md = `## 7. Do's and Don'ts
|
|
288
|
+
|
|
289
|
+
`;
|
|
290
|
+
const primaryFont = result.typography.fontFamilies[0]?.name || "the primary font";
|
|
291
|
+
const primaryColor = result.colors.palette[0]?.value || "#000";
|
|
292
|
+
const accentColor = result.colors.palette.find(
|
|
293
|
+
(c) => c.usage?.includes("accent") || c.name?.includes("accent") || c.name?.includes("primary")
|
|
294
|
+
)?.value;
|
|
295
|
+
md += `### Do
|
|
296
|
+
`;
|
|
297
|
+
md += `- Use \`${primaryFont}\` as the primary typeface with proper fallbacks
|
|
298
|
+
`;
|
|
299
|
+
if (result.typography.fontFamilies[0]?.fallbacks?.length) {
|
|
300
|
+
md += `- Include fallback stack: ${result.typography.fontFamilies[0].fallbacks.join(", ")}
|
|
301
|
+
`;
|
|
302
|
+
}
|
|
303
|
+
md += `- Follow the spacing scale: ${result.spacing.scale.slice(0, 5).map((s) => s.value).join(", ")}...
|
|
304
|
+
`;
|
|
305
|
+
if (accentColor) {
|
|
306
|
+
md += `- Use accent color (\`${accentColor}\`) for interactive elements and CTAs only
|
|
307
|
+
`;
|
|
308
|
+
}
|
|
309
|
+
if (result.effects.borderRadii.length > 0) {
|
|
310
|
+
md += `- Use consistent border radius: \`${result.effects.borderRadii[0].value}\` for standard elements
|
|
311
|
+
`;
|
|
312
|
+
}
|
|
313
|
+
md += `- Match the transition timing: ${result.animations.transitions[0]?.value || "150ms ease"}
|
|
314
|
+
`;
|
|
315
|
+
md += "\n";
|
|
316
|
+
md += `### Don't
|
|
317
|
+
`;
|
|
318
|
+
md += `- Don't mix font families - stick to the extracted type system
|
|
319
|
+
`;
|
|
320
|
+
md += `- Don't use arbitrary spacing values outside the scale
|
|
321
|
+
`;
|
|
322
|
+
if (accentColor) {
|
|
323
|
+
md += `- Don't use accent color (\`${accentColor}\`) decoratively - reserve for interactive elements
|
|
324
|
+
`;
|
|
325
|
+
}
|
|
326
|
+
md += `- Don't ignore the shadow/elevation system - use the extracted levels
|
|
327
|
+
`;
|
|
328
|
+
md += `- Don't add border-radius values outside the established scale
|
|
329
|
+
`;
|
|
330
|
+
md += "\n";
|
|
331
|
+
return md;
|
|
332
|
+
}
|
|
333
|
+
// ─── Section 8: Responsive Behavior ────────────────────────
|
|
334
|
+
section8_Responsive(result, arch) {
|
|
335
|
+
let md = `## 8. Responsive Behavior
|
|
336
|
+
|
|
337
|
+
`;
|
|
338
|
+
if (result.layout.breakpoints.length > 0) {
|
|
339
|
+
md += `### Breakpoints
|
|
340
|
+
|
|
341
|
+
`;
|
|
342
|
+
md += `| Name | Width |
|
|
343
|
+
`;
|
|
344
|
+
md += `|------|-------|
|
|
345
|
+
`;
|
|
346
|
+
for (const bp of result.layout.breakpoints) {
|
|
347
|
+
md += `| ${bp.name || "-"} | ${bp.minWidth || bp.maxWidth || "-"}px |
|
|
348
|
+
`;
|
|
349
|
+
}
|
|
350
|
+
md += "\n";
|
|
351
|
+
} else {
|
|
352
|
+
md += `### Breakpoints (Standard)
|
|
353
|
+
|
|
354
|
+
`;
|
|
355
|
+
md += `| Name | Width |
|
|
356
|
+
`;
|
|
357
|
+
md += `|------|-------|
|
|
358
|
+
`;
|
|
359
|
+
md += `| Mobile | <640px |
|
|
360
|
+
`;
|
|
361
|
+
md += `| Tablet | 640-1024px |
|
|
362
|
+
`;
|
|
363
|
+
md += `| Desktop | >1024px |
|
|
364
|
+
|
|
365
|
+
`;
|
|
366
|
+
}
|
|
367
|
+
md += `### Collapsing Strategy
|
|
368
|
+
`;
|
|
369
|
+
if (result.layout.gridPatterns.length > 0) {
|
|
370
|
+
const grid = result.layout.gridPatterns[0];
|
|
371
|
+
md += `- Grid: ${grid.columns}-column -> single column on mobile
|
|
372
|
+
`;
|
|
373
|
+
}
|
|
374
|
+
md += `- Navigation: horizontal -> hamburger menu
|
|
375
|
+
`;
|
|
376
|
+
md += `- Section spacing: reduce by ~40% on mobile
|
|
377
|
+
`;
|
|
378
|
+
md += "\n";
|
|
379
|
+
return md;
|
|
380
|
+
}
|
|
381
|
+
// ─── Section 9: Agent Prompt Guide ──────────────────────────
|
|
382
|
+
section9_AgentPrompt(result, siteName) {
|
|
383
|
+
let md = `## 9. Agent Prompt Guide
|
|
384
|
+
|
|
385
|
+
`;
|
|
386
|
+
md += `### Quick Color Reference
|
|
387
|
+
`;
|
|
388
|
+
for (const c of result.colors.palette.slice(0, 10)) {
|
|
389
|
+
md += `- ${c.name}: \`${c.value}\`
|
|
390
|
+
`;
|
|
391
|
+
}
|
|
392
|
+
md += "\n";
|
|
393
|
+
md += `### Quick Typography Reference
|
|
394
|
+
`;
|
|
395
|
+
for (const f of result.typography.fontFamilies) {
|
|
396
|
+
md += `- ${f.category || "Primary"}: \`${f.name}\`${f.fallbacks?.length ? `, fallbacks: ${f.fallbacks.join(", ")}` : ""}
|
|
397
|
+
`;
|
|
398
|
+
}
|
|
399
|
+
md += "\n";
|
|
400
|
+
const bg = result.colors.palette.find((c) => c.usage?.includes("background"))?.value || "#ffffff";
|
|
401
|
+
const text = result.colors.palette.find((c) => c.usage?.includes("text"))?.value || "#000000";
|
|
402
|
+
const accent = result.colors.palette.find(
|
|
403
|
+
(c) => c.usage?.includes("accent") || c.name?.includes("primary") || c.name?.includes("blue")
|
|
404
|
+
)?.value || "#3366ff";
|
|
405
|
+
const font = result.typography.fontFamilies[0]?.name || "system-ui";
|
|
406
|
+
const radius = result.effects.borderRadii[0]?.value || "8px";
|
|
407
|
+
md += `### Example Component Prompts
|
|
408
|
+
|
|
409
|
+
`;
|
|
410
|
+
md += `- "Create a hero section on \`${bg}\` background. Headline using ${font}, color \`${text}\`. `;
|
|
411
|
+
md += `CTA button with \`${accent}\` background, \`${radius}\` radius."
|
|
412
|
+
|
|
413
|
+
`;
|
|
414
|
+
md += `- "Design a card: \`${bg}\` background, border \`1px solid rgba(0,0,0,0.1)\`, `;
|
|
415
|
+
md += `\`${radius}\` radius. Title in ${font} bold, color \`${text}\`."
|
|
416
|
+
|
|
417
|
+
`;
|
|
418
|
+
md += `- "Build navigation: ${font} at 14px for links, color \`${text}\`. `;
|
|
419
|
+
md += `Accent CTA \`${accent}\` right-aligned."
|
|
420
|
+
|
|
421
|
+
`;
|
|
422
|
+
md += `### Iteration Guide
|
|
423
|
+
`;
|
|
424
|
+
md += `1. Always use \`${font}\` with the specified fallback stack
|
|
425
|
+
`;
|
|
426
|
+
md += `2. Follow the spacing scale: ${result.spacing.scale.slice(0, 5).map((s) => s.value).join(", ")}...
|
|
427
|
+
`;
|
|
428
|
+
md += `3. Accent color (\`${accent}\`) is for interactive elements only
|
|
429
|
+
`;
|
|
430
|
+
md += `4. Use the shadow/elevation system for depth
|
|
431
|
+
`;
|
|
432
|
+
md += `5. Match transition timing from the extracted values
|
|
433
|
+
`;
|
|
434
|
+
return md;
|
|
435
|
+
}
|
|
436
|
+
// ─── AI Enhancement ────────────────────────────────────────
|
|
437
|
+
async enhanceWithAI(md, result, sourceUrl) {
|
|
438
|
+
if (!this.client) return md;
|
|
439
|
+
try {
|
|
440
|
+
this.logger.info("Enhancing DESIGN.md with AI narrative...");
|
|
441
|
+
const response = await this.client.messages.create({
|
|
442
|
+
model: this.model,
|
|
443
|
+
max_tokens: 1024,
|
|
444
|
+
messages: [{
|
|
445
|
+
role: "user",
|
|
446
|
+
content: `I have a DESIGN.md for ${sourceUrl}. The Section 1 "Visual Theme & Atmosphere" needs a rich, descriptive opening paragraph (3-4 sentences) that captures the design personality. Here's what I know:
|
|
447
|
+
|
|
448
|
+
Colors: ${result.colors.palette.slice(0, 8).map((c) => `${c.name}:${c.value}`).join(", ")}
|
|
449
|
+
Fonts: ${result.typography.fontFamilies.map((f) => f.name).join(", ")}
|
|
450
|
+
Animations: ${result.animations.keyframes.length} keyframes
|
|
451
|
+
Gradients: ${result.colors.gradients.length}
|
|
452
|
+
|
|
453
|
+
Write ONLY the opening paragraph. No heading, no bullets. Describe the visual feel, the mood, the design philosophy. Be specific about colors and typography choices.`
|
|
454
|
+
}]
|
|
455
|
+
});
|
|
456
|
+
const narrative = response.content[0]?.type === "text" ? response.content[0].text : "";
|
|
457
|
+
if (narrative) {
|
|
458
|
+
md = md.replace(
|
|
459
|
+
/## 1\. Visual Theme & Atmosphere\n\n/,
|
|
460
|
+
`## 1. Visual Theme & Atmosphere
|
|
461
|
+
|
|
462
|
+
${narrative}
|
|
463
|
+
|
|
464
|
+
`
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
} catch (error) {
|
|
468
|
+
this.logger.warn("AI enhancement failed, using raw data only");
|
|
469
|
+
}
|
|
470
|
+
return md;
|
|
471
|
+
}
|
|
472
|
+
// ─── Helpers ───────────────────────────────────────────────
|
|
473
|
+
getSiteName(url) {
|
|
474
|
+
try {
|
|
475
|
+
const hostname = new URL(url).hostname;
|
|
476
|
+
return hostname.replace("www.", "").split(".")[0];
|
|
477
|
+
} catch {
|
|
478
|
+
return "Unknown Site";
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
categorizeColor(color) {
|
|
482
|
+
if (color.usage?.includes("background")) return "Background Surfaces";
|
|
483
|
+
if (color.usage?.includes("text")) return "Text & Content";
|
|
484
|
+
if (color.usage?.includes("border")) return "Border & Divider";
|
|
485
|
+
if (color.usage?.includes("accent")) return "Brand & Accent";
|
|
486
|
+
if (color.usage?.includes("shadow")) return "Shadows";
|
|
487
|
+
if (color.name?.includes("gray") || color.name?.includes("grey")) return "Neutrals";
|
|
488
|
+
if (color.name?.includes("blue") || color.name?.includes("red") || color.name?.includes("green")) return "Brand & Accent";
|
|
489
|
+
return "Other";
|
|
490
|
+
}
|
|
491
|
+
gcd(a, b) {
|
|
492
|
+
a = Math.abs(Math.round(a));
|
|
493
|
+
b = Math.abs(Math.round(b));
|
|
494
|
+
while (b) {
|
|
495
|
+
[a, b] = [b, a % b];
|
|
496
|
+
}
|
|
497
|
+
return a;
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
export {
|
|
501
|
+
DesignMdGenerator
|
|
502
|
+
};
|