framer-export 4.0.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
@@ -9,6 +9,79 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
+ // package.json
13
+ var package_default;
14
+ var init_package = __esm({
15
+ "package.json"() {
16
+ package_default = {
17
+ name: "framer-export",
18
+ version: "4.3.0",
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
+ type: "module",
21
+ main: "dist/cli/index.js",
22
+ types: "dist/cli/index.d.ts",
23
+ bin: {
24
+ "framer-export": "./bin/framer-export.js",
25
+ fexport: "./bin/fexport.js",
26
+ framerexport: "./bin/framerexport.js"
27
+ },
28
+ files: [
29
+ "dist/",
30
+ "bin/",
31
+ "LICENSE",
32
+ "README.md"
33
+ ],
34
+ scripts: {
35
+ start: "tsx src/cli/index.ts",
36
+ dev: "tsx src/cli/index.ts",
37
+ build: "tsup",
38
+ typecheck: "tsc --noEmit",
39
+ format: 'prettier --write "src/**/*.ts"',
40
+ prepublishOnly: "npm run build",
41
+ prepack: "npm run build"
42
+ },
43
+ keywords: [
44
+ "framer",
45
+ "webflow",
46
+ "wix",
47
+ "exporter",
48
+ "export",
49
+ "scraper",
50
+ "mirror",
51
+ "site-downloader",
52
+ "site-export",
53
+ "cli"
54
+ ],
55
+ repository: {
56
+ type: "git",
57
+ url: "git+https://github.com/danbenba/FramerExport.git"
58
+ },
59
+ homepage: "https://github.com/danbenba/FramerExport#readme",
60
+ bugs: {
61
+ url: "https://github.com/danbenba/FramerExport/issues"
62
+ },
63
+ license: "MIT",
64
+ author: "Dany (danbenba)",
65
+ engines: {
66
+ node: ">=18.0.0"
67
+ },
68
+ dependencies: {
69
+ chalk: "^5.3.0",
70
+ ora: "^8.0.0",
71
+ prettier: "^3.2.0",
72
+ puppeteer: "^22.0.0",
73
+ "readline-promise": "^1.0.5"
74
+ },
75
+ devDependencies: {
76
+ "@types/node": "^22.0.0",
77
+ tsup: "^8.5.1",
78
+ tsx: "^4.7.0",
79
+ typescript: "^5.4.0"
80
+ }
81
+ };
82
+ }
83
+ });
84
+
12
85
  // src/cli/banner.ts
13
86
  import chalk from "chalk";
14
87
  function getWidth() {
@@ -19,26 +92,26 @@ function showBanner() {
19
92
  const isSmall = width < 65;
20
93
  if (isSmall) {
21
94
  console.log(`
22
- ${chalk.cyan.bold("F-EXPORT")} ${chalk.gray("v4.0.1")}`);
95
+ ${chalk.hex("#D4A017").bold("F-EXPORT")} ${chalk.gray(`v${package_default.version}`)}`);
23
96
  console.log(` ${chalk.white.bold("Framer \xB7 Webflow \xB7 Wix Exporter")}
24
97
  `);
25
98
  return;
26
99
  }
27
- const magenta = chalk.magenta;
28
- const cyan = chalk.cyan.bold;
100
+ const gold = chalk.hex("#D4A017").bold;
29
101
  const gray = chalk.gray;
30
102
  console.log("");
31
103
  ASCII_ART.forEach((line2) => {
32
- console.log(" " + cyan(line2));
104
+ console.log(" " + gold(line2));
33
105
  });
34
106
  console.log("");
35
- console.log(` ${gray("v4.0.1")} ${chalk.white.bold("Framer \xB7 Webflow \xB7 Wix Exporter")}
107
+ console.log(` ${gray(`v${package_default.version}`)} ${chalk.white.bold("Framer \xB7 Webflow \xB7 Wix Exporter")}
36
108
  `);
37
109
  }
38
110
  var ASCII_ART;
39
111
  var init_banner = __esm({
40
112
  "src/cli/banner.ts"() {
41
113
  "use strict";
114
+ init_package();
42
115
  ASCII_ART = [
43
116
  "\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
44
117
  "\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D",
@@ -190,6 +263,13 @@ import chalk3 from "chalk";
190
263
  function setCooking(spinner) {
191
264
  _cooking = spinner;
192
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
+ }
193
273
  function output(line2) {
194
274
  if (_cooking) {
195
275
  _cooking.log(line2);
@@ -197,20 +277,317 @@ function output(line2) {
197
277
  console.log(line2);
198
278
  }
199
279
  }
200
- var _cooking, T, log, info, warn, success;
280
+ var _cooking, T, TIME_COLORS, TAG_ROTATION, MSG_COLORS, _msgIdx, log, info, warn, success;
201
281
  var init_logger = __esm({
202
282
  "src/logger/index.ts"() {
203
283
  "use strict";
204
284
  _cooking = null;
205
285
  T = () => (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
206
- log = (m) => output(`${chalk3.gray(`[${T()}]`)} ${chalk3.cyan("[LOG]")} ${m}`);
207
- 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
+ };
208
321
  warn = (m) => {
209
- 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))}`;
210
323
  if (_cooking) _cooking.log(line2);
211
324
  else console.warn(line2);
212
325
  };
213
- 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();
214
591
  }
215
592
  });
216
593
 
@@ -269,6 +646,12 @@ async function launchAndCapture(exporter) {
269
646
  });
270
647
  success("Page loaded (networkidle2)");
271
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
+ }
272
655
  exporter.cooking?.update("Waiting for " + exporter.platform.displayName + " hydration...");
273
656
  if (exporter.platform.needsHydrationCheck) {
274
657
  log("Checking for #main element hydration...");
@@ -330,15 +713,42 @@ async function launchAndCapture(exporter) {
330
713
  const imgCount = [...exporter.assets.entries.values()].filter((e) => e.localPath.startsWith("assets/images")).length;
331
714
  const fontCount = [...exporter.assets.entries.values()].filter((e) => e.localPath.startsWith("assets/fonts")).length;
332
715
  log(" CSS: " + cssCount + " | JS: " + jsCount + " | Images: " + imgCount + " | Fonts: " + fontCount);
333
- await exporter.browser.close();
334
- exporter.browser = null;
335
- 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;
336
745
  }
337
746
  var init_capture = __esm({
338
747
  "src/exporter/capture.ts"() {
339
748
  "use strict";
340
749
  init_config();
341
750
  init_logger();
751
+ init_platforms();
342
752
  }
343
753
  });
344
754
 
@@ -694,417 +1104,207 @@ async function buildOutput(exporter) {
694
1104
  }
695
1105
  async function rewriteDownloadedFiles(exporter) {
696
1106
  const dirs = ["scripts/vendor", "scripts/modules", "styles"];
697
- let rewritten = 0;
698
- for (const dir of dirs) {
699
- const fullDir = path3.join(exporter.outDir, dir);
700
- let files;
701
- try {
702
- files = await fs2.readdir(fullDir);
703
- } catch {
704
- continue;
705
- }
706
- for (const file of files) {
707
- const ext = path3.extname(file).toLowerCase();
708
- if (![".mjs", ".js", ".css"].includes(ext)) continue;
709
- const filePath = path3.join(fullDir, file);
710
- try {
711
- let content = await fs2.readFile(filePath, "utf-8");
712
- const before = content;
713
- content = exporter.assets.rewrite(content, dir);
714
- if (content !== before) {
715
- await fs2.writeFile(filePath, content);
716
- rewritten++;
717
- }
718
- } catch {
719
- }
720
- }
721
- }
722
- log("Rewrote URLs in " + rewritten + " JS/CSS files");
723
- }
724
- async function prettifyDownloadedJS(exporter) {
725
- const dirs = ["scripts/vendor", "scripts/modules"];
726
- let count = 0;
727
- let total = 0;
728
- for (const dir of dirs) {
729
- const fullDir = path3.join(exporter.outDir, dir);
730
- let files;
731
- try {
732
- files = await fs2.readdir(fullDir);
733
- } catch {
734
- continue;
735
- }
736
- const jsFiles = files.filter((f) => {
737
- const ext = path3.extname(f).toLowerCase();
738
- return ext === ".mjs" || ext === ".js";
739
- });
740
- total += jsFiles.length;
741
- for (const file of jsFiles) {
742
- const filePath = path3.join(fullDir, file);
743
- try {
744
- const raw = await fs2.readFile(filePath, "utf-8");
745
- const nlRatio = (raw.match(/\n/g) || []).length / raw.length;
746
- if (nlRatio > 0.05) {
747
- count++;
748
- continue;
749
- }
750
- const pretty = await prettifyJS(raw);
751
- await fs2.writeFile(filePath, pretty, "utf-8");
752
- count++;
753
- if (count % 5 === 0) {
754
- exporter.cooking?.update("Pretty-printing... (" + count + "/" + total + ")");
755
- }
756
- } catch (e) {
757
- warn("Pretty-print skipped: " + file + " - " + e.message);
758
- count++;
759
- }
760
- }
761
- }
762
- success("Formatted " + count + "/" + total + " JS/MJS files");
763
- }
764
- var init_output = __esm({
765
- "src/exporter/output.ts"() {
766
- "use strict";
767
- init_logger();
768
- init_prettify();
769
- init_template();
770
- }
771
- });
772
-
773
- // src/exporter/summary.ts
774
- import fs3 from "fs/promises";
775
- import path4 from "path";
776
- import chalk4 from "chalk";
777
- function getWidth2() {
778
- return process.stdout.columns || 80;
779
- }
780
- async function printSummary(exporter) {
781
- const width = getWidth2();
782
- const isSmall = width < 50;
783
- const count = async (d) => {
784
- try {
785
- return (await fs3.readdir(path4.join(exporter.outDir, d))).length;
786
- } catch {
787
- return 0;
788
- }
789
- };
790
- const [imgs, fonts, videos, misc, scripts, vendor, styles, data] = await Promise.all([
791
- count("assets/images"),
792
- count("assets/fonts"),
793
- count("assets/videos"),
794
- count("assets/misc"),
795
- count("scripts/modules"),
796
- count("scripts/vendor"),
797
- count("styles"),
798
- count("data")
799
- ]);
800
- console.log("");
801
- if (!isSmall) {
802
- 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"));
803
- console.log(chalk4.magenta(" \u2502") + chalk4.bold.white(" Export Summary ") + chalk4.magenta("\u2502"));
804
- 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"));
805
- } else {
806
- console.log(chalk4.bold.white(" Export Summary:"));
807
- }
808
- const row = (label, count2, type) => {
809
- if (count2 === 0) return;
810
- const l = label.padEnd(18);
811
- const c = String(count2).padStart(3);
812
- if (isSmall) {
813
- console.log(` ${chalk4.yellow(label)} ${chalk4.white(String(count2))} ${chalk4.gray(type)}`);
814
- } else {
815
- console.log(` ${chalk4.magenta("\u2502")} ${chalk4.yellow(l)} ${chalk4.white(c)} ${chalk4.gray(type.padEnd(20))} ${chalk4.magenta("\u2502")}`);
816
- }
817
- };
818
- row("styles/", styles, "CSS files");
819
- row("scripts/vendor/", vendor, "JS modules");
820
- row("scripts/modules/", scripts, "components");
821
- row("assets/images/", imgs, "images");
822
- row("assets/videos/", videos, "videos");
823
- row("assets/fonts/", fonts, "fonts");
824
- row("assets/misc/", misc, "misc files");
825
- row("data/", data, "data files");
826
- if (!isSmall) {
827
- 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"));
828
- }
829
- console.log("");
830
- const quotedDir = `"${exporter.outDir}"`;
831
- if (!isSmall) {
832
- 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"));
833
- console.log(chalk4.magenta(" \u2502") + chalk4.bold.white(" To serve locally ") + chalk4.magenta("\u2502"));
834
- 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"));
835
- console.log(` ${chalk4.magenta("\u2502")} ${chalk4.cyan("cd " + path4.basename(exporter.outDir) + " && node serve.cjs").padEnd(44)} ${chalk4.magenta("\u2502")}`);
836
- 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"));
837
- } else {
838
- console.log(chalk4.bold.white(" To serve locally:"));
839
- console.log(` ${chalk4.cyan("cd " + path4.basename(exporter.outDir) + " && node serve.cjs")}`);
840
- }
841
- console.log("");
842
- console.log(chalk4.gray(" NOTE: Must be served via HTTP for JS modules to work."));
843
- console.log("");
844
- }
845
- var init_summary = __esm({
846
- "src/exporter/summary.ts"() {
847
- "use strict";
848
- }
849
- });
850
-
851
- // src/platforms/framer.ts
852
- var FONT_EXTS, framer;
853
- var init_framer = __esm({
854
- "src/platforms/framer.ts"() {
855
- "use strict";
856
- FONT_EXTS = [".woff2", ".woff", ".ttf", ".otf"];
857
- framer = {
858
- name: "framer",
859
- displayName: "Framer",
860
- detectByUrl(url) {
861
- return /\.framer\.(app|website)|framercanvas\.com/.test(url);
862
- },
863
- detectByHtml(html) {
864
- return html.includes('id="main"') && (html.includes("framerstatic.com") || html.includes("framerusercontent.com"));
865
- },
866
- stripDomains: ["events.framer.com", "api.framer.com", "collect.frameranalytics.com"],
867
- stripSelectors: [
868
- 'script[src*="events.framer.com"]',
869
- "#__framer-badge-container",
870
- "#__framer-badge",
871
- 'link[href*="canvas-sandbox"]',
872
- 'script[src*="framer.com/bootstrap"]'
873
- ],
874
- stripPatterns: [
875
- /<div id="__framer-badge-container"[^>]*><\/div>/g,
876
- /<script>try\{if\(localStorage\.get\("__framer_force_showing_editorbar_since"\)\)[^<]*<\/script>/g
877
- ],
878
- hydrationTimeout: 1e4,
879
- needsHydrationCheck: true,
880
- mapAssetDir(host, pathname, ext) {
881
- if (host.includes("framerusercontent.com")) {
882
- if (pathname.startsWith("/images/")) return "assets/images";
883
- if (pathname.startsWith("/assets/")) {
884
- return FONT_EXTS.includes(ext) ? "assets/fonts" : "assets/misc";
885
- }
886
- if (pathname.startsWith("/sites/")) {
887
- if (ext === ".mjs" || ext === ".js") return "scripts/vendor";
888
- if (ext === ".json") return "data";
889
- if (ext === ".css") return "styles";
890
- if (ext === ".framercms") return "data";
891
- return "assets/misc";
892
- }
893
- if (pathname.startsWith("/modules/")) {
894
- return ext === ".framercms" ? "data" : "scripts/modules";
895
- }
896
- return "assets/misc";
897
- }
898
- if (host.includes("app.framerstatic.com")) {
899
- if (ext === ".css") return "styles";
900
- if (ext === ".mjs" || ext === ".js") return "scripts/vendor";
901
- if (ext === ".woff2" || ext === ".woff") return "assets/fonts";
902
- if (ext === ".png" || ext === ".svg") return "assets/images";
903
- return "assets/misc";
904
- }
905
- if (host.includes("framercanvas.com") || host.includes("framer.com")) {
906
- if (ext === ".mjs" || ext === ".js") return "scripts/vendor";
907
- if (ext === ".css") return "styles";
908
- return "assets/misc";
909
- }
910
- return null;
911
- }
912
- };
913
- }
914
- });
915
-
916
- // src/platforms/webflow.ts
917
- var IMG_EXTS, FONT_EXTS2, VIDEO_EXTS, webflow;
918
- var init_webflow = __esm({
919
- "src/platforms/webflow.ts"() {
920
- "use strict";
921
- IMG_EXTS = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif"];
922
- FONT_EXTS2 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
923
- VIDEO_EXTS = [".mp4", ".webm", ".ogg"];
924
- webflow = {
925
- name: "webflow",
926
- displayName: "Webflow",
927
- detectByUrl(url) {
928
- return /\.webflow\.(io|com)/.test(url);
929
- },
930
- detectByHtml(html) {
931
- return html.includes("data-wf-site") || html.includes("w-webflow-badge") || html.includes("Webflow") || html.includes("webflow.js");
932
- },
933
- stripDomains: ["js-agent.newrelic.com", "cdn.heapanalytics.com", "tr.snapchat.com"],
934
- stripSelectors: [".w-webflow-badge", 'link[href*="editorbar"]'],
935
- stripPatterns: [
936
- /<a[^>]*class="[^"]*w-webflow-badge[^"]*"[^>]*>[\s\S]*?<\/a>/g,
937
- /<a[^>]*href="[^"]*webflow\.com\?utm_campaign=brandjs[^"]*"[^>]*>[\s\S]*?<\/a>/g,
938
- /<style>[^<]*\.w-webflow-badge[^<]*<\/style>/g,
939
- /Powered by <a[^>]*href="[^"]*webflow\.com"[^>]*>[^<]*<\/a>/g,
940
- /<!-- This site was created in Webflow\.[^>]*-->/g,
941
- /<html([^>]*) data-wf-domain="[^"]*"/g,
942
- /<html([^>]*) data-wf-page="[^"]*"/g,
943
- /<html([^>]*) data-wf-site="[^"]*"/g,
944
- /<html([^>]*) data-wf-status="[^"]*"/g,
945
- /<meta[^>]*content="Webflow"[^>]*>/g
946
- ],
947
- hydrationTimeout: 2e3,
948
- needsHydrationCheck: false,
949
- mapAssetDir(host, pathname, ext) {
950
- if (host.includes("website-files.com") || host.includes("webflow.com") || host.includes("uploads-ssl.webflow.com")) {
951
- if (VIDEO_EXTS.includes(ext)) return "assets/videos";
952
- if (pathname.includes("/images/") || IMG_EXTS.includes(ext)) return "assets/images";
953
- if (pathname.includes("/css/") || ext === ".css") return "styles";
954
- if (pathname.includes("/js/") || ext === ".js") return "scripts/vendor";
955
- if (pathname.includes("/gsap/") || pathname.includes("/plugins/")) return "scripts/vendor";
956
- if (FONT_EXTS2.includes(ext)) return "assets/fonts";
957
- return "assets/misc";
958
- }
959
- if (host.includes("d3e54v103j8qbb.cloudfront.net")) {
960
- if (ext === ".js") return "scripts/vendor";
961
- if (ext === ".css") return "styles";
962
- if (IMG_EXTS.includes(ext)) return "assets/images";
963
- return "assets/misc";
1107
+ let rewritten = 0;
1108
+ for (const dir of dirs) {
1109
+ const fullDir = path3.join(exporter.outDir, dir);
1110
+ let files;
1111
+ try {
1112
+ files = await fs2.readdir(fullDir);
1113
+ } catch {
1114
+ continue;
1115
+ }
1116
+ for (const file of files) {
1117
+ const ext = path3.extname(file).toLowerCase();
1118
+ if (![".mjs", ".js", ".css"].includes(ext)) continue;
1119
+ const filePath = path3.join(fullDir, file);
1120
+ try {
1121
+ let content = await fs2.readFile(filePath, "utf-8");
1122
+ const before = content;
1123
+ content = exporter.assets.rewrite(content, dir);
1124
+ if (content !== before) {
1125
+ await fs2.writeFile(filePath, content);
1126
+ rewritten++;
964
1127
  }
965
- return null;
1128
+ } catch {
966
1129
  }
967
- };
1130
+ }
968
1131
  }
969
- });
970
-
971
- // src/platforms/wix.ts
972
- var IMG_EXTS2, FONT_EXTS3, VIDEO_EXTS2, wix;
973
- var init_wix = __esm({
974
- "src/platforms/wix.ts"() {
975
- "use strict";
976
- IMG_EXTS2 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
977
- FONT_EXTS3 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
978
- VIDEO_EXTS2 = [".mp4", ".webm", ".ogg"];
979
- wix = {
980
- name: "wix",
981
- displayName: "Wix",
982
- detectByUrl(url) {
983
- return /\.wixsite\.com|\.wix\.com/.test(url);
984
- },
985
- detectByHtml(html) {
986
- return html.includes("wix-viewer-model") || html.includes('id="WIX_ADS"') || html.includes("X-Wix-") || html.includes("Wix.com") || html.includes("wixcode-sdk");
987
- },
988
- stripDomains: [
989
- "frog.wix.com",
990
- "bi.wixapi.com",
991
- "fed.wixcodescheduler.com",
992
- "editor.wix.com",
993
- "panorama.wixapps.net"
994
- ],
995
- stripSelectors: ["#WIX_ADS", ".wix-ads", "#wix-badge", "#SCROLL_TO_TOP"],
996
- stripPatterns: [
997
- /<div[^>]*class="[^"]*wix-ads[^"]*"[^>]*>[\s\S]*?<\/div>/g
998
- ],
999
- hydrationTimeout: 3e3,
1000
- needsHydrationCheck: false,
1001
- mapAssetDir(host, pathname, ext) {
1002
- if (host === "video.wixstatic.com") {
1003
- return "assets/videos";
1004
- }
1005
- if (host.includes("wixstatic.com")) {
1006
- if (VIDEO_EXTS2.includes(ext)) return "assets/videos";
1007
- if (pathname.includes("/images/") || IMG_EXTS2.includes(ext)) return "assets/images";
1008
- if (ext === ".css") return "styles";
1009
- if (ext === ".js") return "scripts/vendor";
1010
- if (FONT_EXTS3.includes(ext)) return "assets/fonts";
1011
- return "assets/misc";
1132
+ log("Rewrote URLs in " + rewritten + " JS/CSS files");
1133
+ }
1134
+ async function prettifyDownloadedJS(exporter) {
1135
+ const dirs = ["scripts/vendor", "scripts/modules"];
1136
+ let count = 0;
1137
+ let total = 0;
1138
+ for (const dir of dirs) {
1139
+ const fullDir = path3.join(exporter.outDir, dir);
1140
+ let files;
1141
+ try {
1142
+ files = await fs2.readdir(fullDir);
1143
+ } catch {
1144
+ continue;
1145
+ }
1146
+ const jsFiles = files.filter((f) => {
1147
+ const ext = path3.extname(f).toLowerCase();
1148
+ return ext === ".mjs" || ext === ".js";
1149
+ });
1150
+ total += jsFiles.length;
1151
+ for (const file of jsFiles) {
1152
+ const filePath = path3.join(fullDir, file);
1153
+ try {
1154
+ const raw = await fs2.readFile(filePath, "utf-8");
1155
+ const nlRatio = (raw.match(/\n/g) || []).length / raw.length;
1156
+ if (nlRatio > 0.05) {
1157
+ count++;
1158
+ continue;
1012
1159
  }
1013
- if (host.includes("parastorage.com")) {
1014
- if (ext === ".js") return "scripts/vendor";
1015
- if (ext === ".css") return "styles";
1016
- if (FONT_EXTS3.includes(ext)) return "assets/fonts";
1017
- if (IMG_EXTS2.includes(ext)) return "assets/images";
1018
- return "assets/misc";
1160
+ const pretty = await prettifyJS(raw);
1161
+ await fs2.writeFile(filePath, pretty, "utf-8");
1162
+ count++;
1163
+ if (count % 5 === 0) {
1164
+ exporter.cooking?.update("Pretty-printing... (" + count + "/" + total + ")");
1019
1165
  }
1020
- return null;
1166
+ } catch (e) {
1167
+ warn("Pretty-print skipped: " + file + " - " + e.message);
1168
+ count++;
1021
1169
  }
1022
- };
1170
+ }
1171
+ }
1172
+ success("Formatted " + count + "/" + total + " JS/MJS files");
1173
+ }
1174
+ var init_output = __esm({
1175
+ "src/exporter/output.ts"() {
1176
+ "use strict";
1177
+ init_logger();
1178
+ init_prettify();
1179
+ init_template();
1023
1180
  }
1024
1181
  });
1025
1182
 
1026
- // src/platforms/detect.ts
1027
- function detectByUrl(url) {
1028
- for (const platform of ALL_PLATFORMS) {
1029
- if (platform.detectByUrl(url)) return platform;
1030
- }
1031
- return null;
1183
+ // src/exporter/summary.ts
1184
+ import fs3 from "fs/promises";
1185
+ import path4 from "path";
1186
+ import chalk4 from "chalk";
1187
+ function getWidth2() {
1188
+ return process.stdout.columns || 80;
1032
1189
  }
1033
- function detectByHtml(html) {
1034
- for (const platform of ALL_PLATFORMS) {
1035
- if (platform.detectByHtml(html)) return platform;
1190
+ async function printSummary(exporter) {
1191
+ const width = getWidth2();
1192
+ const isSmall = width < 50;
1193
+ const count = async (d) => {
1194
+ try {
1195
+ return (await fs3.readdir(path4.join(exporter.outDir, d))).length;
1196
+ } catch {
1197
+ return 0;
1198
+ }
1199
+ };
1200
+ const [imgs, fonts, videos, misc, scripts, vendor, styles, data] = await Promise.all([
1201
+ count("assets/images"),
1202
+ count("assets/fonts"),
1203
+ count("assets/videos"),
1204
+ count("assets/misc"),
1205
+ count("scripts/modules"),
1206
+ count("scripts/vendor"),
1207
+ count("styles"),
1208
+ count("data")
1209
+ ]);
1210
+ const G = chalk4.hex("#D4A017");
1211
+ console.log("");
1212
+ if (!isSmall) {
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"));
1216
+ } else {
1217
+ console.log(chalk4.bold.white(" Export Summary:"));
1036
1218
  }
1037
- return null;
1038
- }
1039
- function detectPlatform(url, html) {
1040
- const byUrl = detectByUrl(url);
1041
- if (byUrl) return byUrl;
1042
- if (html) {
1043
- const byHtml = detectByHtml(html);
1044
- if (byHtml) return byHtml;
1219
+ const row = (label, count2, type) => {
1220
+ if (count2 === 0) return;
1221
+ const l = label.padEnd(18);
1222
+ const c = String(count2).padStart(3);
1223
+ if (isSmall) {
1224
+ console.log(` ${chalk4.yellow(label)} ${chalk4.white(String(count2))} ${chalk4.gray(type)}`);
1225
+ } else {
1226
+ console.log(` ${G("\u2502")} ${chalk4.yellow(l)} ${chalk4.white(c)} ${chalk4.gray(type.padEnd(20))} ${G("\u2502")}`);
1227
+ }
1228
+ };
1229
+ row("styles/", styles, "CSS files");
1230
+ row("scripts/vendor/", vendor, "JS modules");
1231
+ row("scripts/modules/", scripts, "components");
1232
+ row("assets/images/", imgs, "images");
1233
+ row("assets/videos/", videos, "videos");
1234
+ row("assets/fonts/", fonts, "fonts");
1235
+ row("assets/misc/", misc, "misc files");
1236
+ row("data/", data, "data files");
1237
+ if (!isSmall) {
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"));
1045
1239
  }
1046
- return framer;
1047
- }
1048
- function getPlatformByName(name) {
1049
- const map = { framer, webflow, wix };
1050
- return map[name] || framer;
1051
- }
1052
- var ALL_PLATFORMS;
1053
- var init_detect = __esm({
1054
- "src/platforms/detect.ts"() {
1055
- "use strict";
1056
- init_framer();
1057
- init_webflow();
1058
- init_wix();
1059
- ALL_PLATFORMS = [framer, webflow, wix];
1240
+ console.log("");
1241
+ const quotedDir = `"${exporter.outDir}"`;
1242
+ if (!isSmall) {
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"));
1248
+ } else {
1249
+ console.log(chalk4.bold.white(" To serve locally:"));
1250
+ console.log(` ${chalk4.hex("#D4A017")("cd " + path4.basename(exporter.outDir) + " && node serve.cjs")}`);
1060
1251
  }
1061
- });
1062
-
1063
- // src/platforms/index.ts
1064
- var platforms_exports = {};
1065
- __export(platforms_exports, {
1066
- detectByHtml: () => detectByHtml,
1067
- detectByUrl: () => detectByUrl,
1068
- detectPlatform: () => detectPlatform,
1069
- framer: () => framer,
1070
- getPlatformByName: () => getPlatformByName,
1071
- webflow: () => webflow,
1072
- wix: () => wix
1073
- });
1074
- var init_platforms = __esm({
1075
- "src/platforms/index.ts"() {
1252
+ console.log("");
1253
+ console.log(chalk4.gray(" NOTE: Must be served via HTTP for JS modules to work."));
1254
+ console.log("");
1255
+ }
1256
+ var init_summary = __esm({
1257
+ "src/exporter/summary.ts"() {
1076
1258
  "use strict";
1077
- init_framer();
1078
- init_webflow();
1079
- init_wix();
1080
- init_detect();
1081
1259
  }
1082
1260
  });
1083
1261
 
1084
1262
  // src/cli/cooking.ts
1085
1263
  import chalk5 from "chalk";
1086
- var SHINE_WIDTH, CookingSpinner;
1264
+ var SHINE_WIDTH, FRAME_INTERVAL, SPINNER_FRAMES, PALETTE, CookingSpinner;
1087
1265
  var init_cooking = __esm({
1088
1266
  "src/cli/cooking.ts"() {
1089
1267
  "use strict";
1090
- 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
+ ];
1091
1281
  CookingSpinner = class {
1092
1282
  interval = null;
1283
+ bgInterval = null;
1093
1284
  frame = 0;
1094
1285
  phase = "";
1095
1286
  active = false;
1287
+ bar = 0;
1288
+ barDir = 1;
1096
1289
  start(phase = "") {
1097
1290
  this.phase = phase;
1098
1291
  this.frame = 0;
1292
+ this.bar = 0;
1293
+ this.barDir = 1;
1099
1294
  this.active = true;
1100
1295
  this.draw();
1101
1296
  this.interval = setInterval(() => {
1102
1297
  this.frame++;
1103
1298
  this.draw();
1104
- }, 70);
1299
+ }, FRAME_INTERVAL);
1300
+ this.bgInterval = setInterval(() => {
1301
+ this.bar++;
1302
+ this.draw();
1303
+ }, 800);
1105
1304
  }
1106
1305
  update(phase) {
1107
1306
  this.phase = phase;
1307
+ if (!this.active) this.draw();
1108
1308
  }
1109
1309
  log(message) {
1110
1310
  if (this.active) {
@@ -1121,18 +1321,27 @@ var init_cooking = __esm({
1121
1321
  clearInterval(this.interval);
1122
1322
  this.interval = null;
1123
1323
  }
1324
+ if (this.bgInterval) {
1325
+ clearInterval(this.bgInterval);
1326
+ this.bgInterval = null;
1327
+ }
1124
1328
  process.stdout.write("\r\x1B[2K");
1125
1329
  }
1126
1330
  draw() {
1127
1331
  if (!this.active) return;
1128
- const text = "Cooking";
1129
- const dotCount = 1 + this.frame % 3;
1130
- const dots = ".".repeat(dotCount);
1131
- const pad = " ".repeat(3 - dotCount);
1132
- const shimmer = this.renderShimmer(text);
1133
- const dotStr = chalk5.rgb(180, 180, 180)(dots);
1134
- const phaseText = this.phase ? ` ${chalk5.gray(this.phase)}` : "";
1135
- 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
+ );
1136
1345
  }
1137
1346
  renderShimmer(text) {
1138
1347
  let result = "";
@@ -1141,14 +1350,36 @@ var init_cooking = __esm({
1141
1350
  const dist = Math.abs(i - pos);
1142
1351
  if (dist < SHINE_WIDTH) {
1143
1352
  const t = 1 - dist / SHINE_WIDTH;
1144
- const gray = Math.floor(180 + t * 75);
1145
- 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]);
1146
1355
  } else {
1147
- result += chalk5.rgb(180, 180, 180)(text[i]);
1356
+ result += chalk5.rgb(160, 160, 160)(text[i]);
1148
1357
  }
1149
1358
  }
1150
1359
  return result;
1151
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
+ }
1152
1383
  };
1153
1384
  }
1154
1385
  });
@@ -1219,11 +1450,14 @@ var init_exporter = __esm({
1219
1450
  this.platform = detectPlatform(siteUrl);
1220
1451
  }
1221
1452
  }
1222
- async run() {
1223
- 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"));
1224
1455
  info("Source : " + chalk6.underline(this.siteUrl));
1225
1456
  info("Output : " + chalk6.yellow(this.outDir));
1226
- 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
+ }
1227
1461
  console.log("");
1228
1462
  this.cooking = new CookingSpinner();
1229
1463
  setCooking(this.cooking);
@@ -1237,7 +1471,8 @@ var init_exporter = __esm({
1237
1471
  "styles",
1238
1472
  "scripts/vendor",
1239
1473
  "scripts/modules",
1240
- "data"
1474
+ "data",
1475
+ "subpages"
1241
1476
  ]) {
1242
1477
  await fs4.mkdir(path5.join(this.outDir, d), { recursive: true });
1243
1478
  }
@@ -1254,9 +1489,13 @@ var init_exporter = __esm({
1254
1489
  const htmlDetected = detectPlatform(this.siteUrl, this.ssrHTML);
1255
1490
  if (htmlDetected.name !== this.platform.name) {
1256
1491
  this.platform = htmlDetected;
1257
- log("Platform refined: " + chalk6.magenta(this.platform.displayName) + " (from HTML analysis)");
1492
+ log("Platform refined: " + chalk6.hex("#D4A017")(this.platform.displayName) + " (from HTML analysis)");
1258
1493
  }
1259
1494
  await launchAndCapture(this);
1495
+ if (includeSubpages && this.page) {
1496
+ await this.crawlSubpages();
1497
+ }
1498
+ await closeBrowser(this);
1260
1499
  this.cooking.update("Downloading assets...");
1261
1500
  await downloadAll(this);
1262
1501
  this.cooking.update("Building output...");
@@ -1267,6 +1506,64 @@ var init_exporter = __esm({
1267
1506
  success("Export complete!");
1268
1507
  await printSummary(this);
1269
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
+ }
1270
1567
  };
1271
1568
  }
1272
1569
  });
@@ -1283,10 +1580,10 @@ async function select(question, options, defaultIndex = 0) {
1283
1580
  stdout.write(`\x1B[${options.length + 1}A`);
1284
1581
  stdout.write("\x1B[J");
1285
1582
  }
1286
- console.log(` ${chalk7.cyan("?")} ${chalk7.white.bold(question)}`);
1583
+ console.log(` ${chalk7.hex("#D4A017")("?")} ${chalk7.white.bold(question)}`);
1287
1584
  for (let i = 0; i < options.length; i++) {
1288
1585
  if (i === selected) {
1289
- console.log(` ${chalk7.cyan(">")} ${chalk7.white.bold(options[i].label)}`);
1586
+ console.log(` ${chalk7.hex("#D4A017")(">")} ${chalk7.white.bold(options[i].label)}`);
1290
1587
  } else {
1291
1588
  console.log(` ${chalk7.gray(options[i].label)}`);
1292
1589
  }
@@ -1310,7 +1607,7 @@ async function select(question, options, defaultIndex = 0) {
1310
1607
  stdin.pause();
1311
1608
  stdout.write(`\x1B[${options.length + 1}A`);
1312
1609
  stdout.write("\x1B[J");
1313
- 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)}
1314
1611
  `);
1315
1612
  resolve(options[selected].value);
1316
1613
  } else if (key.ctrl && key.name === "c" || key.name === "escape") {
@@ -1345,7 +1642,7 @@ function getWidth3() {
1345
1642
  function drawHeader(title) {
1346
1643
  const width = getWidth3();
1347
1644
  const isSmall = width < 50;
1348
- const magenta = chalk8.magenta;
1645
+ const magenta = chalk8.hex("#D4A017");
1349
1646
  const boldWhite = chalk8.bold.white;
1350
1647
  if (isSmall) {
1351
1648
  console.log(`
@@ -1363,7 +1660,7 @@ ${magenta("\u25CF")} ${boldWhite(title)}`);
1363
1660
  function line(label, value) {
1364
1661
  const width = getWidth3();
1365
1662
  const isSmall = width < 50;
1366
- const magenta = chalk8.magenta;
1663
+ const magenta = chalk8.hex("#D4A017");
1367
1664
  if (isSmall) {
1368
1665
  console.log(` ${chalk8.bold(label)}: ${chalk8.yellow(value)}`);
1369
1666
  return;
@@ -1377,7 +1674,7 @@ async function runSetup(legacyMode = false) {
1377
1674
  const rl = readline2.createInterface({ input: stdin2, output: stdout2 });
1378
1675
  const ask = async (question, defaultVal) => {
1379
1676
  const suffix = defaultVal ? chalk8.gray(` (${defaultVal})`) : "";
1380
- const prompt = ` ${chalk8.cyan("?")} ${chalk8.white.bold(question)}${suffix} ${chalk8.gray(">")} `;
1677
+ const prompt = ` ${chalk8.hex("#D4A017")("?")} ${chalk8.white.bold(question)}${suffix} ${chalk8.gray(">")} `;
1381
1678
  const answer = await rl.question(prompt);
1382
1679
  return answer.trim() || defaultVal || "";
1383
1680
  };
@@ -1399,10 +1696,10 @@ async function runSetup(legacyMode = false) {
1399
1696
  const detected = detectPlatform(siteUrl);
1400
1697
  let platformName;
1401
1698
  if (legacyMode) {
1402
- console.log(` ${chalk8.blue("i")} Auto-detected: ${chalk8.magenta(detected.displayName)}`);
1699
+ console.log(` ${chalk8.blue("i")} Auto-detected: ${chalk8.hex("#D4A017")(detected.displayName)}`);
1403
1700
  const platformInput = await ask("Platform (framer/webflow/wix)", detected.name);
1404
1701
  platformName = ["framer", "webflow", "wix"].includes(platformInput) ? platformInput : detected.name;
1405
- console.log(` ${chalk8.green("\u2713")} ${chalk8.green("Platform:")} ${chalk8.magenta(platformName)}
1702
+ console.log(` ${chalk8.green("\u2713")} ${chalk8.green("Platform:")} ${chalk8.hex("#D4A017")(platformName)}
1406
1703
  `);
1407
1704
  } else {
1408
1705
  rl.close();
@@ -1417,7 +1714,7 @@ async function runSetup(legacyMode = false) {
1417
1714
  const rl2 = legacyMode ? rl : readline2.createInterface({ input: stdin2, output: stdout2 });
1418
1715
  const ask2 = async (question, defaultVal) => {
1419
1716
  const suffix = defaultVal ? chalk8.gray(` (${defaultVal})`) : "";
1420
- const prompt = ` ${chalk8.cyan("?")} ${chalk8.white.bold(question)}${suffix} ${chalk8.gray(">")} `;
1717
+ const prompt = ` ${chalk8.hex("#D4A017")("?")} ${chalk8.white.bold(question)}${suffix} ${chalk8.gray(">")} `;
1421
1718
  const answer = await rl2.question(prompt);
1422
1719
  return answer.trim() || defaultVal || "";
1423
1720
  };
@@ -1429,10 +1726,15 @@ async function runSetup(legacyMode = false) {
1429
1726
  drawHeader("Step 4 : Options");
1430
1727
  let prettyPrint;
1431
1728
  let concurrency;
1729
+ let includeSubpages;
1432
1730
  if (legacyMode) {
1433
1731
  const prettyAnswer = await ask2("Pretty-print JS files? (y/n)", "y");
1434
1732
  prettyPrint = prettyAnswer.toLowerCase().startsWith("y");
1435
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")}
1436
1738
  `);
1437
1739
  const concurrencyAnswer = await ask2("Download concurrency", "12");
1438
1740
  concurrency = parseInt(concurrencyAnswer, 10) || 12;
@@ -1445,6 +1747,11 @@ async function runSetup(legacyMode = false) {
1445
1747
  { label: "No", value: "no" }
1446
1748
  ]);
1447
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";
1448
1755
  const concurrencyVal = await select("Download concurrency", [
1449
1756
  { label: "6 (slow connection)", value: "6" },
1450
1757
  { label: "12 (default)", value: "12" },
@@ -1456,9 +1763,9 @@ async function runSetup(legacyMode = false) {
1456
1763
  const width = getWidth3();
1457
1764
  const isSmall = width < 50;
1458
1765
  if (!isSmall) {
1459
- 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"));
1460
- console.log(chalk8.magenta(" \u2502") + chalk8.bold.white(" Summary ") + chalk8.magenta("\u2502"));
1461
- 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"));
1462
1769
  } else {
1463
1770
  console.log(chalk8.bold.white(" Summary:"));
1464
1771
  }
@@ -1466,9 +1773,10 @@ async function runSetup(legacyMode = false) {
1466
1773
  line("Platform", platformName);
1467
1774
  line("Output", path6.resolve(outDir));
1468
1775
  line("Pretty-print", prettyPrint ? "yes" : "no");
1776
+ line("Sub-pages", includeSubpages ? "yes" : "no");
1469
1777
  line("Concurrency", String(concurrency));
1470
1778
  if (!isSmall) {
1471
- 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"));
1472
1780
  }
1473
1781
  console.log("");
1474
1782
  let startExport;
@@ -1494,7 +1802,7 @@ async function runSetup(legacyMode = false) {
1494
1802
  CFG2.concurrency = concurrency;
1495
1803
  const exporter = new FramerExporter(siteUrl, path6.resolve(outDir), platformName);
1496
1804
  exporter.prettyPrint = prettyPrint;
1497
- await exporter.run();
1805
+ await exporter.run(includeSubpages);
1498
1806
  }
1499
1807
  var init_setup = __esm({
1500
1808
  "src/cli/setup.ts"() {
@@ -1507,6 +1815,7 @@ var init_setup = __esm({
1507
1815
  });
1508
1816
 
1509
1817
  // src/cli/index.ts
1818
+ init_package();
1510
1819
  import path7 from "path";
1511
1820
  import { URL as URL5 } from "url";
1512
1821
 
@@ -1516,14 +1825,14 @@ import chalk2 from "chalk";
1516
1825
  function showHelp() {
1517
1826
  showBanner();
1518
1827
  console.log(chalk2.white.bold(" USAGE\n"));
1519
- console.log(` ${chalk2.cyan("npm run dev")} ${chalk2.gray("--")} ${chalk2.yellow("<url>")} ${chalk2.gray("[output-dir]")}`);
1520
- console.log(` ${chalk2.cyan("npm start")} ${chalk2.gray("--")} ${chalk2.yellow("<url>")} ${chalk2.gray("[output-dir]")}`);
1521
- 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]")}`);
1522
1830
  console.log("");
1523
1831
  console.log(chalk2.white.bold(" OPTIONS\n"));
1524
1832
  const opts = [
1525
1833
  ["--setup", "Launch the interactive setup assistant"],
1526
1834
  ["--platform <p>", "Force platform: framer, webflow, wix"],
1835
+ ["--subpages", "Crawl and export sub-pages"],
1527
1836
  ["--legacy-mode", "Use text input instead of arrow selection"],
1528
1837
  ["--help, -h", "Show this help message"]
1529
1838
  ];
@@ -1533,21 +1842,22 @@ function showHelp() {
1533
1842
  console.log("");
1534
1843
  console.log(chalk2.white.bold(" SUPPORTED PLATFORMS\n"));
1535
1844
  const platforms = [
1536
- ["Framer", "Auto-detected via .framer.app / .framer.ai URLs"],
1845
+ ["Framer", "Auto-detected via .framer.app / .framer.website URLs"],
1537
1846
  ["Webflow", "Auto-detected via .webflow.io URLs"],
1538
- ["Wix", "Auto-detected via .wixsite.com URLs"]
1847
+ ["Wix", "Auto-detected via .wixsite.com URLs + HTML analysis"]
1539
1848
  ];
1540
1849
  for (const [name, desc] of platforms) {
1541
- console.log(` ${chalk2.magenta(name.padEnd(12))} ${chalk2.gray(desc)}`);
1850
+ console.log(` ${chalk2.hex("#D4A017")(name.padEnd(12))} ${chalk2.gray(desc)}`);
1542
1851
  }
1543
1852
  console.log("");
1544
1853
  console.log(chalk2.white.bold(" EXAMPLES\n"));
1545
- console.log(` ${chalk2.gray("$")} ${chalk2.cyan("framer-export")} ${chalk2.yellow("https://mysite.framer.app")}`);
1546
- console.log(` ${chalk2.gray("$")} ${chalk2.cyan("framer-export")} ${chalk2.yellow("https://mysite.webflow.io")}`);
1547
- console.log(` ${chalk2.gray("$")} ${chalk2.cyan("framer-export")} ${chalk2.yellow("https://user.wixsite.com/my-site")}`);
1548
- console.log(` ${chalk2.gray("$")} ${chalk2.cyan("framer-export")} ${chalk2.green("--platform webflow")} ${chalk2.yellow("https://custom.com")}`);
1549
- console.log(` ${chalk2.gray("$")} ${chalk2.cyan("framer-export")} ${chalk2.green("--setup")}`);
1550
- 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")}`);
1551
1861
  console.log("");
1552
1862
  }
1553
1863
 
@@ -1586,7 +1896,7 @@ async function checkForUpdates(currentVersion) {
1586
1896
  }
1587
1897
 
1588
1898
  // src/cli/index.ts
1589
- var VERSION = "4.0.1";
1899
+ var VERSION = package_default.version;
1590
1900
  function extractFlag(args, flag) {
1591
1901
  const idx = args.indexOf(flag);
1592
1902
  if (idx === -1 || idx + 1 >= args.length) return null;
@@ -1602,6 +1912,25 @@ function hasFlag(args, flag) {
1602
1912
  }
1603
1913
  async function main() {
1604
1914
  const args = process.argv.slice(2);
1915
+ if (args.includes("--version") || args.includes("-v")) {
1916
+ console.log(VERSION);
1917
+ process.exit(0);
1918
+ }
1919
+ if (args.includes("--about")) {
1920
+ const chalk9 = (await import("chalk")).default;
1921
+ showBanner();
1922
+ console.log(` ${chalk9.white.bold("Framer Export")} ${chalk9.gray(`v${package_default.version}`)}`);
1923
+ console.log(` ${chalk9.white(package_default.description)}
1924
+ `);
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}`)}`);
1929
+ console.log(` ${chalk9.white.bold("License")} ${chalk9.green(package_default.license)}`);
1930
+ console.log(` ${chalk9.white.bold("Node")} ${chalk9.gray(`>=${package_default.engines.node}`)}`);
1931
+ console.log("");
1932
+ process.exit(0);
1933
+ }
1605
1934
  checkForUpdates(VERSION).then((latest) => {
1606
1935
  if (!latest) return;
1607
1936
  import("chalk").then((chalk9) => {
@@ -1609,7 +1938,7 @@ async function main() {
1609
1938
  console.log(
1610
1939
  ` ${chalk9.default.yellow("\u21B3")} Update available: ${chalk9.default.gray(VERSION)} \u2192 ${chalk9.default.green(latest)}`
1611
1940
  );
1612
- 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")}`);
1613
1942
  console.log("");
1614
1943
  });
1615
1944
  });
@@ -1630,6 +1959,7 @@ async function main() {
1630
1959
  return;
1631
1960
  }
1632
1961
  const platformOverride = extractFlag(args, "--platform");
1962
+ const includeSubpages = hasFlag(args, "--subpages");
1633
1963
  showBanner();
1634
1964
  const url = args[0];
1635
1965
  try {
@@ -1647,7 +1977,7 @@ async function main() {
1647
1977
  const defaultDir = deriveOutputName2(url, detected);
1648
1978
  const out = args[1] || `./${defaultDir}`;
1649
1979
  try {
1650
- await new FramerExporter2(url, path7.resolve(out), platformOverride || void 0).run();
1980
+ await new FramerExporter2(url, path7.resolve(out), platformOverride || void 0).run(includeSubpages);
1651
1981
  } catch (e) {
1652
1982
  const chalk9 = (await import("chalk")).default;
1653
1983
  console.log(`