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.
@@ -0,0 +1,1032 @@
1
+ // src/extractor/style-applier.ts
2
+ import fs from "fs-extra";
3
+ import path from "path";
4
+ import { chromium } from "playwright";
5
+ import { glob } from "glob";
6
+ import https from "https";
7
+ import http from "http";
8
+ var StyleApplier = class {
9
+ browser = null;
10
+ page = null;
11
+ async apply(sourceUrl, targetProject) {
12
+ console.log(`
13
+ \u{1F3A8} Extracting styles from ${sourceUrl}...`);
14
+ const styles = await this.extractStylesFromSource(sourceUrl);
15
+ console.log(`
16
+ \u{1F4C1} Analyzing project at ${targetProject}...`);
17
+ const components = await this.findProjectComponents(targetProject);
18
+ console.log(` Found ${components.length} components`);
19
+ await this.updateTailwindConfig(targetProject, styles);
20
+ await this.updateGlobalsCss(targetProject, styles);
21
+ await this.generateFramerVariants(targetProject, styles);
22
+ if (styles.assets.length > 0) {
23
+ await this.downloadAssets(targetProject, styles.assets);
24
+ }
25
+ for (const component of components) {
26
+ await this.applyStylesToComponent(component, styles);
27
+ }
28
+ console.log(`
29
+ \u2705 Applied ${styles.sitePrefix} design system`);
30
+ console.log(` \u2022 ${Object.keys(styles.colors).length} colors`);
31
+ console.log(` \u2022 ${Object.keys(styles.gradients).length} gradients`);
32
+ console.log(` \u2022 ${Object.keys(styles.shadows).length} shadows`);
33
+ console.log(` \u2022 ${styles.assets.length} assets downloaded`);
34
+ console.log(` \u2022 ${components.length} components styled`);
35
+ }
36
+ async extractStylesFromSource(url) {
37
+ this.browser = await chromium.launch({ headless: true });
38
+ const context = await this.browser.newContext({
39
+ viewport: { width: 1920, height: 1080 },
40
+ userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
41
+ });
42
+ this.page = await context.newPage();
43
+ const parsedUrl = new URL(url);
44
+ const siteName = parsedUrl.hostname.replace(/^www\./, "").split(".")[0];
45
+ const sitePrefix = siteName.toLowerCase().replace(/[^a-z0-9]/g, "-");
46
+ await this.page.goto(url, { waitUntil: "networkidle", timeout: 6e4 });
47
+ await this.page.waitForTimeout(2e3);
48
+ await this.autoScroll();
49
+ const styles = await this.page.evaluate((prefix) => {
50
+ const result = {
51
+ siteName: document.title || prefix,
52
+ sitePrefix: prefix,
53
+ colors: {},
54
+ gradients: {},
55
+ fonts: [],
56
+ fontSizes: {},
57
+ fontWeights: {},
58
+ lineHeights: {},
59
+ letterSpacing: {},
60
+ spacing: {},
61
+ borderRadius: {},
62
+ shadows: {},
63
+ transitions: [],
64
+ assets: [],
65
+ componentStyles: {
66
+ buttons: [],
67
+ cards: [],
68
+ inputs: [],
69
+ headings: [],
70
+ links: [],
71
+ containers: [],
72
+ heroes: [],
73
+ navs: []
74
+ }
75
+ };
76
+ const colorSet = /* @__PURE__ */ new Set();
77
+ const gradientSet = /* @__PURE__ */ new Set();
78
+ const fontSet = /* @__PURE__ */ new Set();
79
+ const shadowSet = /* @__PURE__ */ new Set();
80
+ const transitionSet = /* @__PURE__ */ new Set();
81
+ const rgbToHex = (rgb) => {
82
+ const match = rgb.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
83
+ if (match) {
84
+ const r = parseInt(match[1]).toString(16).padStart(2, "0");
85
+ const g = parseInt(match[2]).toString(16).padStart(2, "0");
86
+ const b = parseInt(match[3]).toString(16).padStart(2, "0");
87
+ return `#${r}${g}${b}`.toUpperCase();
88
+ }
89
+ return rgb;
90
+ };
91
+ const elements = document.querySelectorAll("*");
92
+ elements.forEach((el) => {
93
+ const style = window.getComputedStyle(el);
94
+ const color = style.color;
95
+ const bgColor = style.backgroundColor;
96
+ if (color && color !== "rgba(0, 0, 0, 0)") {
97
+ colorSet.add(color.startsWith("rgb(") ? rgbToHex(color) : color);
98
+ }
99
+ if (bgColor && bgColor !== "rgba(0, 0, 0, 0)") {
100
+ if (bgColor.includes("gradient")) {
101
+ gradientSet.add(bgColor);
102
+ } else {
103
+ colorSet.add(bgColor.startsWith("rgb(") ? rgbToHex(bgColor) : bgColor);
104
+ }
105
+ }
106
+ const bgImage = style.backgroundImage;
107
+ if (bgImage && bgImage !== "none" && bgImage.includes("gradient")) {
108
+ const tagName = el.tagName.toLowerCase();
109
+ const className = el.className?.toString?.() || "";
110
+ let gradientName = "gradient";
111
+ if (className.includes("hero") || className.includes("Hero") || tagName === "main") {
112
+ gradientName = "bg";
113
+ } else if (className.includes("btn") || className.includes("button") || tagName === "button") {
114
+ gradientName = "button";
115
+ } else if (className.includes("card") || className.includes("Card")) {
116
+ gradientName = "card";
117
+ } else if (className.includes("nav") || className.includes("header") || tagName === "nav" || tagName === "header") {
118
+ gradientName = "nav";
119
+ } else if (className.includes("footer") || tagName === "footer") {
120
+ gradientName = "footer";
121
+ } else if (className.includes("cta") || className.includes("CTA")) {
122
+ gradientName = "cta";
123
+ }
124
+ if (!result.gradients[gradientName]) {
125
+ result.gradients[gradientName] = bgImage;
126
+ }
127
+ }
128
+ const fontFamily = style.fontFamily;
129
+ if (fontFamily) fontSet.add(fontFamily.split(",")[0].trim().replace(/"/g, ""));
130
+ const boxShadow = style.boxShadow;
131
+ if (boxShadow && boxShadow !== "none") shadowSet.add(boxShadow);
132
+ const transition = style.transition;
133
+ if (transition && transition !== "all 0s ease 0s") transitionSet.add(transition);
134
+ });
135
+ let colorIndex = 1;
136
+ colorSet.forEach((c) => {
137
+ if (c.startsWith("#")) {
138
+ result.colors[`color-${colorIndex++}`] = c;
139
+ }
140
+ });
141
+ let gradientIndex = 1;
142
+ gradientSet.forEach((g) => {
143
+ if (!Object.values(result.gradients).includes(g)) {
144
+ result.gradients[`accent-${gradientIndex++}`] = g;
145
+ }
146
+ });
147
+ result.fonts = Array.from(fontSet).slice(0, 5);
148
+ result.shadows = {};
149
+ const assetSet = /* @__PURE__ */ new Set();
150
+ const images = document.querySelectorAll('img[src*=".png"], img[src*=".webp"], img[src*="3d"], img[src*="icon"]');
151
+ images.forEach((img) => {
152
+ const src = img.src;
153
+ if (src && !src.includes("data:") && !assetSet.has(src)) {
154
+ const url2 = new URL(src, window.location.href);
155
+ const filename = url2.pathname.split("/").pop() || "asset.png";
156
+ const isIcon = filename.includes("icon") || img.classList.toString().includes("icon");
157
+ const is3d = filename.includes("3d") || src.includes("3d") || img.closest('[class*="3d"]');
158
+ result.assets.push({
159
+ url: url2.href,
160
+ type: is3d ? "3d" : isIcon ? "icon" : "image",
161
+ filename
162
+ });
163
+ assetSet.add(src);
164
+ }
165
+ });
166
+ const svgs = document.querySelectorAll('svg[class*="icon"], svg[id*="icon"]');
167
+ svgs.forEach((svg, i) => {
168
+ const svgContent = svg.outerHTML;
169
+ result.assets.push({
170
+ url: `data:image/svg+xml,${encodeURIComponent(svgContent)}`,
171
+ type: "icon",
172
+ filename: `icon-${i + 1}.svg`
173
+ });
174
+ });
175
+ let shadowIndex = 1;
176
+ shadowSet.forEach((s) => {
177
+ result.shadows[`shadow-${shadowIndex++}`] = s;
178
+ });
179
+ result.transitions = Array.from(transitionSet).slice(0, 5);
180
+ const buttons = document.querySelectorAll('button, [role="button"], a[class*="button"], a[class*="btn"]');
181
+ buttons.forEach((btn, i) => {
182
+ if (i >= 5) return;
183
+ const style = window.getComputedStyle(btn);
184
+ result.componentStyles.buttons.push({
185
+ selector: "button",
186
+ className: `btn-style-${i + 1}`,
187
+ styles: {
188
+ backgroundColor: style.backgroundColor,
189
+ color: style.color,
190
+ padding: style.padding,
191
+ borderRadius: style.borderRadius,
192
+ fontWeight: style.fontWeight,
193
+ fontSize: style.fontSize,
194
+ border: style.border,
195
+ boxShadow: style.boxShadow,
196
+ transition: style.transition
197
+ }
198
+ });
199
+ });
200
+ const cards = document.querySelectorAll('[class*="card"], [class*="Card"], article, .rounded-xl, .rounded-2xl, .rounded-3xl');
201
+ cards.forEach((card, i) => {
202
+ if (i >= 5) return;
203
+ const style = window.getComputedStyle(card);
204
+ if (style.borderRadius !== "0px" && style.boxShadow !== "none") {
205
+ result.componentStyles.cards.push({
206
+ selector: ".card",
207
+ className: `card-style-${i + 1}`,
208
+ styles: {
209
+ backgroundColor: style.backgroundColor,
210
+ borderRadius: style.borderRadius,
211
+ boxShadow: style.boxShadow,
212
+ padding: style.padding,
213
+ border: style.border
214
+ }
215
+ });
216
+ }
217
+ });
218
+ const headings = document.querySelectorAll("h1, h2, h3, h4, h5, h6");
219
+ const headingStyles = {};
220
+ headings.forEach((h) => {
221
+ const tag = h.tagName.toLowerCase();
222
+ if (!headingStyles[tag]) {
223
+ const style = window.getComputedStyle(h);
224
+ headingStyles[tag] = {
225
+ selector: tag,
226
+ className: `${tag}-style`,
227
+ styles: {
228
+ fontSize: style.fontSize,
229
+ fontWeight: style.fontWeight,
230
+ lineHeight: style.lineHeight,
231
+ letterSpacing: style.letterSpacing,
232
+ color: style.color,
233
+ fontFamily: style.fontFamily
234
+ }
235
+ };
236
+ }
237
+ });
238
+ result.componentStyles.headings = Object.values(headingStyles);
239
+ const inputs = document.querySelectorAll("input, textarea, select");
240
+ inputs.forEach((input, i) => {
241
+ if (i >= 3) return;
242
+ const style = window.getComputedStyle(input);
243
+ result.componentStyles.inputs.push({
244
+ selector: "input",
245
+ className: `input-style-${i + 1}`,
246
+ styles: {
247
+ backgroundColor: style.backgroundColor,
248
+ border: style.border,
249
+ borderRadius: style.borderRadius,
250
+ padding: style.padding,
251
+ fontSize: style.fontSize,
252
+ color: style.color,
253
+ outline: style.outline
254
+ }
255
+ });
256
+ });
257
+ const sections = document.querySelectorAll('section, [class*="hero"], [class*="Hero"], main > div:first-child');
258
+ sections.forEach((section, i) => {
259
+ if (i >= 3) return;
260
+ const style = window.getComputedStyle(section);
261
+ result.componentStyles.heroes.push({
262
+ selector: "section",
263
+ className: `hero-style-${i + 1}`,
264
+ styles: {
265
+ backgroundColor: style.backgroundColor,
266
+ backgroundImage: style.backgroundImage,
267
+ padding: style.padding,
268
+ minHeight: style.minHeight
269
+ }
270
+ });
271
+ });
272
+ const navs = document.querySelectorAll('nav, header, [class*="nav"], [class*="Nav"]');
273
+ navs.forEach((nav, i) => {
274
+ if (i >= 2) return;
275
+ const style = window.getComputedStyle(nav);
276
+ result.componentStyles.navs.push({
277
+ selector: "nav",
278
+ className: `nav-style-${i + 1}`,
279
+ styles: {
280
+ backgroundColor: style.backgroundColor,
281
+ padding: style.padding,
282
+ boxShadow: style.boxShadow,
283
+ backdropFilter: style.backdropFilter
284
+ }
285
+ });
286
+ });
287
+ const containers = document.querySelectorAll('[class*="container"], [class*="Container"], .max-w-');
288
+ containers.forEach((container, i) => {
289
+ if (i >= 3) return;
290
+ const style = window.getComputedStyle(container);
291
+ result.componentStyles.containers.push({
292
+ selector: ".container",
293
+ className: `container-style-${i + 1}`,
294
+ styles: {
295
+ maxWidth: style.maxWidth,
296
+ padding: style.padding,
297
+ margin: style.margin
298
+ }
299
+ });
300
+ });
301
+ const links = document.querySelectorAll('a:not([class*="button"]):not([class*="btn"])');
302
+ links.forEach((link, i) => {
303
+ if (i >= 3) return;
304
+ const style = window.getComputedStyle(link);
305
+ result.componentStyles.links.push({
306
+ selector: "a",
307
+ className: `link-style-${i + 1}`,
308
+ styles: {
309
+ color: style.color,
310
+ textDecoration: style.textDecoration,
311
+ fontWeight: style.fontWeight,
312
+ transition: style.transition
313
+ }
314
+ });
315
+ });
316
+ const fontSizes = ["12px", "14px", "16px", "18px", "20px", "24px", "32px", "48px", "64px", "72px", "96px", "120px"];
317
+ fontSizes.forEach((size, i) => {
318
+ result.fontSizes[`text-${["xs", "sm", "base", "lg", "xl", "2xl", "3xl", "4xl", "5xl", "6xl", "7xl", "8xl"][i]}`] = size;
319
+ });
320
+ [4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 80, 96, 128].forEach((px, i) => {
321
+ result.spacing[`${i + 1}`] = `${px}px`;
322
+ });
323
+ ["4px", "8px", "12px", "16px", "20px", "24px", "32px", "40px", "50px", "9999px"].forEach((r, i) => {
324
+ result.borderRadius[["sm", "md", "lg", "xl", "2xl", "3xl", "4xl", "5xl", "6xl", "full"][i]] = r;
325
+ });
326
+ return result;
327
+ }, sitePrefix);
328
+ styles.siteName = siteName;
329
+ styles.sitePrefix = sitePrefix;
330
+ await this.browser.close();
331
+ return styles;
332
+ }
333
+ async autoScroll() {
334
+ if (!this.page) return;
335
+ await this.page.evaluate(async () => {
336
+ await new Promise((resolve) => {
337
+ let totalHeight = 0;
338
+ const distance = 300;
339
+ const timer = setInterval(() => {
340
+ window.scrollBy(0, distance);
341
+ totalHeight += distance;
342
+ if (totalHeight >= document.body.scrollHeight) {
343
+ clearInterval(timer);
344
+ window.scrollTo(0, 0);
345
+ resolve();
346
+ }
347
+ }, 100);
348
+ });
349
+ });
350
+ }
351
+ async findProjectComponents(projectPath) {
352
+ const components = [];
353
+ const files = await glob("**/*.{tsx,jsx}", {
354
+ cwd: projectPath,
355
+ ignore: ["node_modules/**", ".next/**", "dist/**", "build/**"]
356
+ });
357
+ for (const file of files) {
358
+ const filePath = path.join(projectPath, file);
359
+ const content = await fs.readFile(filePath, "utf-8");
360
+ const name = path.basename(file, path.extname(file));
361
+ const type = this.detectComponentType(name, content);
362
+ if (type !== "unknown") {
363
+ components.push({ filePath, name, type, content });
364
+ }
365
+ }
366
+ return components;
367
+ }
368
+ detectComponentType(name, content) {
369
+ const nameLower = name.toLowerCase();
370
+ if (nameLower.includes("button") || nameLower.includes("btn")) return "button";
371
+ if (nameLower.includes("card")) return "card";
372
+ if (nameLower.includes("input") || nameLower.includes("field") || nameLower.includes("text")) return "input";
373
+ if (nameLower.includes("heading") || nameLower.includes("title") || nameLower.includes("header") && !nameLower.includes("nav")) return "heading";
374
+ if (nameLower.includes("link")) return "link";
375
+ if (nameLower.includes("container") || nameLower.includes("wrapper") || nameLower.includes("layout")) return "container";
376
+ if (nameLower.includes("hero") || nameLower.includes("banner") || nameLower.includes("splash")) return "hero";
377
+ if (nameLower.includes("nav") || nameLower.includes("menu") || nameLower.includes("header")) return "nav";
378
+ if (content.includes("<button") || content.includes("onClick")) return "button";
379
+ if (content.includes("<input") || content.includes("<textarea")) return "input";
380
+ if (/<h[1-6]/.test(content)) return "heading";
381
+ return "unknown";
382
+ }
383
+ async updateTailwindConfig(projectPath, styles) {
384
+ const configPaths = [
385
+ "tailwind.config.js",
386
+ "tailwind.config.ts",
387
+ "tailwind.config.mjs"
388
+ ];
389
+ let configPath = null;
390
+ for (const cp of configPaths) {
391
+ const fullPath = path.join(projectPath, cp);
392
+ if (await fs.pathExists(fullPath)) {
393
+ configPath = fullPath;
394
+ break;
395
+ }
396
+ }
397
+ if (!configPath) {
398
+ configPath = path.join(projectPath, "tailwind.config.js");
399
+ }
400
+ const config = `/** @type {import('tailwindcss').Config} */
401
+ module.exports = {
402
+ content: [
403
+ './src/**/*.{js,ts,jsx,tsx,mdx}',
404
+ './app/**/*.{js,ts,jsx,tsx,mdx}',
405
+ './components/**/*.{js,ts,jsx,tsx,mdx}',
406
+ ],
407
+ theme: {
408
+ extend: {
409
+ colors: {
410
+ ${Object.entries(styles.colors).map(([k, v]) => ` '${k}': '${v}',`).join("\n")}
411
+ },
412
+ fontFamily: {
413
+ 'extracted': [${styles.fonts.length > 0 ? styles.fonts.map((f) => `'${f}'`).join(", ") : "'system-ui', 'sans-serif'"}],
414
+ },
415
+ boxShadow: {
416
+ ${Object.entries(styles.shadows).map(([k, v]) => ` '${k}': '${v}',`).join("\n")}
417
+ },
418
+ borderRadius: {
419
+ ${Object.entries(styles.borderRadius).map(([k, v]) => ` '${k}': '${v}',`).join("\n")}
420
+ },
421
+ },
422
+ },
423
+ plugins: [],
424
+ };
425
+ `;
426
+ await fs.writeFile(configPath, config);
427
+ console.log(` \u2713 Updated ${path.basename(configPath)}`);
428
+ }
429
+ async updateGlobalsCss(projectPath, styles) {
430
+ const possiblePaths = [
431
+ "src/app/globals.css",
432
+ "src/styles/globals.css",
433
+ "styles/globals.css",
434
+ "app/globals.css",
435
+ "src/index.css"
436
+ ];
437
+ let globalsPath = null;
438
+ for (const gp of possiblePaths) {
439
+ const fullPath = path.join(projectPath, gp);
440
+ if (await fs.pathExists(fullPath)) {
441
+ globalsPath = fullPath;
442
+ break;
443
+ }
444
+ }
445
+ if (!globalsPath) {
446
+ globalsPath = path.join(projectPath, "src/app/globals.css");
447
+ await fs.ensureDir(path.dirname(globalsPath));
448
+ }
449
+ const prefix = styles.sitePrefix;
450
+ const cssContent = `@tailwind base;
451
+ @tailwind components;
452
+ @tailwind utilities;
453
+
454
+ /* ========================================
455
+ ${styles.siteName.toUpperCase()} DESIGN SYSTEM
456
+ Extracted via Extraktor
457
+ ======================================== */
458
+
459
+ :root {
460
+ /* Colors */
461
+ ${Object.entries(styles.colors).map(([k, v]) => ` --${k}: ${v};`).join("\n")}
462
+
463
+ /* Gradients */
464
+ ${Object.entries(styles.gradients).map(([k, v]) => ` --gradient-${k}: ${v};`).join("\n")}
465
+
466
+ /* Shadows */
467
+ ${Object.entries(styles.shadows).map(([k, v]) => ` --${k}: ${v};`).join("\n")}
468
+
469
+ /* Border Radius */
470
+ ${Object.entries(styles.borderRadius).map(([k, v]) => ` --radius-${k}: ${v};`).join("\n")}
471
+ }
472
+
473
+ /* ========================================
474
+ BASE STYLES
475
+ ======================================== */
476
+ html {
477
+ scroll-behavior: smooth;
478
+ }
479
+
480
+ body {
481
+ font-family: ${styles.fonts[0] ? `'${styles.fonts[0]}', ` : ""}-apple-system, BlinkMacSystemFont, system-ui, sans-serif;
482
+ ${styles.gradients["bg"] ? `background: var(--gradient-bg);` : ""}
483
+ min-height: 100vh;
484
+ -webkit-font-smoothing: antialiased;
485
+ color: var(--color-3);
486
+ }
487
+
488
+ /* ========================================
489
+ ${prefix.toUpperCase()} NAVIGATION
490
+ ======================================== */
491
+ .${prefix}-nav {
492
+ background: rgba(255, 255, 255, 0.8);
493
+ backdrop-filter: blur(20px);
494
+ -webkit-backdrop-filter: blur(20px);
495
+ border: 1px solid rgba(255, 255, 255, 0.5);
496
+ border-radius: var(--radius-3xl);
497
+ box-shadow: var(--shadow-sm);
498
+ }
499
+
500
+ .${prefix}-nav-link {
501
+ color: var(--color-7);
502
+ font-weight: 700;
503
+ font-size: 16px;
504
+ padding: 12px 20px;
505
+ border-radius: var(--radius-xl);
506
+ transition: all 0.2s ease;
507
+ }
508
+
509
+ .${prefix}-nav-link:hover {
510
+ color: var(--color-3);
511
+ background: rgba(0, 0, 0, 0.05);
512
+ }
513
+
514
+ /* ========================================
515
+ ${prefix.toUpperCase()} BUTTONS
516
+ ======================================== */
517
+ .${prefix}-btn-primary {
518
+ ${styles.gradients["button"] ? `background: var(--gradient-button);` : `background: var(--color-5);`}
519
+ color: white;
520
+ font-weight: 800;
521
+ padding: 16px 32px;
522
+ border-radius: var(--radius-full);
523
+ font-size: 18px;
524
+ border: none;
525
+ cursor: pointer;
526
+ box-shadow: var(--shadow-md);
527
+ transition: all 0.3s cubic-bezier(0.22, 1, 0.36, 1);
528
+ }
529
+
530
+ .${prefix}-btn-primary:hover {
531
+ transform: scale(1.05);
532
+ box-shadow: var(--shadow-lg);
533
+ }
534
+
535
+ .${prefix}-btn-secondary {
536
+ background: var(--color-4);
537
+ color: var(--color-3);
538
+ font-weight: 700;
539
+ padding: 16px 32px;
540
+ border-radius: var(--radius-full);
541
+ font-size: 18px;
542
+ border: 1px solid rgba(0, 0, 0, 0.06);
543
+ cursor: pointer;
544
+ box-shadow: var(--shadow-sm);
545
+ transition: all 0.3s cubic-bezier(0.22, 1, 0.36, 1);
546
+ }
547
+
548
+ .${prefix}-btn-secondary:hover {
549
+ transform: scale(1.05);
550
+ box-shadow: var(--shadow-md);
551
+ }
552
+
553
+ /* ========================================
554
+ ${prefix.toUpperCase()} CARDS
555
+ ======================================== */
556
+ .${prefix}-card {
557
+ background: var(--color-4);
558
+ border-radius: var(--radius-4xl);
559
+ box-shadow: var(--shadow-md);
560
+ border: 1px solid rgba(0, 0, 0, 0.04);
561
+ overflow: hidden;
562
+ }
563
+
564
+ .${prefix}-card-featured {
565
+ ${styles.gradients["card"] ? `background: var(--gradient-card);` : `background: var(--color-4);`}
566
+ border-radius: var(--radius-5xl);
567
+ box-shadow: var(--shadow-lg);
568
+ border: 1px solid rgba(255, 255, 255, 0.5);
569
+ overflow: hidden;
570
+ }
571
+
572
+ .${prefix}-card-glass {
573
+ background: rgba(255, 255, 255, 0.6);
574
+ backdrop-filter: blur(20px);
575
+ -webkit-backdrop-filter: blur(20px);
576
+ border-radius: var(--radius-3xl);
577
+ box-shadow: var(--shadow-sm);
578
+ border: 1px solid rgba(255, 255, 255, 0.8);
579
+ }
580
+
581
+ /* ========================================
582
+ ${prefix.toUpperCase()} TYPOGRAPHY
583
+ ======================================== */
584
+ .${prefix}-h1 {
585
+ font-size: 120px;
586
+ font-weight: 900;
587
+ line-height: 1;
588
+ letter-spacing: -2.4px;
589
+ color: var(--color-3);
590
+ }
591
+
592
+ .${prefix}-h2 {
593
+ font-size: 72px;
594
+ font-weight: 900;
595
+ line-height: 1.1;
596
+ letter-spacing: -1px;
597
+ color: var(--color-3);
598
+ }
599
+
600
+ .${prefix}-h3 {
601
+ font-size: 48px;
602
+ font-weight: 800;
603
+ line-height: 1.1;
604
+ letter-spacing: -1px;
605
+ color: var(--color-3);
606
+ }
607
+
608
+ .${prefix}-h4 {
609
+ font-size: 32px;
610
+ font-weight: 800;
611
+ line-height: 1.1;
612
+ letter-spacing: -0.5px;
613
+ color: var(--color-3);
614
+ }
615
+
616
+ .${prefix}-text-gradient {
617
+ ${styles.gradients["button"] ? `background: var(--gradient-button);` : `background: linear-gradient(90deg, var(--color-5), var(--color-6));`}
618
+ -webkit-background-clip: text;
619
+ -webkit-text-fill-color: transparent;
620
+ background-clip: text;
621
+ }
622
+
623
+ .${prefix}-text-muted {
624
+ color: var(--color-7);
625
+ }
626
+
627
+ /* ========================================
628
+ ${prefix.toUpperCase()} BADGES
629
+ ======================================== */
630
+ .${prefix}-badge {
631
+ display: inline-flex;
632
+ align-items: center;
633
+ gap: 8px;
634
+ background: var(--color-4);
635
+ border: 1px solid rgba(0, 0, 0, 0.06);
636
+ padding: 10px 20px;
637
+ border-radius: var(--radius-full);
638
+ font-weight: 700;
639
+ font-size: 14px;
640
+ box-shadow: var(--shadow-sm);
641
+ }
642
+
643
+ .${prefix}-badge-featured {
644
+ ${styles.gradients["button"] ? `background: var(--gradient-button);` : `background: var(--color-5);`}
645
+ color: white;
646
+ font-weight: 800;
647
+ padding: 8px 16px;
648
+ border-radius: var(--radius-lg);
649
+ font-size: 12px;
650
+ letter-spacing: 0.5px;
651
+ text-transform: uppercase;
652
+ }
653
+
654
+ /* ========================================
655
+ ${prefix.toUpperCase()} STATS
656
+ ======================================== */
657
+ .${prefix}-stat-card {
658
+ background: var(--color-4);
659
+ border-radius: var(--radius-4xl);
660
+ padding: 32px;
661
+ text-align: center;
662
+ box-shadow: var(--shadow-md);
663
+ border: 1px solid rgba(0, 0, 0, 0.04);
664
+ }
665
+
666
+ .${prefix}-stat-icon {
667
+ width: 64px;
668
+ height: 64px;
669
+ margin: 0 auto 20px;
670
+ ${styles.gradients["button"] ? `background: var(--gradient-button);` : `background: var(--color-5);`}
671
+ border-radius: var(--radius-2xl);
672
+ display: flex;
673
+ align-items: center;
674
+ justify-content: center;
675
+ box-shadow: var(--shadow-md);
676
+ }
677
+
678
+ /* ========================================
679
+ ${prefix.toUpperCase()} CTA
680
+ ======================================== */
681
+ .${prefix}-cta {
682
+ ${styles.gradients["button"] ? `background: var(--gradient-button);` : `background: var(--color-5);`}
683
+ border-radius: var(--radius-5xl);
684
+ box-shadow: var(--shadow-lg);
685
+ position: relative;
686
+ overflow: hidden;
687
+ }
688
+
689
+ .${prefix}-cta::before {
690
+ content: '';
691
+ position: absolute;
692
+ top: 0;
693
+ left: 0;
694
+ right: 0;
695
+ height: 50%;
696
+ background: linear-gradient(180deg, rgba(255,255,255,0.2) 0%, transparent 100%);
697
+ }
698
+
699
+ /* ========================================
700
+ ${prefix.toUpperCase()} FOOTER
701
+ ======================================== */
702
+ .${prefix}-footer {
703
+ ${styles.gradients["footer"] ? `background: var(--gradient-footer);` : `background: var(--color-9);`}
704
+ border-top: 1px solid rgba(0, 0, 0, 0.05);
705
+ }
706
+
707
+ /* ========================================
708
+ RESPONSIVE
709
+ ======================================== */
710
+ @media (max-width: 1024px) {
711
+ .${prefix}-h1 { font-size: 72px; }
712
+ .${prefix}-h2 { font-size: 48px; }
713
+ .${prefix}-h3 { font-size: 36px; }
714
+ }
715
+
716
+ @media (max-width: 768px) {
717
+ .${prefix}-h1 { font-size: 48px; letter-spacing: -1px; }
718
+ .${prefix}-h2 { font-size: 36px; }
719
+ .${prefix}-h3 { font-size: 28px; }
720
+ }
721
+ `;
722
+ await fs.writeFile(globalsPath, cssContent);
723
+ console.log(` \u2713 Updated ${path.relative(projectPath, globalsPath)}`);
724
+ }
725
+ async generateFramerVariants(projectPath, styles) {
726
+ const variantsPath = path.join(projectPath, "src/lib/framer-variants.ts");
727
+ await fs.ensureDir(path.dirname(variantsPath));
728
+ const prefix = styles.sitePrefix;
729
+ const content = `// Framer Motion Variants
730
+ // Extracted from ${styles.siteName} via Extraktor
731
+
732
+ // Transition presets
733
+ export const transitions = {
734
+ fast: { duration: 0.15 },
735
+ normal: { duration: 0.3 },
736
+ slow: { duration: 0.5 },
737
+ spring: { type: 'spring', stiffness: 300, damping: 30 },
738
+ springBouncy: { type: 'spring', stiffness: 400, damping: 20 },
739
+ smooth: { duration: 0.3, ease: [0.22, 1, 0.36, 1] },
740
+ };
741
+
742
+ // Fade animations
743
+ export const fadeIn = {
744
+ initial: { opacity: 0 },
745
+ animate: { opacity: 1 },
746
+ exit: { opacity: 0 },
747
+ };
748
+
749
+ export const fadeInUp = {
750
+ initial: { opacity: 0, y: 20 },
751
+ animate: { opacity: 1, y: 0 },
752
+ exit: { opacity: 0, y: -20 },
753
+ };
754
+
755
+ export const fadeInDown = {
756
+ initial: { opacity: 0, y: -20 },
757
+ animate: { opacity: 1, y: 0 },
758
+ exit: { opacity: 0, y: 20 },
759
+ };
760
+
761
+ export const fadeInLeft = {
762
+ initial: { opacity: 0, x: -20 },
763
+ animate: { opacity: 1, x: 0 },
764
+ exit: { opacity: 0, x: 20 },
765
+ };
766
+
767
+ export const fadeInRight = {
768
+ initial: { opacity: 0, x: 20 },
769
+ animate: { opacity: 1, x: 0 },
770
+ exit: { opacity: 0, x: -20 },
771
+ };
772
+
773
+ // Scale animations
774
+ export const scaleIn = {
775
+ initial: { opacity: 0, scale: 0.9 },
776
+ animate: { opacity: 1, scale: 1 },
777
+ exit: { opacity: 0, scale: 0.9 },
778
+ };
779
+
780
+ export const scaleInBounce = {
781
+ initial: { opacity: 0, scale: 0.8 },
782
+ animate: { opacity: 1, scale: 1, transition: transitions.springBouncy },
783
+ exit: { opacity: 0, scale: 0.8 },
784
+ };
785
+
786
+ // Stagger containers
787
+ export const staggerContainer = {
788
+ initial: {},
789
+ animate: {
790
+ transition: {
791
+ staggerChildren: 0.1,
792
+ delayChildren: 0.1,
793
+ },
794
+ },
795
+ };
796
+
797
+ export const staggerContainerFast = {
798
+ initial: {},
799
+ animate: {
800
+ transition: {
801
+ staggerChildren: 0.05,
802
+ delayChildren: 0.05,
803
+ },
804
+ },
805
+ };
806
+
807
+ // Hover states
808
+ export const hoverScale = {
809
+ whileHover: { scale: 1.02 },
810
+ whileTap: { scale: 0.98 },
811
+ };
812
+
813
+ export const hoverScaleLarge = {
814
+ whileHover: { scale: 1.05 },
815
+ whileTap: { scale: 0.95 },
816
+ };
817
+
818
+ export const hoverLift = {
819
+ whileHover: { y: -4, transition: { duration: 0.2 } },
820
+ whileTap: { y: 0 },
821
+ };
822
+
823
+ export const hoverGlow = {
824
+ whileHover: {
825
+ boxShadow: '0 10px 40px rgba(0, 0, 0, 0.15)',
826
+ transition: { duration: 0.3 }
827
+ },
828
+ };
829
+
830
+ // Button variants
831
+ export const buttonVariants = {
832
+ initial: { scale: 1 },
833
+ hover: { scale: 1.02 },
834
+ tap: { scale: 0.98 },
835
+ disabled: { opacity: 0.5 },
836
+ };
837
+
838
+ // Card variants
839
+ export const cardVariants = {
840
+ initial: { opacity: 0, y: 20 },
841
+ animate: { opacity: 1, y: 0 },
842
+ hover: { y: -4, boxShadow: '0 20px 40px rgba(0, 0, 0, 0.1)' },
843
+ };
844
+
845
+ // Modal variants
846
+ export const modalVariants = {
847
+ initial: { opacity: 0, scale: 0.95 },
848
+ animate: { opacity: 1, scale: 1 },
849
+ exit: { opacity: 0, scale: 0.95 },
850
+ };
851
+
852
+ export const overlayVariants = {
853
+ initial: { opacity: 0 },
854
+ animate: { opacity: 1 },
855
+ exit: { opacity: 0 },
856
+ };
857
+
858
+ // Scroll reveal
859
+ export const scrollReveal = {
860
+ initial: { opacity: 0, y: 60 },
861
+ whileInView: { opacity: 1, y: 0 },
862
+ viewport: { once: true, margin: '-100px' },
863
+ };
864
+
865
+ // Navigation
866
+ export const navItemVariants = {
867
+ initial: { opacity: 0, y: -10 },
868
+ animate: { opacity: 1, y: 0 },
869
+ hover: { color: 'var(--color-primary)' },
870
+ };
871
+
872
+ // Floating animation (for 3D elements)
873
+ export const floatingVariants = {
874
+ animate: {
875
+ y: [0, -10, 0],
876
+ transition: {
877
+ duration: 3,
878
+ repeat: Infinity,
879
+ ease: 'easeInOut',
880
+ },
881
+ },
882
+ };
883
+
884
+ // Export all as default
885
+ export default {
886
+ transitions,
887
+ fadeIn,
888
+ fadeInUp,
889
+ fadeInDown,
890
+ fadeInLeft,
891
+ fadeInRight,
892
+ scaleIn,
893
+ scaleInBounce,
894
+ staggerContainer,
895
+ staggerContainerFast,
896
+ hoverScale,
897
+ hoverScaleLarge,
898
+ hoverLift,
899
+ hoverGlow,
900
+ buttonVariants,
901
+ cardVariants,
902
+ modalVariants,
903
+ overlayVariants,
904
+ scrollReveal,
905
+ navItemVariants,
906
+ floatingVariants,
907
+ };
908
+ `;
909
+ await fs.writeFile(variantsPath, content);
910
+ console.log(` \u2713 Generated ${path.relative(projectPath, variantsPath)}`);
911
+ }
912
+ async downloadAssets(projectPath, assets) {
913
+ const assetsDir = path.join(projectPath, "public/assets");
914
+ await fs.ensureDir(assetsDir);
915
+ let downloaded = 0;
916
+ for (const asset of assets.slice(0, 20)) {
917
+ try {
918
+ const outputPath = path.join(assetsDir, asset.filename);
919
+ if (asset.url.startsWith("data:")) {
920
+ const match = asset.url.match(/data:image\/svg\+xml,(.+)/);
921
+ if (match) {
922
+ const svgContent = decodeURIComponent(match[1]);
923
+ await fs.writeFile(outputPath, svgContent);
924
+ downloaded++;
925
+ }
926
+ } else {
927
+ await this.downloadFile(asset.url, outputPath);
928
+ downloaded++;
929
+ }
930
+ } catch (err) {
931
+ }
932
+ }
933
+ if (downloaded > 0) {
934
+ console.log(` \u2713 Downloaded ${downloaded} assets to public/assets/`);
935
+ }
936
+ }
937
+ downloadFile(url, outputPath) {
938
+ return new Promise((resolve, reject) => {
939
+ const protocol = url.startsWith("https") ? https : http;
940
+ const file = fs.createWriteStream(outputPath);
941
+ protocol.get(url, (response) => {
942
+ if (response.statusCode === 301 || response.statusCode === 302) {
943
+ const redirectUrl = response.headers.location;
944
+ if (redirectUrl) {
945
+ file.close();
946
+ fs.unlinkSync(outputPath);
947
+ this.downloadFile(redirectUrl, outputPath).then(resolve).catch(reject);
948
+ return;
949
+ }
950
+ }
951
+ response.pipe(file);
952
+ file.on("finish", () => {
953
+ file.close();
954
+ resolve();
955
+ });
956
+ }).on("error", (err) => {
957
+ fs.unlink(outputPath, () => {
958
+ });
959
+ reject(err);
960
+ });
961
+ });
962
+ }
963
+ async applyStylesToComponent(component, styles) {
964
+ let content = component.content;
965
+ let modified = false;
966
+ const componentStyles = styles.componentStyles[`${component.type}s`] || [];
967
+ if (componentStyles.length === 0) return;
968
+ const primaryStyle = componentStyles[0];
969
+ if (!primaryStyle) return;
970
+ const styleClass = primaryStyle.className;
971
+ const classNamePatterns = [
972
+ // className="..."
973
+ /(className\s*=\s*["'])([^"']*)(["'])/g,
974
+ // className={`...`}
975
+ /(className\s*=\s*\{`?)([^`}]*)(`?\})/g,
976
+ // className={cn("...")}
977
+ /(className\s*=\s*\{cn\(["'])([^"']*)(["']\)\})/g
978
+ ];
979
+ for (const pattern of classNamePatterns) {
980
+ if (pattern.test(content)) {
981
+ content = content.replace(pattern, (match, prefix, classes, suffix) => {
982
+ if (classes.includes(styleClass)) {
983
+ return match;
984
+ }
985
+ modified = true;
986
+ return `${prefix}${styleClass} ${classes}${suffix}`;
987
+ });
988
+ break;
989
+ }
990
+ }
991
+ if (!modified) {
992
+ const returnMatch = content.match(/return\s*\(\s*<(\w+)/);
993
+ if (returnMatch) {
994
+ const element = returnMatch[1];
995
+ const elementPattern = new RegExp(`(<${element})(\\s|>)`, "g");
996
+ if (content.match(new RegExp(`<${element}[^>]*className`))) {
997
+ content = content.replace(
998
+ new RegExp(`(<${element}[^>]*className\\s*=\\s*["'])([^"']*)(["'])`),
999
+ `$1${styleClass} $2$3`
1000
+ );
1001
+ modified = true;
1002
+ } else {
1003
+ content = content.replace(elementPattern, `$1 className="${styleClass}"$2`);
1004
+ modified = true;
1005
+ }
1006
+ }
1007
+ }
1008
+ if (component.type === "button" && primaryStyle.styles.backgroundColor?.includes("gradient")) {
1009
+ content = content.replace(
1010
+ /(style\s*=\s*\{\{?)([^}]*)(}?\})/g,
1011
+ `$1$2, background: 'var(--gradient-1)'$3`
1012
+ );
1013
+ if (!content.includes("style=")) {
1014
+ const returnMatch = content.match(/return\s*\(\s*<(\w+)([^>]*)(>)/);
1015
+ if (returnMatch) {
1016
+ content = content.replace(
1017
+ returnMatch[0],
1018
+ `return (<${returnMatch[1]}${returnMatch[2]} style={{ background: 'var(--gradient-1)' }}${returnMatch[3]}`
1019
+ );
1020
+ }
1021
+ }
1022
+ modified = true;
1023
+ }
1024
+ if (modified) {
1025
+ await fs.writeFile(component.filePath, content);
1026
+ console.log(` \u2713 Styled ${component.name} (${component.type})`);
1027
+ }
1028
+ }
1029
+ };
1030
+ export {
1031
+ StyleApplier
1032
+ };