framer-export 4.2.1 → 4.3.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/dist/cli/index.js CHANGED
@@ -15,7 +15,7 @@ var init_package = __esm({
15
15
  "package.json"() {
16
16
  package_default = {
17
17
  name: "framer-export",
18
- version: "4.2.1",
18
+ version: "4.3.0",
19
19
  description: "Export any Framer, Webflow, or Wix site into a fully working local mirror. Downloads all assets, strips badges, rewrites URLs, and pretty-prints JS.",
20
20
  type: "module",
21
21
  main: "dist/cli/index.js",
@@ -92,16 +92,16 @@ function showBanner() {
92
92
  const isSmall = width < 65;
93
93
  if (isSmall) {
94
94
  console.log(`
95
- ${chalk.cyan.bold("F-EXPORT")} ${chalk.gray(`v${package_default.version}`)}`);
95
+ ${chalk.hex("#D4A017").bold("F-EXPORT")} ${chalk.gray(`v${package_default.version}`)}`);
96
96
  console.log(` ${chalk.white.bold("Framer \xB7 Webflow \xB7 Wix Exporter")}
97
97
  `);
98
98
  return;
99
99
  }
100
- const cyan = chalk.cyan.bold;
100
+ const gold = chalk.hex("#D4A017").bold;
101
101
  const gray = chalk.gray;
102
102
  console.log("");
103
103
  ASCII_ART.forEach((line2) => {
104
- console.log(" " + cyan(line2));
104
+ console.log(" " + gold(line2));
105
105
  });
106
106
  console.log("");
107
107
  console.log(` ${gray(`v${package_default.version}`)} ${chalk.white.bold("Framer \xB7 Webflow \xB7 Wix Exporter")}
@@ -263,6 +263,13 @@ import chalk3 from "chalk";
263
263
  function setCooking(spinner) {
264
264
  _cooking = spinner;
265
265
  }
266
+ function nextMsgColor() {
267
+ return MSG_COLORS[_msgIdx++ % MSG_COLORS.length];
268
+ }
269
+ function trunc(text, max) {
270
+ if (text.length <= max) return text;
271
+ return text.slice(0, max - 2) + "\u2026";
272
+ }
266
273
  function output(line2) {
267
274
  if (_cooking) {
268
275
  _cooking.log(line2);
@@ -270,20 +277,317 @@ function output(line2) {
270
277
  console.log(line2);
271
278
  }
272
279
  }
273
- var _cooking, T, log, info, warn, success;
280
+ var _cooking, T, TIME_COLORS, TAG_ROTATION, MSG_COLORS, _msgIdx, log, info, warn, success;
274
281
  var init_logger = __esm({
275
282
  "src/logger/index.ts"() {
276
283
  "use strict";
277
284
  _cooking = null;
278
285
  T = () => (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
279
- log = (m) => output(`${chalk3.gray(`[${T()}]`)} ${chalk3.cyan("[LOG]")} ${m}`);
280
- info = (m) => output(`${chalk3.gray(`[${T()}]`)} ${chalk3.blue("[INFO]")} ${chalk3.bold(m)}`);
286
+ TIME_COLORS = [
287
+ chalk3.gray,
288
+ chalk3.hex("#9e9e9e"),
289
+ chalk3.hex("#757575"),
290
+ chalk3.hex("#6d6d6d")
291
+ ];
292
+ TAG_ROTATION = [
293
+ (s) => chalk3.hex("#D4A017")(s),
294
+ (s) => chalk3.hex("#E0A030")(s),
295
+ (s) => chalk3.hex("#DAA520")(s),
296
+ (s) => chalk3.hex("#C4953A")(s),
297
+ (s) => chalk3.hex("#F0C040")(s)
298
+ ];
299
+ MSG_COLORS = [
300
+ (s) => chalk3.hex("#D4A017")(s),
301
+ (s) => chalk3.hex("#C8A951")(s),
302
+ (s) => chalk3.hex("#B8960A")(s),
303
+ (s) => chalk3.hex("#d4a76a")(s),
304
+ (s) => chalk3.hex("#c0a060")(s),
305
+ (s) => chalk3.hex("#dab660")(s)
306
+ ];
307
+ _msgIdx = 0;
308
+ log = (m) => {
309
+ const timeColor = TIME_COLORS[Math.floor(Math.random() * TIME_COLORS.length)];
310
+ const t = timeColor(`[${T()}]`);
311
+ const tag = TAG_ROTATION[0]("[LOG]");
312
+ const msg = nextMsgColor()(trunc(m, 120));
313
+ output(`${t} ${tag} ${msg}`);
314
+ };
315
+ info = (m) => {
316
+ const timeColor = chalk3.gray;
317
+ const t = timeColor(`[${T()}]`);
318
+ const tag = chalk3.hex("#E8A317")("[INFO]");
319
+ output(`${t} ${tag} ${chalk3.bold(trunc(m, 120))}`);
320
+ };
281
321
  warn = (m) => {
282
- const line2 = `${chalk3.gray(`[${T()}]`)} ${chalk3.yellow("[WARN]")} ${chalk3.yellow(m)}`;
322
+ const line2 = `${chalk3.gray(`[${T()}]`)} ${chalk3.hex("#CC7722").bold("[WARN]")} ${chalk3.hex("#CC7722")(trunc(m, 120))}`;
283
323
  if (_cooking) _cooking.log(line2);
284
324
  else console.warn(line2);
285
325
  };
286
- success = (m) => output(`${chalk3.gray(`[${T()}]`)} ${chalk3.green("[OK]")} ${chalk3.green(m)}`);
326
+ success = (m) => {
327
+ const t = chalk3.hex("#8a8a8a")(`[${T()}]`);
328
+ const tag = chalk3.greenBright("[OK]");
329
+ const msg = chalk3.green(trunc(m, 120));
330
+ output(`${t} ${tag} ${msg}`);
331
+ };
332
+ }
333
+ });
334
+
335
+ // src/platforms/framer.ts
336
+ var FONT_EXTS, framer;
337
+ var init_framer = __esm({
338
+ "src/platforms/framer.ts"() {
339
+ "use strict";
340
+ FONT_EXTS = [".woff2", ".woff", ".ttf", ".otf"];
341
+ framer = {
342
+ name: "framer",
343
+ displayName: "Framer",
344
+ detectByUrl(url) {
345
+ return /\.framer\.(app|website)|framercanvas\.com/.test(url);
346
+ },
347
+ detectByHtml(html) {
348
+ return html.includes('id="main"') && (html.includes("framerstatic.com") || html.includes("framerusercontent.com"));
349
+ },
350
+ stripDomains: ["events.framer.com", "api.framer.com", "collect.frameranalytics.com"],
351
+ stripSelectors: [
352
+ 'script[src*="events.framer.com"]',
353
+ "#__framer-badge-container",
354
+ "#__framer-badge",
355
+ 'link[href*="canvas-sandbox"]',
356
+ 'script[src*="framer.com/bootstrap"]'
357
+ ],
358
+ stripPatterns: [
359
+ /<div id="__framer-badge-container"[^>]*><\/div>/g,
360
+ /<script>try\{if\(localStorage\.get\("__framer_force_showing_editorbar_since"\)\)[^<]*<\/script>/g
361
+ ],
362
+ hydrationTimeout: 1e4,
363
+ needsHydrationCheck: true,
364
+ mapAssetDir(host, pathname, ext) {
365
+ if (host.includes("framerusercontent.com")) {
366
+ if (pathname.startsWith("/images/")) return "assets/images";
367
+ if (pathname.startsWith("/assets/")) {
368
+ return FONT_EXTS.includes(ext) ? "assets/fonts" : "assets/misc";
369
+ }
370
+ if (pathname.startsWith("/sites/")) {
371
+ if (ext === ".mjs" || ext === ".js") return "scripts/vendor";
372
+ if (ext === ".json") return "data";
373
+ if (ext === ".css") return "styles";
374
+ if (ext === ".framercms") return "data";
375
+ return "assets/misc";
376
+ }
377
+ if (pathname.startsWith("/modules/")) {
378
+ return ext === ".framercms" ? "data" : "scripts/modules";
379
+ }
380
+ return "assets/misc";
381
+ }
382
+ if (host.includes("app.framerstatic.com")) {
383
+ if (ext === ".css") return "styles";
384
+ if (ext === ".mjs" || ext === ".js") return "scripts/vendor";
385
+ if (ext === ".woff2" || ext === ".woff") return "assets/fonts";
386
+ if (ext === ".png" || ext === ".svg") return "assets/images";
387
+ return "assets/misc";
388
+ }
389
+ if (host.includes("framercanvas.com") || host.includes("framer.com")) {
390
+ if (ext === ".mjs" || ext === ".js") return "scripts/vendor";
391
+ if (ext === ".css") return "styles";
392
+ return "assets/misc";
393
+ }
394
+ return null;
395
+ }
396
+ };
397
+ }
398
+ });
399
+
400
+ // src/platforms/webflow.ts
401
+ var IMG_EXTS, FONT_EXTS2, VIDEO_EXTS, webflow;
402
+ var init_webflow = __esm({
403
+ "src/platforms/webflow.ts"() {
404
+ "use strict";
405
+ IMG_EXTS = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif"];
406
+ FONT_EXTS2 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
407
+ VIDEO_EXTS = [".mp4", ".webm", ".ogg"];
408
+ webflow = {
409
+ name: "webflow",
410
+ displayName: "Webflow",
411
+ detectByUrl(url) {
412
+ return /\.webflow\.(io|com)/.test(url);
413
+ },
414
+ detectByHtml(html) {
415
+ return html.includes("data-wf-site") || html.includes("w-webflow-badge") || html.includes("Webflow") || html.includes("webflow.js");
416
+ },
417
+ stripDomains: ["js-agent.newrelic.com", "cdn.heapanalytics.com", "tr.snapchat.com"],
418
+ stripSelectors: [".w-webflow-badge", 'link[href*="editorbar"]'],
419
+ stripPatterns: [
420
+ /<a[^>]*class="[^"]*w-webflow-badge[^"]*"[^>]*>[\s\S]*?<\/a>/g,
421
+ /<a[^>]*href="[^"]*webflow\.com\?utm_campaign=brandjs[^"]*"[^>]*>[\s\S]*?<\/a>/g,
422
+ /<style>[^<]*\.w-webflow-badge[^<]*<\/style>/g,
423
+ /Powered by <a[^>]*href="[^"]*webflow\.com"[^>]*>[^<]*<\/a>/g,
424
+ /<!-- This site was created in Webflow\.[^>]*-->/g,
425
+ /<html([^>]*) data-wf-domain="[^"]*"/g,
426
+ /<html([^>]*) data-wf-page="[^"]*"/g,
427
+ /<html([^>]*) data-wf-site="[^"]*"/g,
428
+ /<html([^>]*) data-wf-status="[^"]*"/g,
429
+ /<meta[^>]*content="Webflow"[^>]*>/g
430
+ ],
431
+ hydrationTimeout: 2e3,
432
+ needsHydrationCheck: false,
433
+ mapAssetDir(host, pathname, ext) {
434
+ if (host.includes("website-files.com") || host.includes("webflow.com") || host.includes("uploads-ssl.webflow.com")) {
435
+ if (VIDEO_EXTS.includes(ext)) return "assets/videos";
436
+ if (pathname.includes("/images/") || IMG_EXTS.includes(ext)) return "assets/images";
437
+ if (pathname.includes("/css/") || ext === ".css") return "styles";
438
+ if (pathname.includes("/js/") || ext === ".js") return "scripts/vendor";
439
+ if (pathname.includes("/gsap/") || pathname.includes("/plugins/")) return "scripts/vendor";
440
+ if (FONT_EXTS2.includes(ext)) return "assets/fonts";
441
+ return "assets/misc";
442
+ }
443
+ if (host.includes("d3e54v103j8qbb.cloudfront.net")) {
444
+ if (ext === ".js") return "scripts/vendor";
445
+ if (ext === ".css") return "styles";
446
+ if (IMG_EXTS.includes(ext)) return "assets/images";
447
+ return "assets/misc";
448
+ }
449
+ return null;
450
+ }
451
+ };
452
+ }
453
+ });
454
+
455
+ // src/platforms/wix.ts
456
+ var IMG_EXTS2, FONT_EXTS3, VIDEO_EXTS2, wix;
457
+ var init_wix = __esm({
458
+ "src/platforms/wix.ts"() {
459
+ "use strict";
460
+ IMG_EXTS2 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
461
+ FONT_EXTS3 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
462
+ VIDEO_EXTS2 = [".mp4", ".webm", ".ogg"];
463
+ wix = {
464
+ name: "wix",
465
+ displayName: "Wix",
466
+ detectByUrl(url) {
467
+ return /\.wixsite\.com|\.wix\.com/.test(url);
468
+ },
469
+ detectByHtml(html) {
470
+ return html.includes("wix-viewer-model") || html.includes('id="WIX_ADS"') || html.includes("X-Wix-") || html.includes("Wix.com") || html.includes("wixcode-sdk") || html.includes("data-wix") || html.includes("wix-code") || html.includes("WixCode") || /\bwix\.com\b/.test(html) || /meta\s+name=["']generator["']\s+content=["'][^"']*Wix[^"']*["']/i.test(html) || /_wix_/i.test(html) || /wix-ecom/i.test(html);
471
+ },
472
+ stripDomains: [
473
+ "frog.wix.com",
474
+ "bi.wixapi.com",
475
+ "fed.wixcodescheduler.com",
476
+ "editor.wix.com",
477
+ "panorama.wixapps.net"
478
+ ],
479
+ stripSelectors: ["#WIX_ADS", ".wix-ads", "#wix-badge", "#SCROLL_TO_TOP"],
480
+ stripPatterns: [
481
+ /<div[^>]*class="[^"]*wix-ads[^"]*"[^>]*>[\s\S]*?<\/div>/g
482
+ ],
483
+ hydrationTimeout: 3e3,
484
+ needsHydrationCheck: false,
485
+ mapAssetDir(host, pathname, ext) {
486
+ if (host === "video.wixstatic.com") {
487
+ return "assets/videos";
488
+ }
489
+ if (host.includes("wixstatic.com")) {
490
+ if (VIDEO_EXTS2.includes(ext)) return "assets/videos";
491
+ if (pathname.includes("/images/") || IMG_EXTS2.includes(ext)) return "assets/images";
492
+ if (ext === ".css") return "styles";
493
+ if (ext === ".js") return "scripts/vendor";
494
+ if (FONT_EXTS3.includes(ext)) return "assets/fonts";
495
+ return "assets/misc";
496
+ }
497
+ if (host.includes("parastorage.com")) {
498
+ if (ext === ".js") return "scripts/vendor";
499
+ if (ext === ".css") return "styles";
500
+ if (FONT_EXTS3.includes(ext)) return "assets/fonts";
501
+ if (IMG_EXTS2.includes(ext)) return "assets/images";
502
+ return "assets/misc";
503
+ }
504
+ return null;
505
+ }
506
+ };
507
+ }
508
+ });
509
+
510
+ // src/platforms/detect.ts
511
+ function detectByUrl(url) {
512
+ for (const platform of ALL_PLATFORMS) {
513
+ if (platform.detectByUrl(url)) return platform;
514
+ }
515
+ return null;
516
+ }
517
+ function detectByHtml(html) {
518
+ for (const platform of ALL_PLATFORMS) {
519
+ if (platform.detectByHtml(html)) return platform;
520
+ }
521
+ return null;
522
+ }
523
+ function detectPlatform(url, html) {
524
+ const byUrl = detectByUrl(url);
525
+ if (byUrl) return byUrl;
526
+ if (html) {
527
+ const byHtml = detectByHtml(html);
528
+ if (byHtml) return byHtml;
529
+ }
530
+ return framer;
531
+ }
532
+ async function detectByDom(page) {
533
+ try {
534
+ const signal = await page.evaluate(() => {
535
+ const html = document.documentElement.outerHTML || "";
536
+ if (/wix-viewer-model|_wix_|wix-ecom/i.test(html)) return "wix";
537
+ if (/data-wf-|w-webflow-badge|webflow\.js/i.test(html)) return "webflow";
538
+ if (/framerusercontent|framercanvas|framerstatic/i.test(html)) return "framer";
539
+ const srcs = [];
540
+ const scripts = document.querySelectorAll("script[src]");
541
+ scripts.forEach((s) => {
542
+ if (s.src) srcs.push(s.src);
543
+ });
544
+ const allSrcs = srcs.join(" ");
545
+ if (/wixstatic\.com|parastorage\.com/.test(allSrcs)) return "wix";
546
+ if (/website-files\.com|webflow\.com/.test(allSrcs)) return "webflow";
547
+ if (/framerusercontent\.com|framerstatic\.com|framer\.app/.test(allSrcs)) return "framer";
548
+ return "";
549
+ });
550
+ if (signal === "wix") return wix;
551
+ if (signal === "webflow") return webflow;
552
+ if (signal === "framer") return framer;
553
+ } catch {
554
+ }
555
+ return null;
556
+ }
557
+ function getPlatformByName(name) {
558
+ const map = { framer, webflow, wix };
559
+ return map[name] || framer;
560
+ }
561
+ var ALL_PLATFORMS;
562
+ var init_detect = __esm({
563
+ "src/platforms/detect.ts"() {
564
+ "use strict";
565
+ init_framer();
566
+ init_webflow();
567
+ init_wix();
568
+ ALL_PLATFORMS = [framer, webflow, wix];
569
+ }
570
+ });
571
+
572
+ // src/platforms/index.ts
573
+ var platforms_exports = {};
574
+ __export(platforms_exports, {
575
+ detectByDom: () => detectByDom,
576
+ detectByHtml: () => detectByHtml,
577
+ detectByUrl: () => detectByUrl,
578
+ detectPlatform: () => detectPlatform,
579
+ framer: () => framer,
580
+ getPlatformByName: () => getPlatformByName,
581
+ webflow: () => webflow,
582
+ wix: () => wix
583
+ });
584
+ var init_platforms = __esm({
585
+ "src/platforms/index.ts"() {
586
+ "use strict";
587
+ init_framer();
588
+ init_webflow();
589
+ init_wix();
590
+ init_detect();
287
591
  }
288
592
  });
289
593
 
@@ -342,6 +646,12 @@ async function launchAndCapture(exporter) {
342
646
  });
343
647
  success("Page loaded (networkidle2)");
344
648
  log("Intercepted " + intercepted + " resources, blocked " + blocked + " tracking requests");
649
+ log("Checking DOM-based platform detection...");
650
+ const domDetected = await detectByDom(exporter.page);
651
+ if (domDetected && domDetected.name !== exporter.platform.name) {
652
+ log("Platform refined from DOM: " + domDetected.displayName + " (override: " + exporter.platform.name + ")");
653
+ exporter.platform = domDetected;
654
+ }
345
655
  exporter.cooking?.update("Waiting for " + exporter.platform.displayName + " hydration...");
346
656
  if (exporter.platform.needsHydrationCheck) {
347
657
  log("Checking for #main element hydration...");
@@ -403,15 +713,42 @@ async function launchAndCapture(exporter) {
403
713
  const imgCount = [...exporter.assets.entries.values()].filter((e) => e.localPath.startsWith("assets/images")).length;
404
714
  const fontCount = [...exporter.assets.entries.values()].filter((e) => e.localPath.startsWith("assets/fonts")).length;
405
715
  log(" CSS: " + cssCount + " | JS: " + jsCount + " | Images: " + imgCount + " | Fonts: " + fontCount);
406
- await exporter.browser.close();
407
- exporter.browser = null;
408
- log("Browser closed");
716
+ }
717
+ async function closeBrowser(exporter) {
718
+ if (exporter.browser) {
719
+ await exporter.browser.close();
720
+ exporter.browser = null;
721
+ log("Browser closed");
722
+ }
723
+ }
724
+ async function captureSubpage(page, url, platform) {
725
+ log(" Navigating to sub-page: " + url);
726
+ await page.goto(url, { waitUntil: "networkidle2", timeout: CFG.timeout });
727
+ if (platform.needsHydrationCheck) {
728
+ await page.evaluate(
729
+ `new Promise(function(r) {
730
+ var t = Date.now();
731
+ (function tick() {
732
+ var m = document.getElementById('main') || document.body;
733
+ if (m && m.children.length > 0) setTimeout(r, 500);
734
+ else if (Date.now() - t > ${platform.hydrationTimeout}) r();
735
+ else setTimeout(tick, 200);
736
+ })();
737
+ })`
738
+ );
739
+ } else {
740
+ await new Promise((r) => setTimeout(r, 1e3));
741
+ }
742
+ const html = await page.evaluate(() => document.documentElement.outerHTML || document.body.innerHTML);
743
+ log(" Sub-page fetched: " + (html.length / 1024).toFixed(1) + " KB");
744
+ return html;
409
745
  }
410
746
  var init_capture = __esm({
411
747
  "src/exporter/capture.ts"() {
412
748
  "use strict";
413
749
  init_config();
414
750
  init_logger();
751
+ init_platforms();
415
752
  }
416
753
  });
417
754
 
@@ -870,11 +1207,12 @@ async function printSummary(exporter) {
870
1207
  count("styles"),
871
1208
  count("data")
872
1209
  ]);
1210
+ const G = chalk4.hex("#D4A017");
873
1211
  console.log("");
874
1212
  if (!isSmall) {
875
- console.log(chalk4.magenta(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
876
- console.log(chalk4.magenta(" \u2502") + chalk4.bold.white(" Export Summary ") + chalk4.magenta("\u2502"));
877
- console.log(chalk4.magenta(" \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"));
1213
+ console.log(G(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
1214
+ console.log(G(" \u2502") + chalk4.bold.white(" Export Summary ") + G("\u2502"));
1215
+ console.log(G(" \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"));
878
1216
  } else {
879
1217
  console.log(chalk4.bold.white(" Export Summary:"));
880
1218
  }
@@ -885,7 +1223,7 @@ async function printSummary(exporter) {
885
1223
  if (isSmall) {
886
1224
  console.log(` ${chalk4.yellow(label)} ${chalk4.white(String(count2))} ${chalk4.gray(type)}`);
887
1225
  } else {
888
- console.log(` ${chalk4.magenta("\u2502")} ${chalk4.yellow(l)} ${chalk4.white(c)} ${chalk4.gray(type.padEnd(20))} ${chalk4.magenta("\u2502")}`);
1226
+ console.log(` ${G("\u2502")} ${chalk4.yellow(l)} ${chalk4.white(c)} ${chalk4.gray(type.padEnd(20))} ${G("\u2502")}`);
889
1227
  }
890
1228
  };
891
1229
  row("styles/", styles, "CSS files");
@@ -897,19 +1235,19 @@ async function printSummary(exporter) {
897
1235
  row("assets/misc/", misc, "misc files");
898
1236
  row("data/", data, "data files");
899
1237
  if (!isSmall) {
900
- console.log(chalk4.magenta(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
1238
+ console.log(G(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
901
1239
  }
902
1240
  console.log("");
903
1241
  const quotedDir = `"${exporter.outDir}"`;
904
1242
  if (!isSmall) {
905
- console.log(chalk4.magenta(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
906
- console.log(chalk4.magenta(" \u2502") + chalk4.bold.white(" To serve locally ") + chalk4.magenta("\u2502"));
907
- console.log(chalk4.magenta(" \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"));
908
- console.log(` ${chalk4.magenta("\u2502")} ${chalk4.cyan("cd " + path4.basename(exporter.outDir) + " && node serve.cjs").padEnd(44)} ${chalk4.magenta("\u2502")}`);
909
- console.log(chalk4.magenta(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
1243
+ console.log(G(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
1244
+ console.log(G(" \u2502") + chalk4.bold.white(" To serve locally ") + G("\u2502"));
1245
+ console.log(G(" \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"));
1246
+ console.log(` ${G("\u2502")} ${chalk4.hex("#D4A017")("cd " + path4.basename(exporter.outDir) + " && node serve.cjs").padEnd(44)} ${G("\u2502")}`);
1247
+ console.log(G(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
910
1248
  } else {
911
1249
  console.log(chalk4.bold.white(" To serve locally:"));
912
- console.log(` ${chalk4.cyan("cd " + path4.basename(exporter.outDir) + " && node serve.cjs")}`);
1250
+ console.log(` ${chalk4.hex("#D4A017")("cd " + path4.basename(exporter.outDir) + " && node serve.cjs")}`);
913
1251
  }
914
1252
  console.log("");
915
1253
  console.log(chalk4.gray(" NOTE: Must be served via HTTP for JS modules to work."));
@@ -921,263 +1259,52 @@ var init_summary = __esm({
921
1259
  }
922
1260
  });
923
1261
 
924
- // src/platforms/framer.ts
925
- var FONT_EXTS, framer;
926
- var init_framer = __esm({
927
- "src/platforms/framer.ts"() {
928
- "use strict";
929
- FONT_EXTS = [".woff2", ".woff", ".ttf", ".otf"];
930
- framer = {
931
- name: "framer",
932
- displayName: "Framer",
933
- detectByUrl(url) {
934
- return /\.framer\.(app|website)|framercanvas\.com/.test(url);
935
- },
936
- detectByHtml(html) {
937
- return html.includes('id="main"') && (html.includes("framerstatic.com") || html.includes("framerusercontent.com"));
938
- },
939
- stripDomains: ["events.framer.com", "api.framer.com", "collect.frameranalytics.com"],
940
- stripSelectors: [
941
- 'script[src*="events.framer.com"]',
942
- "#__framer-badge-container",
943
- "#__framer-badge",
944
- 'link[href*="canvas-sandbox"]',
945
- 'script[src*="framer.com/bootstrap"]'
946
- ],
947
- stripPatterns: [
948
- /<div id="__framer-badge-container"[^>]*><\/div>/g,
949
- /<script>try\{if\(localStorage\.get\("__framer_force_showing_editorbar_since"\)\)[^<]*<\/script>/g
950
- ],
951
- hydrationTimeout: 1e4,
952
- needsHydrationCheck: true,
953
- mapAssetDir(host, pathname, ext) {
954
- if (host.includes("framerusercontent.com")) {
955
- if (pathname.startsWith("/images/")) return "assets/images";
956
- if (pathname.startsWith("/assets/")) {
957
- return FONT_EXTS.includes(ext) ? "assets/fonts" : "assets/misc";
958
- }
959
- if (pathname.startsWith("/sites/")) {
960
- if (ext === ".mjs" || ext === ".js") return "scripts/vendor";
961
- if (ext === ".json") return "data";
962
- if (ext === ".css") return "styles";
963
- if (ext === ".framercms") return "data";
964
- return "assets/misc";
965
- }
966
- if (pathname.startsWith("/modules/")) {
967
- return ext === ".framercms" ? "data" : "scripts/modules";
968
- }
969
- return "assets/misc";
970
- }
971
- if (host.includes("app.framerstatic.com")) {
972
- if (ext === ".css") return "styles";
973
- if (ext === ".mjs" || ext === ".js") return "scripts/vendor";
974
- if (ext === ".woff2" || ext === ".woff") return "assets/fonts";
975
- if (ext === ".png" || ext === ".svg") return "assets/images";
976
- return "assets/misc";
977
- }
978
- if (host.includes("framercanvas.com") || host.includes("framer.com")) {
979
- if (ext === ".mjs" || ext === ".js") return "scripts/vendor";
980
- if (ext === ".css") return "styles";
981
- return "assets/misc";
982
- }
983
- return null;
984
- }
985
- };
986
- }
987
- });
988
-
989
- // src/platforms/webflow.ts
990
- var IMG_EXTS, FONT_EXTS2, VIDEO_EXTS, webflow;
991
- var init_webflow = __esm({
992
- "src/platforms/webflow.ts"() {
993
- "use strict";
994
- IMG_EXTS = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif"];
995
- FONT_EXTS2 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
996
- VIDEO_EXTS = [".mp4", ".webm", ".ogg"];
997
- webflow = {
998
- name: "webflow",
999
- displayName: "Webflow",
1000
- detectByUrl(url) {
1001
- return /\.webflow\.(io|com)/.test(url);
1002
- },
1003
- detectByHtml(html) {
1004
- return html.includes("data-wf-site") || html.includes("w-webflow-badge") || html.includes("Webflow") || html.includes("webflow.js");
1005
- },
1006
- stripDomains: ["js-agent.newrelic.com", "cdn.heapanalytics.com", "tr.snapchat.com"],
1007
- stripSelectors: [".w-webflow-badge", 'link[href*="editorbar"]'],
1008
- stripPatterns: [
1009
- /<a[^>]*class="[^"]*w-webflow-badge[^"]*"[^>]*>[\s\S]*?<\/a>/g,
1010
- /<a[^>]*href="[^"]*webflow\.com\?utm_campaign=brandjs[^"]*"[^>]*>[\s\S]*?<\/a>/g,
1011
- /<style>[^<]*\.w-webflow-badge[^<]*<\/style>/g,
1012
- /Powered by <a[^>]*href="[^"]*webflow\.com"[^>]*>[^<]*<\/a>/g,
1013
- /<!-- This site was created in Webflow\.[^>]*-->/g,
1014
- /<html([^>]*) data-wf-domain="[^"]*"/g,
1015
- /<html([^>]*) data-wf-page="[^"]*"/g,
1016
- /<html([^>]*) data-wf-site="[^"]*"/g,
1017
- /<html([^>]*) data-wf-status="[^"]*"/g,
1018
- /<meta[^>]*content="Webflow"[^>]*>/g
1019
- ],
1020
- hydrationTimeout: 2e3,
1021
- needsHydrationCheck: false,
1022
- mapAssetDir(host, pathname, ext) {
1023
- if (host.includes("website-files.com") || host.includes("webflow.com") || host.includes("uploads-ssl.webflow.com")) {
1024
- if (VIDEO_EXTS.includes(ext)) return "assets/videos";
1025
- if (pathname.includes("/images/") || IMG_EXTS.includes(ext)) return "assets/images";
1026
- if (pathname.includes("/css/") || ext === ".css") return "styles";
1027
- if (pathname.includes("/js/") || ext === ".js") return "scripts/vendor";
1028
- if (pathname.includes("/gsap/") || pathname.includes("/plugins/")) return "scripts/vendor";
1029
- if (FONT_EXTS2.includes(ext)) return "assets/fonts";
1030
- return "assets/misc";
1031
- }
1032
- if (host.includes("d3e54v103j8qbb.cloudfront.net")) {
1033
- if (ext === ".js") return "scripts/vendor";
1034
- if (ext === ".css") return "styles";
1035
- if (IMG_EXTS.includes(ext)) return "assets/images";
1036
- return "assets/misc";
1037
- }
1038
- return null;
1039
- }
1040
- };
1041
- }
1042
- });
1043
-
1044
- // src/platforms/wix.ts
1045
- var IMG_EXTS2, FONT_EXTS3, VIDEO_EXTS2, wix;
1046
- var init_wix = __esm({
1047
- "src/platforms/wix.ts"() {
1048
- "use strict";
1049
- IMG_EXTS2 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
1050
- FONT_EXTS3 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
1051
- VIDEO_EXTS2 = [".mp4", ".webm", ".ogg"];
1052
- wix = {
1053
- name: "wix",
1054
- displayName: "Wix",
1055
- detectByUrl(url) {
1056
- return /\.wixsite\.com|\.wix\.com/.test(url);
1057
- },
1058
- detectByHtml(html) {
1059
- return html.includes("wix-viewer-model") || html.includes('id="WIX_ADS"') || html.includes("X-Wix-") || html.includes("Wix.com") || html.includes("wixcode-sdk");
1060
- },
1061
- stripDomains: [
1062
- "frog.wix.com",
1063
- "bi.wixapi.com",
1064
- "fed.wixcodescheduler.com",
1065
- "editor.wix.com",
1066
- "panorama.wixapps.net"
1067
- ],
1068
- stripSelectors: ["#WIX_ADS", ".wix-ads", "#wix-badge", "#SCROLL_TO_TOP"],
1069
- stripPatterns: [
1070
- /<div[^>]*class="[^"]*wix-ads[^"]*"[^>]*>[\s\S]*?<\/div>/g
1071
- ],
1072
- hydrationTimeout: 3e3,
1073
- needsHydrationCheck: false,
1074
- mapAssetDir(host, pathname, ext) {
1075
- if (host === "video.wixstatic.com") {
1076
- return "assets/videos";
1077
- }
1078
- if (host.includes("wixstatic.com")) {
1079
- if (VIDEO_EXTS2.includes(ext)) return "assets/videos";
1080
- if (pathname.includes("/images/") || IMG_EXTS2.includes(ext)) return "assets/images";
1081
- if (ext === ".css") return "styles";
1082
- if (ext === ".js") return "scripts/vendor";
1083
- if (FONT_EXTS3.includes(ext)) return "assets/fonts";
1084
- return "assets/misc";
1085
- }
1086
- if (host.includes("parastorage.com")) {
1087
- if (ext === ".js") return "scripts/vendor";
1088
- if (ext === ".css") return "styles";
1089
- if (FONT_EXTS3.includes(ext)) return "assets/fonts";
1090
- if (IMG_EXTS2.includes(ext)) return "assets/images";
1091
- return "assets/misc";
1092
- }
1093
- return null;
1094
- }
1095
- };
1096
- }
1097
- });
1098
-
1099
- // src/platforms/detect.ts
1100
- function detectByUrl(url) {
1101
- for (const platform of ALL_PLATFORMS) {
1102
- if (platform.detectByUrl(url)) return platform;
1103
- }
1104
- return null;
1105
- }
1106
- function detectByHtml(html) {
1107
- for (const platform of ALL_PLATFORMS) {
1108
- if (platform.detectByHtml(html)) return platform;
1109
- }
1110
- return null;
1111
- }
1112
- function detectPlatform(url, html) {
1113
- const byUrl = detectByUrl(url);
1114
- if (byUrl) return byUrl;
1115
- if (html) {
1116
- const byHtml = detectByHtml(html);
1117
- if (byHtml) return byHtml;
1118
- }
1119
- return framer;
1120
- }
1121
- function getPlatformByName(name) {
1122
- const map = { framer, webflow, wix };
1123
- return map[name] || framer;
1124
- }
1125
- var ALL_PLATFORMS;
1126
- var init_detect = __esm({
1127
- "src/platforms/detect.ts"() {
1128
- "use strict";
1129
- init_framer();
1130
- init_webflow();
1131
- init_wix();
1132
- ALL_PLATFORMS = [framer, webflow, wix];
1133
- }
1134
- });
1135
-
1136
- // src/platforms/index.ts
1137
- var platforms_exports = {};
1138
- __export(platforms_exports, {
1139
- detectByHtml: () => detectByHtml,
1140
- detectByUrl: () => detectByUrl,
1141
- detectPlatform: () => detectPlatform,
1142
- framer: () => framer,
1143
- getPlatformByName: () => getPlatformByName,
1144
- webflow: () => webflow,
1145
- wix: () => wix
1146
- });
1147
- var init_platforms = __esm({
1148
- "src/platforms/index.ts"() {
1149
- "use strict";
1150
- init_framer();
1151
- init_webflow();
1152
- init_wix();
1153
- init_detect();
1154
- }
1155
- });
1156
-
1157
1262
  // src/cli/cooking.ts
1158
1263
  import chalk5 from "chalk";
1159
- var SHINE_WIDTH, CookingSpinner;
1264
+ var SHINE_WIDTH, FRAME_INTERVAL, SPINNER_FRAMES, PALETTE, CookingSpinner;
1160
1265
  var init_cooking = __esm({
1161
1266
  "src/cli/cooking.ts"() {
1162
1267
  "use strict";
1163
- SHINE_WIDTH = 8;
1268
+ SHINE_WIDTH = 10;
1269
+ FRAME_INTERVAL = 140;
1270
+ SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1271
+ PALETTE = [
1272
+ (s) => chalk5.hex("#D4A017")(s),
1273
+ (s) => chalk5.hex("#E8A317")(s),
1274
+ (s) => chalk5.hex("#DAA520")(s),
1275
+ (s) => chalk5.hex("#F0C040")(s),
1276
+ (s) => chalk5.hex("#C4953A")(s),
1277
+ (s) => chalk5.hex("#E6B83C")(s),
1278
+ (s) => chalk5.hex("#C8A951")(s),
1279
+ (s) => chalk5.hex("#D9B44D")(s)
1280
+ ];
1164
1281
  CookingSpinner = class {
1165
1282
  interval = null;
1283
+ bgInterval = null;
1166
1284
  frame = 0;
1167
1285
  phase = "";
1168
1286
  active = false;
1287
+ bar = 0;
1288
+ barDir = 1;
1169
1289
  start(phase = "") {
1170
1290
  this.phase = phase;
1171
1291
  this.frame = 0;
1292
+ this.bar = 0;
1293
+ this.barDir = 1;
1172
1294
  this.active = true;
1173
1295
  this.draw();
1174
1296
  this.interval = setInterval(() => {
1175
1297
  this.frame++;
1176
1298
  this.draw();
1177
- }, 70);
1299
+ }, FRAME_INTERVAL);
1300
+ this.bgInterval = setInterval(() => {
1301
+ this.bar++;
1302
+ this.draw();
1303
+ }, 800);
1178
1304
  }
1179
1305
  update(phase) {
1180
1306
  this.phase = phase;
1307
+ if (!this.active) this.draw();
1181
1308
  }
1182
1309
  log(message) {
1183
1310
  if (this.active) {
@@ -1194,18 +1321,27 @@ var init_cooking = __esm({
1194
1321
  clearInterval(this.interval);
1195
1322
  this.interval = null;
1196
1323
  }
1324
+ if (this.bgInterval) {
1325
+ clearInterval(this.bgInterval);
1326
+ this.bgInterval = null;
1327
+ }
1197
1328
  process.stdout.write("\r\x1B[2K");
1198
1329
  }
1199
1330
  draw() {
1200
1331
  if (!this.active) return;
1201
- const text = "Cooking";
1202
- const dotCount = 1 + this.frame % 3;
1203
- const dots = ".".repeat(dotCount);
1204
- const pad = " ".repeat(3 - dotCount);
1205
- const shimmer = this.renderShimmer(text);
1206
- const dotStr = chalk5.rgb(180, 180, 180)(dots);
1207
- const phaseText = this.phase ? ` ${chalk5.gray(this.phase)}` : "";
1208
- process.stdout.write(`\r\x1B[2K ${shimmer}${dotStr}${pad}${phaseText}`);
1332
+ const spinner = SPINNER_FRAMES[this.frame % SPINNER_FRAMES.length];
1333
+ const paletteFn = PALETTE[this.frame % PALETTE.length];
1334
+ const dotPhase = Math.floor(this.frame / 3) % 4;
1335
+ const dotStr = dotPhase === 0 ? " " : dotPhase === 1 ? ". " : dotPhase === 2 ? ".. " : "...";
1336
+ const shimmer = this.renderShimmer("Cooking");
1337
+ const spinnerChar = chalk5.hex("#F0C040")(spinner);
1338
+ const barLen = 12;
1339
+ const barPos = this.bar % (barLen * 2);
1340
+ const barStr = this.renderBar(barLen, barPos);
1341
+ const phaseStr = this.phase ? ` ${chalk5.gray(this.limitLen(this.phase, 38))}` : "";
1342
+ process.stdout.write(
1343
+ `\r\x1B[2K ${spinnerChar} ${shimmer} ${paletteFn(dotStr)} ${barStr}${phaseStr}`
1344
+ );
1209
1345
  }
1210
1346
  renderShimmer(text) {
1211
1347
  let result = "";
@@ -1214,14 +1350,36 @@ var init_cooking = __esm({
1214
1350
  const dist = Math.abs(i - pos);
1215
1351
  if (dist < SHINE_WIDTH) {
1216
1352
  const t = 1 - dist / SHINE_WIDTH;
1217
- const gray = Math.floor(180 + t * 75);
1218
- result += chalk5.rgb(gray, gray, gray)(text[i]);
1353
+ const g = Math.floor(160 + t * 95);
1354
+ result += chalk5.rgb(g, g, g)(text[i]);
1219
1355
  } else {
1220
- result += chalk5.rgb(180, 180, 180)(text[i]);
1356
+ result += chalk5.rgb(160, 160, 160)(text[i]);
1221
1357
  }
1222
1358
  }
1223
1359
  return result;
1224
1360
  }
1361
+ renderBar(len, pos) {
1362
+ let s = "";
1363
+ for (let i = 0; i < len; i++) {
1364
+ const dist = Math.abs(i - pos);
1365
+ if (dist < 3) {
1366
+ const t = 1 - dist / 3;
1367
+ const r = Math.floor(200 + t * 55);
1368
+ const g = Math.floor(160 + t * 50);
1369
+ const b = Math.floor(20 + t * 30);
1370
+ s += chalk5.rgb(r, g, b)("\u258C");
1371
+ } else if (dist < 6) {
1372
+ s += chalk5.rgb(140, 100, 20)("\u258C");
1373
+ } else {
1374
+ s += chalk5.rgb(50, 35, 10)(" ");
1375
+ }
1376
+ }
1377
+ return `[${s}]`;
1378
+ }
1379
+ limitLen(s, max) {
1380
+ if (s.length <= max) return s;
1381
+ return s.slice(0, max - 3) + "\u2026";
1382
+ }
1225
1383
  };
1226
1384
  }
1227
1385
  });
@@ -1292,11 +1450,14 @@ var init_exporter = __esm({
1292
1450
  this.platform = detectPlatform(siteUrl);
1293
1451
  }
1294
1452
  }
1295
- async run() {
1296
- console.log(chalk6.bold.magenta("\n Cooksite v4 - Full Mirror & Clean Export\n"));
1453
+ async run(includeSubpages = false) {
1454
+ console.log(chalk6.bold.hex("#D4A017")("\n Cooksite v4 - Full Mirror & Clean Export\n"));
1297
1455
  info("Source : " + chalk6.underline(this.siteUrl));
1298
1456
  info("Output : " + chalk6.yellow(this.outDir));
1299
- info("Platform : " + chalk6.magenta(this.platform.displayName));
1457
+ info("Platform : " + chalk6.hex("#D4A017")(this.platform.displayName));
1458
+ if (includeSubpages) {
1459
+ info("Subpages : " + chalk6.green("enabled"));
1460
+ }
1300
1461
  console.log("");
1301
1462
  this.cooking = new CookingSpinner();
1302
1463
  setCooking(this.cooking);
@@ -1310,7 +1471,8 @@ var init_exporter = __esm({
1310
1471
  "styles",
1311
1472
  "scripts/vendor",
1312
1473
  "scripts/modules",
1313
- "data"
1474
+ "data",
1475
+ "subpages"
1314
1476
  ]) {
1315
1477
  await fs4.mkdir(path5.join(this.outDir, d), { recursive: true });
1316
1478
  }
@@ -1327,9 +1489,13 @@ var init_exporter = __esm({
1327
1489
  const htmlDetected = detectPlatform(this.siteUrl, this.ssrHTML);
1328
1490
  if (htmlDetected.name !== this.platform.name) {
1329
1491
  this.platform = htmlDetected;
1330
- log("Platform refined: " + chalk6.magenta(this.platform.displayName) + " (from HTML analysis)");
1492
+ log("Platform refined: " + chalk6.hex("#D4A017")(this.platform.displayName) + " (from HTML analysis)");
1331
1493
  }
1332
1494
  await launchAndCapture(this);
1495
+ if (includeSubpages && this.page) {
1496
+ await this.crawlSubpages();
1497
+ }
1498
+ await closeBrowser(this);
1333
1499
  this.cooking.update("Downloading assets...");
1334
1500
  await downloadAll(this);
1335
1501
  this.cooking.update("Building output...");
@@ -1340,6 +1506,64 @@ var init_exporter = __esm({
1340
1506
  success("Export complete!");
1341
1507
  await printSummary(this);
1342
1508
  }
1509
+ async crawlSubpages() {
1510
+ this.cooking?.update("Discovering sub-pages...");
1511
+ log("Scanning page for internal links...");
1512
+ const page = this.page;
1513
+ const baseUrl = new URL(this.siteUrl);
1514
+ const baseHost = baseUrl.hostname.replace(/^www\./, "");
1515
+ const links = await page.evaluate((host) => {
1516
+ const anchors = Array.from(document.querySelectorAll("a[href]"));
1517
+ const hrefs = /* @__PURE__ */ new Set();
1518
+ for (const a of anchors) {
1519
+ const href = a.href;
1520
+ if (!href || href.startsWith("javascript:") || href.startsWith("mailto:") || href.startsWith("tel:") || href.startsWith("#")) continue;
1521
+ try {
1522
+ const u = new URL(href);
1523
+ const h = u.hostname.replace(/^www\./, "");
1524
+ if (h === host && u.pathname !== "/" && u.pathname !== "" && !u.pathname.startsWith("/#")) {
1525
+ hrefs.add(href.split("#")[0]);
1526
+ }
1527
+ } catch {
1528
+ }
1529
+ }
1530
+ return Array.from(hrefs);
1531
+ }, baseHost);
1532
+ log("Found " + links.length + " sub-page links");
1533
+ const uniqueLinks = [...new Set(links)].slice(0, 50);
1534
+ if (uniqueLinks.length === 0) {
1535
+ log("No sub-pages to crawl");
1536
+ return;
1537
+ }
1538
+ for (let i = 0; i < uniqueLinks.length; i++) {
1539
+ const link = uniqueLinks[i];
1540
+ this.cooking?.update("Crawling sub-page " + (i + 1) + "/" + uniqueLinks.length);
1541
+ try {
1542
+ const html = await captureSubpage(page, link, {
1543
+ needsHydrationCheck: this.platform.needsHydrationCheck,
1544
+ hydrationTimeout: this.platform.hydrationTimeout
1545
+ });
1546
+ const slug = this.deriveSlug(link, baseUrl);
1547
+ const filename = slug + ".html";
1548
+ const filepath = path5.join(this.outDir, "subpages", filename);
1549
+ await fs4.writeFile(filepath, html, "utf-8");
1550
+ log(" Saved: subpages/" + filename);
1551
+ } catch (e) {
1552
+ log(" Skipped " + link + ": " + e.message);
1553
+ }
1554
+ }
1555
+ success("Sub-pages crawled: " + uniqueLinks.length);
1556
+ }
1557
+ deriveSlug(link, baseUrl) {
1558
+ try {
1559
+ const u = new URL(link);
1560
+ let pathname = u.pathname.replace(/\/+$/, "").replace(/^\//, "");
1561
+ if (!pathname) return "index";
1562
+ return pathname.replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_+/g, "_") || "page";
1563
+ } catch {
1564
+ return "page";
1565
+ }
1566
+ }
1343
1567
  };
1344
1568
  }
1345
1569
  });
@@ -1356,10 +1580,10 @@ async function select(question, options, defaultIndex = 0) {
1356
1580
  stdout.write(`\x1B[${options.length + 1}A`);
1357
1581
  stdout.write("\x1B[J");
1358
1582
  }
1359
- console.log(` ${chalk7.cyan("?")} ${chalk7.white.bold(question)}`);
1583
+ console.log(` ${chalk7.hex("#D4A017")("?")} ${chalk7.white.bold(question)}`);
1360
1584
  for (let i = 0; i < options.length; i++) {
1361
1585
  if (i === selected) {
1362
- console.log(` ${chalk7.cyan(">")} ${chalk7.white.bold(options[i].label)}`);
1586
+ console.log(` ${chalk7.hex("#D4A017")(">")} ${chalk7.white.bold(options[i].label)}`);
1363
1587
  } else {
1364
1588
  console.log(` ${chalk7.gray(options[i].label)}`);
1365
1589
  }
@@ -1383,7 +1607,7 @@ async function select(question, options, defaultIndex = 0) {
1383
1607
  stdin.pause();
1384
1608
  stdout.write(`\x1B[${options.length + 1}A`);
1385
1609
  stdout.write("\x1B[J");
1386
- console.log(` ${chalk7.green("\u2713")} ${chalk7.white.bold(question)} ${chalk7.cyan(options[selected].label)}
1610
+ console.log(` ${chalk7.green("\u2713")} ${chalk7.white.bold(question)} ${chalk7.hex("#D4A017")(options[selected].label)}
1387
1611
  `);
1388
1612
  resolve(options[selected].value);
1389
1613
  } else if (key.ctrl && key.name === "c" || key.name === "escape") {
@@ -1418,7 +1642,7 @@ function getWidth3() {
1418
1642
  function drawHeader(title) {
1419
1643
  const width = getWidth3();
1420
1644
  const isSmall = width < 50;
1421
- const magenta = chalk8.magenta;
1645
+ const magenta = chalk8.hex("#D4A017");
1422
1646
  const boldWhite = chalk8.bold.white;
1423
1647
  if (isSmall) {
1424
1648
  console.log(`
@@ -1436,7 +1660,7 @@ ${magenta("\u25CF")} ${boldWhite(title)}`);
1436
1660
  function line(label, value) {
1437
1661
  const width = getWidth3();
1438
1662
  const isSmall = width < 50;
1439
- const magenta = chalk8.magenta;
1663
+ const magenta = chalk8.hex("#D4A017");
1440
1664
  if (isSmall) {
1441
1665
  console.log(` ${chalk8.bold(label)}: ${chalk8.yellow(value)}`);
1442
1666
  return;
@@ -1450,7 +1674,7 @@ async function runSetup(legacyMode = false) {
1450
1674
  const rl = readline2.createInterface({ input: stdin2, output: stdout2 });
1451
1675
  const ask = async (question, defaultVal) => {
1452
1676
  const suffix = defaultVal ? chalk8.gray(` (${defaultVal})`) : "";
1453
- const prompt = ` ${chalk8.cyan("?")} ${chalk8.white.bold(question)}${suffix} ${chalk8.gray(">")} `;
1677
+ const prompt = ` ${chalk8.hex("#D4A017")("?")} ${chalk8.white.bold(question)}${suffix} ${chalk8.gray(">")} `;
1454
1678
  const answer = await rl.question(prompt);
1455
1679
  return answer.trim() || defaultVal || "";
1456
1680
  };
@@ -1472,10 +1696,10 @@ async function runSetup(legacyMode = false) {
1472
1696
  const detected = detectPlatform(siteUrl);
1473
1697
  let platformName;
1474
1698
  if (legacyMode) {
1475
- console.log(` ${chalk8.blue("i")} Auto-detected: ${chalk8.magenta(detected.displayName)}`);
1699
+ console.log(` ${chalk8.blue("i")} Auto-detected: ${chalk8.hex("#D4A017")(detected.displayName)}`);
1476
1700
  const platformInput = await ask("Platform (framer/webflow/wix)", detected.name);
1477
1701
  platformName = ["framer", "webflow", "wix"].includes(platformInput) ? platformInput : detected.name;
1478
- console.log(` ${chalk8.green("\u2713")} ${chalk8.green("Platform:")} ${chalk8.magenta(platformName)}
1702
+ console.log(` ${chalk8.green("\u2713")} ${chalk8.green("Platform:")} ${chalk8.hex("#D4A017")(platformName)}
1479
1703
  `);
1480
1704
  } else {
1481
1705
  rl.close();
@@ -1490,7 +1714,7 @@ async function runSetup(legacyMode = false) {
1490
1714
  const rl2 = legacyMode ? rl : readline2.createInterface({ input: stdin2, output: stdout2 });
1491
1715
  const ask2 = async (question, defaultVal) => {
1492
1716
  const suffix = defaultVal ? chalk8.gray(` (${defaultVal})`) : "";
1493
- const prompt = ` ${chalk8.cyan("?")} ${chalk8.white.bold(question)}${suffix} ${chalk8.gray(">")} `;
1717
+ const prompt = ` ${chalk8.hex("#D4A017")("?")} ${chalk8.white.bold(question)}${suffix} ${chalk8.gray(">")} `;
1494
1718
  const answer = await rl2.question(prompt);
1495
1719
  return answer.trim() || defaultVal || "";
1496
1720
  };
@@ -1502,10 +1726,15 @@ async function runSetup(legacyMode = false) {
1502
1726
  drawHeader("Step 4 : Options");
1503
1727
  let prettyPrint;
1504
1728
  let concurrency;
1729
+ let includeSubpages;
1505
1730
  if (legacyMode) {
1506
1731
  const prettyAnswer = await ask2("Pretty-print JS files? (y/n)", "y");
1507
1732
  prettyPrint = prettyAnswer.toLowerCase().startsWith("y");
1508
1733
  console.log(` ${chalk8.green("\u2713")} Pretty-print: ${prettyPrint ? chalk8.green("yes") : chalk8.red("no")}
1734
+ `);
1735
+ const subpagesAnswer = await ask2("Export sub-pages? (y/n)", "n");
1736
+ includeSubpages = subpagesAnswer.toLowerCase().startsWith("y");
1737
+ console.log(` ${chalk8.green("\u2713")} Sub-pages: ${includeSubpages ? chalk8.green("yes") : chalk8.red("no")}
1509
1738
  `);
1510
1739
  const concurrencyAnswer = await ask2("Download concurrency", "12");
1511
1740
  concurrency = parseInt(concurrencyAnswer, 10) || 12;
@@ -1518,6 +1747,11 @@ async function runSetup(legacyMode = false) {
1518
1747
  { label: "No", value: "no" }
1519
1748
  ]);
1520
1749
  prettyPrint = prettyVal === "yes";
1750
+ const subpagesVal = await select("Export sub-pages?", [
1751
+ { label: "No", value: "no" },
1752
+ { label: "Yes, crawl and export", value: "yes" }
1753
+ ], 0);
1754
+ includeSubpages = subpagesVal === "yes";
1521
1755
  const concurrencyVal = await select("Download concurrency", [
1522
1756
  { label: "6 (slow connection)", value: "6" },
1523
1757
  { label: "12 (default)", value: "12" },
@@ -1529,9 +1763,9 @@ async function runSetup(legacyMode = false) {
1529
1763
  const width = getWidth3();
1530
1764
  const isSmall = width < 50;
1531
1765
  if (!isSmall) {
1532
- console.log(chalk8.magenta(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
1533
- console.log(chalk8.magenta(" \u2502") + chalk8.bold.white(" Summary ") + chalk8.magenta("\u2502"));
1534
- console.log(chalk8.magenta(" \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"));
1766
+ console.log(chalk8.hex("#D4A017")(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
1767
+ console.log(chalk8.hex("#D4A017")(" \u2502") + chalk8.bold.white(" Summary ") + chalk8.hex("#D4A017")("\u2502"));
1768
+ console.log(chalk8.hex("#D4A017")(" \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"));
1535
1769
  } else {
1536
1770
  console.log(chalk8.bold.white(" Summary:"));
1537
1771
  }
@@ -1539,9 +1773,10 @@ async function runSetup(legacyMode = false) {
1539
1773
  line("Platform", platformName);
1540
1774
  line("Output", path6.resolve(outDir));
1541
1775
  line("Pretty-print", prettyPrint ? "yes" : "no");
1776
+ line("Sub-pages", includeSubpages ? "yes" : "no");
1542
1777
  line("Concurrency", String(concurrency));
1543
1778
  if (!isSmall) {
1544
- console.log(chalk8.magenta(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
1779
+ console.log(chalk8.hex("#D4A017")(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
1545
1780
  }
1546
1781
  console.log("");
1547
1782
  let startExport;
@@ -1567,7 +1802,7 @@ async function runSetup(legacyMode = false) {
1567
1802
  CFG2.concurrency = concurrency;
1568
1803
  const exporter = new FramerExporter(siteUrl, path6.resolve(outDir), platformName);
1569
1804
  exporter.prettyPrint = prettyPrint;
1570
- await exporter.run();
1805
+ await exporter.run(includeSubpages);
1571
1806
  }
1572
1807
  var init_setup = __esm({
1573
1808
  "src/cli/setup.ts"() {
@@ -1590,14 +1825,14 @@ import chalk2 from "chalk";
1590
1825
  function showHelp() {
1591
1826
  showBanner();
1592
1827
  console.log(chalk2.white.bold(" USAGE\n"));
1593
- console.log(` ${chalk2.cyan("npm run dev")} ${chalk2.gray("--")} ${chalk2.yellow("<url>")} ${chalk2.gray("[output-dir]")}`);
1594
- console.log(` ${chalk2.cyan("npm start")} ${chalk2.gray("--")} ${chalk2.yellow("<url>")} ${chalk2.gray("[output-dir]")}`);
1595
- console.log(` ${chalk2.cyan("framer-export")} ${chalk2.yellow("<url>")} ${chalk2.gray("[output-dir]")}`);
1828
+ console.log(` ${chalk2.hex("#D4A017")("framer-export")} ${chalk2.yellow("<url>")} ${chalk2.gray("[output-dir]")}`);
1829
+ console.log(` ${chalk2.hex("#D4A017")("fexport")} ${chalk2.yellow("<url>")} ${chalk2.gray("[output-dir]")}`);
1596
1830
  console.log("");
1597
1831
  console.log(chalk2.white.bold(" OPTIONS\n"));
1598
1832
  const opts = [
1599
1833
  ["--setup", "Launch the interactive setup assistant"],
1600
1834
  ["--platform <p>", "Force platform: framer, webflow, wix"],
1835
+ ["--subpages", "Crawl and export sub-pages"],
1601
1836
  ["--legacy-mode", "Use text input instead of arrow selection"],
1602
1837
  ["--help, -h", "Show this help message"]
1603
1838
  ];
@@ -1607,21 +1842,22 @@ function showHelp() {
1607
1842
  console.log("");
1608
1843
  console.log(chalk2.white.bold(" SUPPORTED PLATFORMS\n"));
1609
1844
  const platforms = [
1610
- ["Framer", "Auto-detected via .framer.app / .framer.ai URLs"],
1845
+ ["Framer", "Auto-detected via .framer.app / .framer.website URLs"],
1611
1846
  ["Webflow", "Auto-detected via .webflow.io URLs"],
1612
- ["Wix", "Auto-detected via .wixsite.com URLs"]
1847
+ ["Wix", "Auto-detected via .wixsite.com URLs + HTML analysis"]
1613
1848
  ];
1614
1849
  for (const [name, desc] of platforms) {
1615
- console.log(` ${chalk2.magenta(name.padEnd(12))} ${chalk2.gray(desc)}`);
1850
+ console.log(` ${chalk2.hex("#D4A017")(name.padEnd(12))} ${chalk2.gray(desc)}`);
1616
1851
  }
1617
1852
  console.log("");
1618
1853
  console.log(chalk2.white.bold(" EXAMPLES\n"));
1619
- console.log(` ${chalk2.gray("$")} ${chalk2.cyan("framer-export")} ${chalk2.yellow("https://mysite.framer.app")}`);
1620
- console.log(` ${chalk2.gray("$")} ${chalk2.cyan("framer-export")} ${chalk2.yellow("https://mysite.webflow.io")}`);
1621
- console.log(` ${chalk2.gray("$")} ${chalk2.cyan("framer-export")} ${chalk2.yellow("https://user.wixsite.com/my-site")}`);
1622
- console.log(` ${chalk2.gray("$")} ${chalk2.cyan("framer-export")} ${chalk2.green("--platform webflow")} ${chalk2.yellow("https://custom.com")}`);
1623
- console.log(` ${chalk2.gray("$")} ${chalk2.cyan("framer-export")} ${chalk2.green("--setup")}`);
1624
- console.log(` ${chalk2.gray("$")} ${chalk2.cyan("framer-export")} ${chalk2.green("--setup --legacy-mode")}`);
1854
+ console.log(` ${chalk2.gray("$")} ${chalk2.hex("#D4A017")("framer-export")} ${chalk2.yellow("https://mysite.framer.app")}`);
1855
+ console.log(` ${chalk2.gray("$")} ${chalk2.hex("#D4A017")("framer-export")} ${chalk2.yellow("https://mysite.webflow.io")}`);
1856
+ console.log(` ${chalk2.gray("$")} ${chalk2.hex("#D4A017")("framer-export")} ${chalk2.yellow("https://user.wixsite.com/my-site")}`);
1857
+ console.log(` ${chalk2.gray("$")} ${chalk2.hex("#D4A017")("framer-export")} ${chalk2.green("--platform webflow")} ${chalk2.yellow("https://custom.com")}`);
1858
+ console.log(` ${chalk2.gray("$")} ${chalk2.hex("#D4A017")("framer-export")} ${chalk2.green("--subpages")} ${chalk2.yellow("https://mysite.com")}`);
1859
+ console.log(` ${chalk2.gray("$")} ${chalk2.hex("#D4A017")("framer-export")} ${chalk2.green("--setup")}`);
1860
+ console.log(` ${chalk2.gray("$")} ${chalk2.hex("#D4A017")("framer-export")} ${chalk2.green("--setup --legacy-mode")}`);
1625
1861
  console.log("");
1626
1862
  }
1627
1863
 
@@ -1686,10 +1922,10 @@ async function main() {
1686
1922
  console.log(` ${chalk9.white.bold("Framer Export")} ${chalk9.gray(`v${package_default.version}`)}`);
1687
1923
  console.log(` ${chalk9.white(package_default.description)}
1688
1924
  `);
1689
- console.log(` ${chalk9.white.bold("Author")} ${chalk9.cyan("Dany (danbenba)")}`);
1690
- console.log(` ${chalk9.white.bold("Portfolio")} ${chalk9.underline.cyan("https://github.com/danbenba")}`);
1691
- console.log(` ${chalk9.white.bold("GitHub")} ${chalk9.underline.cyan(package_default.repository.url.replace("git+", "").replace(".git", ""))}`);
1692
- console.log(` ${chalk9.white.bold("npm")} ${chalk9.underline.cyan(`https://www.npmjs.com/package/${package_default.name}`)}`);
1925
+ console.log(` ${chalk9.white.bold("Author")} ${chalk9.hex("#D4A017")("Dany (danbenba)")}`);
1926
+ console.log(` ${chalk9.white.bold("Portfolio")} ${chalk9.underline.hex("#D4A017")("https://github.com/danbenba")}`);
1927
+ console.log(` ${chalk9.white.bold("GitHub")} ${chalk9.underline.hex("#D4A017")(package_default.repository.url.replace("git+", "").replace(".git", ""))}`);
1928
+ console.log(` ${chalk9.white.bold("npm")} ${chalk9.underline.hex("#D4A017")(`https://www.npmjs.com/package/${package_default.name}`)}`);
1693
1929
  console.log(` ${chalk9.white.bold("License")} ${chalk9.green(package_default.license)}`);
1694
1930
  console.log(` ${chalk9.white.bold("Node")} ${chalk9.gray(`>=${package_default.engines.node}`)}`);
1695
1931
  console.log("");
@@ -1702,7 +1938,7 @@ async function main() {
1702
1938
  console.log(
1703
1939
  ` ${chalk9.default.yellow("\u21B3")} Update available: ${chalk9.default.gray(VERSION)} \u2192 ${chalk9.default.green(latest)}`
1704
1940
  );
1705
- console.log(` ${chalk9.default.gray(" Run:")} ${chalk9.default.cyan("npm i -g framer-export@latest")}`);
1941
+ console.log(` ${chalk9.default.hex("#D4A017")(" Run:")} ${chalk9.default.hex("#B8860B")("npm i -g framer-export@latest")}`);
1706
1942
  console.log("");
1707
1943
  });
1708
1944
  });
@@ -1723,6 +1959,7 @@ async function main() {
1723
1959
  return;
1724
1960
  }
1725
1961
  const platformOverride = extractFlag(args, "--platform");
1962
+ const includeSubpages = hasFlag(args, "--subpages");
1726
1963
  showBanner();
1727
1964
  const url = args[0];
1728
1965
  try {
@@ -1740,7 +1977,7 @@ async function main() {
1740
1977
  const defaultDir = deriveOutputName2(url, detected);
1741
1978
  const out = args[1] || `./${defaultDir}`;
1742
1979
  try {
1743
- await new FramerExporter2(url, path7.resolve(out), platformOverride || void 0).run();
1980
+ await new FramerExporter2(url, path7.resolve(out), platformOverride || void 0).run(includeSubpages);
1744
1981
  } catch (e) {
1745
1982
  const chalk9 = (await import("chalk")).default;
1746
1983
  console.log(`