@reddoorla/maintenance 0.75.1 → 0.76.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.
Files changed (70) hide show
  1. package/dist/{announce-V6EYPPUR.js → announce-RUT247HS.js} +9 -6
  2. package/dist/{announce-V6EYPPUR.js.map → announce-RUT247HS.js.map} +1 -1
  3. package/dist/{blux-C24OVDDW.js → blux-RIJBRVCF.js} +1867 -277
  4. package/dist/blux-RIJBRVCF.js.map +1 -0
  5. package/dist/chunk-37S6L6G4.js +170 -0
  6. package/dist/chunk-37S6L6G4.js.map +1 -0
  7. package/dist/{chunk-TDG5Z5OT.js → chunk-3U3IGVAQ.js} +72 -9
  8. package/dist/chunk-3U3IGVAQ.js.map +1 -0
  9. package/dist/{chunk-IDBMYDVH.js → chunk-4VAUSRSS.js} +5 -3
  10. package/dist/{chunk-IDBMYDVH.js.map → chunk-4VAUSRSS.js.map} +1 -1
  11. package/dist/{run-migration-LJNNZWIV.js → chunk-6DOFNDXU.js} +5 -3
  12. package/dist/chunk-6DOFNDXU.js.map +1 -0
  13. package/dist/{chunk-NUIBQPB3.js → chunk-6SZBNALD.js} +62 -9
  14. package/dist/chunk-6SZBNALD.js.map +1 -0
  15. package/dist/{chunk-6OYICQSJ.js → chunk-CZAMYVOP.js} +9 -4
  16. package/dist/chunk-CZAMYVOP.js.map +1 -0
  17. package/dist/chunk-DRDAEELJ.js +197 -0
  18. package/dist/chunk-DRDAEELJ.js.map +1 -0
  19. package/dist/{chunk-2AUUAJ5X.js → chunk-DYKG53IR.js} +2 -2
  20. package/dist/{chunk-HBFMTM2E.js → chunk-JTRTG5CU.js} +3 -3
  21. package/dist/{chunk-JCITWZQR.js → chunk-MNE374CZ.js} +2 -50
  22. package/dist/chunk-MNE374CZ.js.map +1 -0
  23. package/dist/chunk-MPBXOJ3N.js +51 -0
  24. package/dist/chunk-MPBXOJ3N.js.map +1 -0
  25. package/dist/{chunk-I55YWV6T.js → chunk-QWFHC3VH.js} +3 -3
  26. package/dist/{chunk-KLVMJYZZ.js → chunk-SEQA7MC3.js} +2 -2
  27. package/dist/chunk-UWJZXL2D.js +9 -0
  28. package/dist/chunk-UWJZXL2D.js.map +1 -0
  29. package/dist/cli/bin.js +36 -9
  30. package/dist/cli/bin.js.map +1 -1
  31. package/dist/cli/commands/audit.js +1 -1
  32. package/dist/{digest-LEMZS5ZK.js → digest-75FWSC74.js} +9 -6
  33. package/dist/{digest-LEMZS5ZK.js.map → digest-75FWSC74.js.map} +1 -1
  34. package/dist/favicon-SRHSJZ62.js +42 -0
  35. package/dist/favicon-SRHSJZ62.js.map +1 -0
  36. package/dist/freeze-Y5OJFOST.js +442 -0
  37. package/dist/freeze-Y5OJFOST.js.map +1 -0
  38. package/dist/header-image-H4N54DHN.js +95 -0
  39. package/dist/header-image-H4N54DHN.js.map +1 -0
  40. package/dist/index.d.ts +58 -1
  41. package/dist/index.js +64 -16
  42. package/dist/index.js.map +1 -1
  43. package/dist/{init-Y2DMKKE7.js → init-2KOISBCZ.js} +3 -3
  44. package/dist/{launch-ZDX7UA3Y.js → launch-K4EHKTXC.js} +7 -5
  45. package/dist/{launch-ZDX7UA3Y.js.map → launch-K4EHKTXC.js.map} +1 -1
  46. package/dist/{orchestrate-NYRYEKQM.js → orchestrate-CPR6KVS2.js} +4 -3
  47. package/dist/{preflight-JCQFKB6M.js → preflight-QRRMPI7K.js} +5 -4
  48. package/dist/{preflight-JCQFKB6M.js.map → preflight-QRRMPI7K.js.map} +1 -1
  49. package/dist/{report-POHQEV6F.js → report-SLGBDJ6O.js} +8 -6
  50. package/dist/{report-POHQEV6F.js.map → report-SLGBDJ6O.js.map} +1 -1
  51. package/dist/reports/header-image/assets/plate.png +0 -0
  52. package/dist/run-migration-WP57U5XO.js +9 -0
  53. package/dist/run-migration-WP57U5XO.js.map +1 -0
  54. package/dist/{selftest-OO2WDSDM.js → selftest-RVASRO3Z.js} +8 -6
  55. package/dist/{selftest-OO2WDSDM.js.map → selftest-RVASRO3Z.js.map} +1 -1
  56. package/dist/webflow-624FEBFD.js +548 -0
  57. package/dist/webflow-624FEBFD.js.map +1 -0
  58. package/package.json +3 -3
  59. package/dist/blux-C24OVDDW.js.map +0 -1
  60. package/dist/chunk-6OYICQSJ.js.map +0 -1
  61. package/dist/chunk-JCITWZQR.js.map +0 -1
  62. package/dist/chunk-NUIBQPB3.js.map +0 -1
  63. package/dist/chunk-TDG5Z5OT.js.map +0 -1
  64. package/dist/run-migration-LJNNZWIV.js.map +0 -1
  65. /package/dist/{chunk-2AUUAJ5X.js.map → chunk-DYKG53IR.js.map} +0 -0
  66. /package/dist/{chunk-HBFMTM2E.js.map → chunk-JTRTG5CU.js.map} +0 -0
  67. /package/dist/{chunk-I55YWV6T.js.map → chunk-QWFHC3VH.js.map} +0 -0
  68. /package/dist/{chunk-KLVMJYZZ.js.map → chunk-SEQA7MC3.js.map} +0 -0
  69. /package/dist/{init-Y2DMKKE7.js.map → init-2KOISBCZ.js.map} +0 -0
  70. /package/dist/{orchestrate-NYRYEKQM.js.map → orchestrate-CPR6KVS2.js.map} +0 -0
@@ -1,3 +1,13 @@
1
+ import {
2
+ FROZEN_PAGE_TYPE,
3
+ extractMapConfig,
4
+ frozenPageCustomType,
5
+ makeIsMapMount
6
+ } from "./chunk-DRDAEELJ.js";
7
+ import {
8
+ assetRef,
9
+ richText
10
+ } from "./chunk-UWJZXL2D.js";
1
11
  import {
2
12
  CDN_HOSTS,
3
13
  collectAssetUrls
@@ -5,7 +15,7 @@ import {
5
15
 
6
16
  // src/cli/commands/blux.ts
7
17
  import { readFile, writeFile, mkdir } from "fs/promises";
8
- import { join, dirname } from "path";
18
+ import { join, dirname, basename } from "path";
9
19
  import { glob } from "tinyglobby";
10
20
 
11
21
  // src/blux/parse.ts
@@ -46,7 +56,7 @@ function parseBluxSite(input) {
46
56
  // src/blux/archetype.ts
47
57
  var nonEmpty = (v) => v != null && v !== "" && !(Array.isArray(v) && v.length === 0) && !(typeof v === "object" && !Array.isArray(v) && Object.keys(v).length === 0);
48
58
  function archetype(b) {
49
- const heading = visibleText(b.title, b._title) !== void 0;
59
+ const heading2 = visibleText(b.title, b._title) !== void 0;
50
60
  const text = visibleText(b.body, b._body) !== void 0;
51
61
  const media = nonEmpty(b.media?.media);
52
62
  const bg = nonEmpty(b.backgroundMedia?.media);
@@ -55,16 +65,16 @@ function archetype(b) {
55
65
  if (kids && cls === "slides")
56
66
  return { sliceType: "slider", variation: "default", confidence: 0.85 };
57
67
  if (bg)
58
- return { sliceType: "hero", variation: "default", confidence: heading || text ? 0.9 : 0.7 };
68
+ return { sliceType: "hero", variation: "default", confidence: heading2 || text ? 0.9 : 0.7 };
59
69
  if (kids)
60
70
  return { sliceType: "grid", variation: "default", confidence: cls === "grid" ? 0.9 : 0.7 };
61
- if (media && heading && text)
71
+ if (media && heading2 && text)
62
72
  return { sliceType: "media_text", variation: "imageRight", confidence: 0.9 };
63
- if (media && (heading || text))
73
+ if (media && (heading2 || text))
64
74
  return { sliceType: "media_text", variation: "imageRight", confidence: 0.75 };
65
75
  if (media) return { sliceType: "media_text", variation: "imageRight", confidence: 0.6 };
66
- if (heading && text) return { sliceType: "rich_text", variation: "default", confidence: 0.85 };
67
- if (heading || text) return { sliceType: "rich_text", variation: "default", confidence: 0.6 };
76
+ if (heading2 && text) return { sliceType: "rich_text", variation: "default", confidence: 0.85 };
77
+ if (heading2 || text) return { sliceType: "rich_text", variation: "default", confidence: 0.6 };
68
78
  return { sliceType: "rich_text", variation: "default", confidence: 0.2 };
69
79
  }
70
80
 
@@ -147,11 +157,11 @@ function sectionFromBlock(b, pageUid, diagnostics) {
147
157
  message: `block mapped to ${a.sliceType} at ${a.confidence}`
148
158
  });
149
159
  }
150
- const heading = visibleText(b.title, b._title);
160
+ const heading2 = visibleText(b.title, b._title);
151
161
  const body = visibleText(b.body, b._body);
152
- const headingRole = heading !== void 0 ? textRole(b._title) : void 0;
162
+ const headingRole = heading2 !== void 0 ? textRole(b._title) : void 0;
153
163
  const bodyRole = body !== void 0 ? textRole(b._body) : void 0;
154
- const headingStyle = heading !== void 0 ? inlineStyle(b._title) : void 0;
164
+ const headingStyle = heading2 !== void 0 ? inlineStyle(b._title) : void 0;
155
165
  const bodyStyle = body !== void 0 ? inlineStyle(b._body) : void 0;
156
166
  const block = {};
157
167
  for (const [k, v] of Object.entries(b.styles ?? {})) {
@@ -170,7 +180,7 @@ function sectionFromBlock(b, pageUid, diagnostics) {
170
180
  variation: a.variation,
171
181
  confidence: a.confidence,
172
182
  fields: {
173
- ...heading !== void 0 ? { heading } : {},
183
+ ...heading2 !== void 0 ? { heading: heading2 } : {},
174
184
  ...body !== void 0 ? { body } : {},
175
185
  ...b.media?.media ? { media: b.media.media } : {},
176
186
  ...b.backgroundMedia?.media ? { backgroundMedia: b.backgroundMedia.media } : {},
@@ -394,10 +404,6 @@ function assembleIR(input) {
394
404
  };
395
405
  }
396
406
 
397
- // src/blux/emit/plan.ts
398
- var richText = (html) => ({ __richtext_html: html });
399
- var assetRef = (id) => ({ __asset_id: id });
400
-
401
407
  // src/blux/emit/custom-types.ts
402
408
  var FIELD_CONFIG = {
403
409
  text: () => ({ type: "Text", config: {} }),
@@ -680,13 +686,8 @@ function buildMigrationPlan(ir) {
680
686
  }
681
687
 
682
688
  // src/blux/emit/theme.ts
683
- function emitThemeCss(theme) {
689
+ function themeVarLines(theme) {
684
690
  const lines = [];
685
- if (theme.fontLoad.length) {
686
- const spec = theme.fontLoad.map((f) => `${f.family} ${f.weights.join(",")}`).join("; ");
687
- lines.push(`/* Fonts to load \u2014 ${spec} */`);
688
- }
689
- lines.push("@theme {");
690
691
  for (const c of theme.colors) lines.push(` --color-${c.role}: ${c.value};`);
691
692
  lines.push(` --font-heading: ${theme.fonts.heading || "sans-serif"};`);
692
693
  lines.push(` --font-body: ${theme.fonts.body || "sans-serif"};`);
@@ -703,9 +704,22 @@ function emitThemeCss(theme) {
703
704
  if (t.mobileLineHeight)
704
705
  lines.push(` --text-${t.role}--mobile-line-height: ${t.mobileLineHeight};`);
705
706
  }
707
+ return lines;
708
+ }
709
+ function emitThemeCss(theme) {
710
+ const lines = [];
711
+ if (theme.fontLoad.length) {
712
+ const spec = theme.fontLoad.map((f) => `${f.family} ${f.weights.join(",")}`).join("; ");
713
+ lines.push(`/* Fonts to load \u2014 ${spec} */`);
714
+ }
715
+ lines.push("@theme {");
716
+ lines.push(...themeVarLines(theme));
706
717
  lines.push("}");
707
718
  return lines.join("\n") + "\n";
708
719
  }
720
+ function emitRootVarsCss(theme) {
721
+ return [":root {", ...themeVarLines(theme), "}"].join("\n") + "\n";
722
+ }
709
723
  function emitRolesCss(theme) {
710
724
  if (!theme.textStyles.length) return "";
711
725
  const lines = [
@@ -756,6 +770,11 @@ function emitButtonsCss(theme) {
756
770
  }
757
771
  return lines.join("\n") + "\n";
758
772
  }
773
+ function emitThemeCssFile(theme) {
774
+ const rolesCss = emitRolesCss(theme);
775
+ const buttonsCss = emitButtonsCss(theme);
776
+ return emitThemeCss(theme) + "\n" + emitRootVarsCss(theme) + (rolesCss ? "\n" + rolesCss : "") + (buttonsCss ? "\n" + buttonsCss : "");
777
+ }
759
778
 
760
779
  // src/blux/emit/review.ts
761
780
  function buildReviewManifest(ir, opts) {
@@ -768,6 +787,47 @@ function buildReviewManifest(ir, opts) {
768
787
  return { pairs, diagnostics: ir.diagnostics };
769
788
  }
770
789
 
790
+ // src/blux/emit/block-styles.ts
791
+ function blockStylesByIndex(siteJson, pageIndex = 0) {
792
+ const out = /* @__PURE__ */ new Map();
793
+ const blocks = siteJson?.content?.pages?.[pageIndex]?.items;
794
+ if (!Array.isArray(blocks)) return out;
795
+ blocks.forEach((b, i) => {
796
+ const styles = b?.styles ?? {};
797
+ const cleaned = {};
798
+ for (const [k, v] of Object.entries(styles)) {
799
+ const c = cleanCssValue(v);
800
+ if (c) cleaned[k] = c;
801
+ }
802
+ if (Object.keys(cleaned).length) out.set(i, cleaned);
803
+ });
804
+ return out;
805
+ }
806
+ var CONTAINER_RE = /^\.(blocks\d+)container$/;
807
+ function blockClassDefaults(siteJson) {
808
+ const out = /* @__PURE__ */ new Map();
809
+ const blocks = siteJson?.styles?.blocks;
810
+ if (!Array.isArray(blocks)) return out;
811
+ for (const slot of blocks) {
812
+ if (slot === null || typeof slot !== "object") continue;
813
+ for (const [selector, css] of Object.entries(slot)) {
814
+ const blockClass = CONTAINER_RE.exec(selector)?.[1];
815
+ if (!blockClass || css === null || typeof css !== "object") continue;
816
+ const rec = css;
817
+ const padding = cleanCssValue(rec["padding"]);
818
+ const mobilePadding = cleanCssValue(rec["__media_mobile_padding"]);
819
+ const maxWidth = cleanCssValue(rec["max-width"]);
820
+ const defaults = {
821
+ ...padding ? { padding } : {},
822
+ ...mobilePadding ? { mobilePadding } : {},
823
+ ...maxWidth ? { maxWidth } : {}
824
+ };
825
+ if (Object.keys(defaults).length) out.set(blockClass, defaults);
826
+ }
827
+ }
828
+ return out;
829
+ }
830
+
771
831
  // src/blux/validate.ts
772
832
  var DROP_ELEMENTS = /<(script|style|head|noscript|svg|template)[\s\S]*?<\/\1>/gi;
773
833
  var COMMENTS = /<!--[\s\S]*?-->/g;
@@ -1334,10 +1394,10 @@ function pureCellMedia(cell) {
1334
1394
  const n = unboxed(cell.node);
1335
1395
  return n.kind === "media" ? n.media : null;
1336
1396
  }
1337
- function textRoleMeta(heading, subtitle) {
1397
+ function textRoleMeta(heading2, subtitle) {
1338
1398
  return {
1339
- ...heading?.kind === "heading" && heading.role ? { headingRole: heading.role } : {},
1340
- ...heading?.kind === "heading" ? { headingLevel: heading.level } : {},
1399
+ ...heading2?.kind === "heading" && heading2.role ? { headingRole: heading2.role } : {},
1400
+ ...heading2?.kind === "heading" ? { headingLevel: heading2.level } : {},
1341
1401
  ...subtitle?.kind === "subtitle" && subtitle.role ? { subtitleRole: subtitle.role } : {}
1342
1402
  };
1343
1403
  }
@@ -1545,108 +1605,6 @@ function classifyBands(bands, opts = {}) {
1545
1605
  return bands.map((b) => classifyBand(b, opts));
1546
1606
  }
1547
1607
 
1548
- // src/blux/grid/extract-map.ts
1549
- var SCRIPT_RE = /<script\b[^>]*>([\s\S]*?)<\/script>/g;
1550
- function findScript(html, marker) {
1551
- for (const m of html.matchAll(SCRIPT_RE)) {
1552
- const body = m[1];
1553
- if (body !== void 0 && marker.test(body)) return body;
1554
- }
1555
- return null;
1556
- }
1557
- function extractStyles(script) {
1558
- const at = script.indexOf("styles:[");
1559
- if (at === -1) return [];
1560
- const start = at + "styles:".length;
1561
- let depth = 0;
1562
- for (let i = start; i < script.length; i++) {
1563
- const ch = script[i];
1564
- if (ch === "[") depth++;
1565
- else if (ch === "]") {
1566
- depth--;
1567
- if (depth === 0) {
1568
- try {
1569
- const raw = script.slice(start, i + 1);
1570
- const jsonish = raw.replace(/([{,])\s*([A-Za-z_][A-Za-z0-9_.]*)\s*:/g, '$1"$2":');
1571
- const parsed = JSON.parse(jsonish);
1572
- return Array.isArray(parsed) ? parsed : [];
1573
- } catch {
1574
- return [];
1575
- }
1576
- }
1577
- }
1578
- }
1579
- return [];
1580
- }
1581
- var LAYER_RE = /(\w+)\s*:\s*new google\.maps\.KmlLayer\(\{([^}]*)\}\)/g;
1582
- function extractMapConfig(html) {
1583
- const init = findScript(html, /function initMap\(\)[\s\S]*new google\.maps\.Map/);
1584
- if (!init) return null;
1585
- const mountId = /getElementById\(\s*["']([^"']+)["']\s*\)/.exec(init)?.[1];
1586
- if (!mountId) return null;
1587
- const layers = [];
1588
- let mid;
1589
- for (const m of init.matchAll(LAYER_RE)) {
1590
- const name = m[1];
1591
- const args = m[2];
1592
- if (!name || args === void 0) continue;
1593
- const layerMid = /[?&]mid=([^&"']+)/.exec(args)?.[1];
1594
- const lid = /[?&]lid=([^&"']+)/.exec(args)?.[1];
1595
- if (!layerMid || !lid) continue;
1596
- mid ??= layerMid;
1597
- layers.push({
1598
- name,
1599
- lid,
1600
- initiallyVisible: /\bmap\s*:\s*map\b/.test(args),
1601
- preserveViewport: /preserveViewport\s*:\s*true/.test(args)
1602
- });
1603
- }
1604
- if (!mid || layers.length === 0) return null;
1605
- const centerM = /center\s*:\s*\{\s*lat\s*:\s*(-?[\d.]+)\s*,\s*lng\s*:\s*(-?[\d.]+)\s*\}/.exec(
1606
- init
1607
- );
1608
- const zoomM = /zoom\s*:\s*(\d+)/.exec(init);
1609
- const mountTag = new RegExp(`<[^>]*\\bid=["']${mountId}["'][^>]*>`, "i").exec(html)?.[0] ?? "";
1610
- const height = /height\s*:\s*([\d.]+(?:px|%|vh|vw|em|rem))/i.exec(mountTag)?.[1];
1611
- const toggles = extractToggles(html);
1612
- return {
1613
- mountId,
1614
- mid,
1615
- layers,
1616
- toggles,
1617
- styles: extractStyles(init),
1618
- ...centerM?.[1] && centerM[2] ? { center: { lat: Number(centerM[1]), lng: Number(centerM[2]) } } : {},
1619
- ...zoomM?.[1] ? { zoom: Number(zoomM[1]) } : {},
1620
- ...height ? { height } : {},
1621
- ...toggles.length ? { defaultToggle: 0 } : {}
1622
- };
1623
- }
1624
- function makeIsMapMount(config) {
1625
- const marker = `id="${config.mountId}"`;
1626
- return (node) => node.kind === "raw" && node.html.includes(marker);
1627
- }
1628
- function extractToggles(html) {
1629
- const labels = [...html.matchAll(/map_icon_text">([^<]*)</g)].map((m) => m[1]).filter((l) => l !== void 0);
1630
- const click = findScript(html, /clickMap\s*=\s*\{\s*0\s*:/);
1631
- if (!click || labels.length === 0) return [];
1632
- const bodyM = /clickMap\s*=\s*(\{[\s\S]*?\}\})\s*;/.exec(click);
1633
- if (!bodyM?.[1]) return [];
1634
- const groups = [];
1635
- for (const g of bodyM[1].matchAll(/(\d+)\s*:\s*function\s*\(onoff\)\s*\{([^}]*)\}/g)) {
1636
- const idx = Number(g[1]);
1637
- const body = g[2] ?? "";
1638
- const layerNames = [
1639
- ...new Set(
1640
- [...body.matchAll(/mapLayers\.(\w+)\./g)].map((m) => m[1]).filter((n) => n !== void 0)
1641
- )
1642
- ];
1643
- const label = labels[idx];
1644
- if (label === void 0) return [];
1645
- groups[idx] = { label, layers: layerNames, panelIndex: idx };
1646
- }
1647
- return groups.length === labels.length && groups.every((g) => g !== void 0) ? groups : [];
1648
- }
1649
-
1650
1608
  // src/blux/grid/feed-grid.ts
1651
1609
  var termMatchesTag = (term, tag) => term === tag || term.endsWith("s") && term.slice(0, -1) === tag || tag.endsWith("s") && tag.slice(0, -1) === term;
1652
1610
  function tagFilter(expr) {
@@ -1737,12 +1695,12 @@ function resolveFeedTiles(bandDef, resolvers) {
1737
1695
  return tiles.length ? tiles : null;
1738
1696
  }
1739
1697
  function materializeFeedGrid(opts) {
1740
- const { heading, tiles, columns, spacing, overlay } = opts;
1741
- const headingNode = heading ? {
1698
+ const { heading: heading2, tiles, columns, spacing, overlay } = opts;
1699
+ const headingNode = heading2 ? {
1742
1700
  kind: "heading",
1743
- level: heading.level,
1744
- html: heading.html,
1745
- ...heading.role ? { role: heading.role } : {}
1701
+ level: heading2.level,
1702
+ html: heading2.html,
1703
+ ...heading2.role ? { role: heading2.role } : {}
1746
1704
  } : null;
1747
1705
  if (!tiles || !tiles.length) return headingNode;
1748
1706
  const cols = Math.max(1, Math.round(columns));
@@ -1909,47 +1867,6 @@ function materializeProducts(records, resolveImage) {
1909
1867
  return [...bySlug.values()];
1910
1868
  }
1911
1869
 
1912
- // src/blux/emit/block-styles.ts
1913
- function blockStylesByIndex(siteJson, pageIndex = 0) {
1914
- const out = /* @__PURE__ */ new Map();
1915
- const blocks = siteJson?.content?.pages?.[pageIndex]?.items;
1916
- if (!Array.isArray(blocks)) return out;
1917
- blocks.forEach((b, i) => {
1918
- const styles = b?.styles ?? {};
1919
- const cleaned = {};
1920
- for (const [k, v] of Object.entries(styles)) {
1921
- const c = cleanCssValue(v);
1922
- if (c) cleaned[k] = c;
1923
- }
1924
- if (Object.keys(cleaned).length) out.set(i, cleaned);
1925
- });
1926
- return out;
1927
- }
1928
- var CONTAINER_RE = /^\.(blocks\d+)container$/;
1929
- function blockClassDefaults(siteJson) {
1930
- const out = /* @__PURE__ */ new Map();
1931
- const blocks = siteJson?.styles?.blocks;
1932
- if (!Array.isArray(blocks)) return out;
1933
- for (const slot of blocks) {
1934
- if (slot === null || typeof slot !== "object") continue;
1935
- for (const [selector, css] of Object.entries(slot)) {
1936
- const blockClass = CONTAINER_RE.exec(selector)?.[1];
1937
- if (!blockClass || css === null || typeof css !== "object") continue;
1938
- const rec = css;
1939
- const padding = cleanCssValue(rec["padding"]);
1940
- const mobilePadding = cleanCssValue(rec["__media_mobile_padding"]);
1941
- const maxWidth = cleanCssValue(rec["max-width"]);
1942
- const defaults = {
1943
- ...padding ? { padding } : {},
1944
- ...mobilePadding ? { mobilePadding } : {},
1945
- ...maxWidth ? { maxWidth } : {}
1946
- };
1947
- if (Object.keys(defaults).length) out.set(blockClass, defaults);
1948
- }
1949
- }
1950
- return out;
1951
- }
1952
-
1953
1870
  // src/blux/emit/grid-slice.ts
1954
1871
  function stripTags(html) {
1955
1872
  return html.replace(/<[^>]*>/g, "").replace(/\s+/g, " ").trim();
@@ -2488,110 +2405,1411 @@ function convertSite({
2488
2405
  return { pages, ir, plan, presentation };
2489
2406
  }
2490
2407
 
2491
- // src/blux/emit/site-config.ts
2492
- function parseNavItems(items) {
2493
- if (!Array.isArray(items)) return [];
2494
- const out = [];
2495
- for (const raw of items) {
2496
- const label = typeof raw?.title === "string" ? raw.title.trim() : "";
2497
- if (!label) continue;
2498
- const href = typeof raw?.link === "string" ? raw.link : "";
2499
- const children = parseNavItems(raw?.items);
2500
- out.push({ label, href, ...children.length ? { children } : {} });
2501
- }
2502
- return out;
2408
+ // src/blux/catalog/spec.ts
2409
+ var CATALOG_PAGE_TYPE = "catalog_page";
2410
+
2411
+ // src/blux/catalog/catalog-page-type.ts
2412
+ var CATALOG_PAGE_LABEL = "Catalog Page (Blux migration)";
2413
+ var BLUX_SLICE_CHOICES = [
2414
+ "blux_section",
2415
+ "blux_text",
2416
+ "blux_block",
2417
+ "blux_grid",
2418
+ "blux_gallery",
2419
+ "blux_carousel",
2420
+ "blux_media",
2421
+ "blux_media_text",
2422
+ "blux_embed",
2423
+ "blux_table",
2424
+ "blux_collection"
2425
+ ];
2426
+ function sliceChoices() {
2427
+ return Object.fromEntries(BLUX_SLICE_CHOICES.map((t) => [t, { type: "SharedSlice" }]));
2428
+ }
2429
+ function catalogPageCustomType() {
2430
+ return {
2431
+ id: CATALOG_PAGE_TYPE,
2432
+ label: CATALOG_PAGE_LABEL,
2433
+ repeatable: true,
2434
+ json: {
2435
+ id: CATALOG_PAGE_TYPE,
2436
+ label: CATALOG_PAGE_LABEL,
2437
+ format: "page",
2438
+ repeatable: true,
2439
+ status: true,
2440
+ json: {
2441
+ Main: {
2442
+ uid: { type: "UID", config: { label: "UID", placeholder: "" } },
2443
+ title: {
2444
+ type: "StructuredText",
2445
+ config: {
2446
+ label: "Title",
2447
+ placeholder: "",
2448
+ allowTargetBlank: true,
2449
+ single: "heading1"
2450
+ }
2451
+ },
2452
+ slices: {
2453
+ type: "Slices",
2454
+ fieldset: "Slice Zone",
2455
+ config: { choices: sliceChoices() }
2456
+ }
2457
+ },
2458
+ "SEO & Metadata": {
2459
+ meta_title: {
2460
+ type: "Text",
2461
+ config: {
2462
+ label: "Meta Title",
2463
+ placeholder: "A title of the page used for social media and search engines"
2464
+ }
2465
+ },
2466
+ meta_description: {
2467
+ type: "Text",
2468
+ config: {
2469
+ label: "Meta Description",
2470
+ placeholder: "A brief summary of the page"
2471
+ }
2472
+ },
2473
+ meta_image: {
2474
+ type: "Image",
2475
+ config: {
2476
+ label: "Meta Image",
2477
+ constraint: { width: 2400, height: 1260 },
2478
+ thumbnails: []
2479
+ }
2480
+ }
2481
+ }
2482
+ }
2483
+ }
2484
+ };
2503
2485
  }
2504
- var SOCIAL_DOMAINS = {
2505
- facebook: ["facebook.com", "fb.com"],
2506
- twitter: ["twitter.com", "x.com"],
2507
- instagram: ["instagram.com"],
2508
- pinterest: ["pinterest.com"],
2509
- linkedin: ["linkedin.com"],
2510
- "linkedin-company": ["linkedin.com"],
2511
- youtube: ["youtube.com", "youtu.be"],
2512
- reddit: ["reddit.com"]
2513
- };
2514
- function hostMatches(host, domain) {
2515
- return host === domain || host.endsWith("." + domain);
2486
+
2487
+ // src/blux/catalog/sanitize.ts
2488
+ var SCRIPT_BLOCK_RE = /<script\b[^>]*>[\s\S]*?<\/script\s*>/gi;
2489
+ var SCRIPT_TAIL_RE = /<script\b[\s\S]*$/i;
2490
+ var ON_HANDLER_RE = /\s+on\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi;
2491
+ var JS_URL_QUOTED_RE = /\b(href|src)\s*=\s*(["'])\s*javascript:[\s\S]*?\2/gi;
2492
+ var JS_URL_BARE_RE = /\b(href|src)\s*=\s*javascript:[^\s>]*/gi;
2493
+ function sanitizeHtml(html) {
2494
+ return html.replace(SCRIPT_BLOCK_RE, "").replace(SCRIPT_TAIL_RE, "").replace(ON_HANDLER_RE, "").replace(JS_URL_QUOTED_RE, "$1=$2#$2").replace(JS_URL_BARE_RE, '$1="#"');
2495
+ }
2496
+ function hasVisibleContent(html) {
2497
+ const clean = sanitizeHtml(html);
2498
+ if (/<(img|iframe|video)\b/i.test(clean)) return true;
2499
+ return clean.replace(/<[^>]*>/g, "").trim().length > 0;
2516
2500
  }
2517
- function hostOf(url) {
2518
- try {
2519
- return new URL(url).hostname.toLowerCase();
2520
- } catch {
2521
- return "";
2501
+
2502
+ // src/blux/catalog/cells.ts
2503
+ function unbox(n) {
2504
+ let cur = n;
2505
+ while (cur.kind === "stack" && cur.children.length === 1) {
2506
+ const only = cur.children[0];
2507
+ if (!only) break;
2508
+ cur = only;
2522
2509
  }
2510
+ return cur;
2523
2511
  }
2524
- function footerRegion(html) {
2525
- const lower = html.toLowerCase();
2526
- const start = lower.lastIndexOf("<footer");
2527
- if (start === -1) return html;
2528
- const end = lower.indexOf("</footer>", start);
2529
- return end === -1 ? html.slice(start) : html.slice(start, end + "</footer>".length);
2512
+ function collectRaws2(node) {
2513
+ switch (node.kind) {
2514
+ case "raw":
2515
+ return [node];
2516
+ case "row":
2517
+ return node.cells.flatMap((c) => collectRaws2(c.node));
2518
+ case "stack":
2519
+ return node.children.flatMap(collectRaws2);
2520
+ case "heading":
2521
+ case "body":
2522
+ case "subtitle":
2523
+ case "media":
2524
+ case "widget":
2525
+ return [];
2526
+ }
2530
2527
  }
2531
- function socialHrefResolverFromHtml(htmls) {
2532
- const hrefs = [];
2533
- const re = /href\s*=\s*["']([^"']+)["']/gi;
2534
- for (const html of htmls) {
2535
- if (typeof html !== "string") continue;
2536
- const region = footerRegion(html);
2537
- let m;
2538
- while (m = re.exec(region)) {
2539
- const url = m[1];
2540
- if (url && /^https?:\/\//i.test(url)) hrefs.push(url);
2528
+ function rawHtmlOf(node) {
2529
+ const raws = collectRaws2(node).filter((r) => !isEmptyRaw(r));
2530
+ return raws.length ? raws.map((r) => r.html).join("\n") : void 0;
2531
+ }
2532
+ function wrapBare(html) {
2533
+ return html.trim().startsWith("<") ? html : `<p>${html.trim()}</p>`;
2534
+ }
2535
+ function visualFieldsOf(u, token) {
2536
+ const style = "style" in u && u.style ? u.style : {};
2537
+ const media = collectMedia(u)[0];
2538
+ const out = {};
2539
+ if (typeof token?.ratio === "number") out.width = `${token.ratio}%`;
2540
+ if (typeof token?.spacing === "number") out.spacing = token.spacing;
2541
+ if (style["background-color"]) out.backgroundColor = style["background-color"];
2542
+ if (style["padding"]) out.contentPadding = style["padding"];
2543
+ if (style["_valign"] === "middle") out.valign = true;
2544
+ if (style["_fill"] === "column" || media?.fit === "cover") out.cover = true;
2545
+ return out;
2546
+ }
2547
+ function roleWrapHtml(role, inner) {
2548
+ return role ? `<div class="txt-role-${role}">${inner}</div>` : inner;
2549
+ }
2550
+ function textOf(n) {
2551
+ let title;
2552
+ let titleRole;
2553
+ const bodyParts = [];
2554
+ for (const t of collectText(n)) {
2555
+ if (t.kind === "heading") {
2556
+ if (blockPlainText(t.html) === "") continue;
2557
+ const wrapped = `<h${t.level}>${t.html}</h${t.level}>`;
2558
+ if (title === void 0) {
2559
+ title = wrapped;
2560
+ titleRole = t.role;
2561
+ } else bodyParts.push(roleWrapHtml(t.role, wrapped));
2562
+ } else if (t.kind === "body") {
2563
+ if (t.html) bodyParts.push(roleWrapHtml(t.role, wrapBare(t.html)));
2564
+ } else if (t.kind === "subtitle") {
2565
+ bodyParts.push(roleWrapHtml(t.role, `<p>${t.text}</p>`));
2541
2566
  }
2542
2567
  }
2543
- return (network) => {
2544
- const domains = SOCIAL_DOMAINS[network];
2545
- if (!domains) return void 0;
2546
- return hrefs.find((u) => {
2547
- const host = hostOf(u);
2548
- return host && domains.some((d) => hostMatches(host, d));
2549
- });
2568
+ return {
2569
+ ...title ? { title } : {},
2570
+ ...titleRole ? { titleRole } : {},
2571
+ ...bodyParts.length ? { bodyHtml: bodyParts.join("\n") } : {}
2550
2572
  };
2551
2573
  }
2552
- function parseSocials(raw, resolveSocialHref) {
2553
- const out = [];
2554
- const items = Array.isArray(raw) ? raw : [];
2555
- for (const it of items) {
2556
- const media = it?.media;
2557
- if (!media || media.type !== "social") continue;
2558
- const networks = media.networks;
2559
- if (!networks || typeof networks !== "object") continue;
2560
- const urls = media.urls ?? {};
2561
- for (const [network, on] of Object.entries(networks)) {
2562
- if (on !== true) continue;
2563
- const fromExport = typeof urls[network] === "string" ? urls[network] : void 0;
2564
- const url = fromExport ?? resolveSocialHref?.(network);
2565
- out.push({ network, ...url ? { href: url } : {} });
2566
- }
2574
+ function containsRow(node) {
2575
+ switch (node.kind) {
2576
+ case "row":
2577
+ return true;
2578
+ case "stack":
2579
+ return node.children.some(containsRow);
2580
+ default:
2581
+ return false;
2567
2582
  }
2568
- return out;
2569
2583
  }
2570
- function collectTitles(node, acc) {
2571
- if (Array.isArray(node)) {
2572
- for (const n of node) collectTitles(n, acc);
2573
- return acc;
2584
+ function buildCell(node, depth, state, token) {
2585
+ const u = unbox(node);
2586
+ if (u.kind === "row" && depth === 0) {
2587
+ return {
2588
+ kind: "subgrid",
2589
+ subgrid: u.cells.map((c) => buildCell(c.node, depth + 1, state, c.token))
2590
+ };
2574
2591
  }
2575
- if (node && typeof node === "object") {
2576
- const rec = node;
2577
- if (typeof rec.title === "string" && rec.title.trim()) acc.push(rec.title.trim());
2578
- for (const k of Object.keys(rec)) if (k !== "title") collectTitles(rec[k], acc);
2592
+ const allMedia = collectMedia(u);
2593
+ if (depth === 0 && allMedia.length > 1) {
2594
+ const { title: title2, titleRole: titleRole2, bodyHtml: bodyHtml2 } = textOf(u);
2595
+ const embedHtml2 = rawHtmlOf(u);
2596
+ const textItem = title2 || bodyHtml2 || embedHtml2 ? [
2597
+ {
2598
+ kind: "text",
2599
+ ...title2 ? { title: title2 } : {},
2600
+ ...bodyHtml2 ? { bodyHtml: bodyHtml2 } : {},
2601
+ ...embedHtml2 ? { embedHtml: embedHtml2 } : {},
2602
+ ...titleRole2 ? { titleRole: titleRole2 } : {}
2603
+ }
2604
+ ] : [];
2605
+ return {
2606
+ kind: "subgrid",
2607
+ subgrid: [...textItem, ...allMedia.map((m) => ({ kind: "media", media: m }))]
2608
+ };
2579
2609
  }
2580
- return acc;
2610
+ if (depth > 0 && (allMedia.length > 1 || containsRow(u))) state.flattened = true;
2611
+ const media = allMedia[0];
2612
+ const vis = visualFieldsOf(u, token);
2613
+ const { title, titleRole, bodyHtml } = textOf(u);
2614
+ const embedHtml = rawHtmlOf(u);
2615
+ if (u.kind === "media" || media && !title && !bodyHtml) {
2616
+ return {
2617
+ kind: "media",
2618
+ ...media ? { media } : {},
2619
+ ...title ? { title } : {},
2620
+ ...bodyHtml ? { bodyHtml } : {},
2621
+ ...embedHtml ? { embedHtml } : {},
2622
+ ...titleRole ? { titleRole } : {},
2623
+ ...vis
2624
+ };
2625
+ }
2626
+ return {
2627
+ kind: title || bodyHtml ? "text" : embedHtml ? "embed" : "text",
2628
+ ...title ? { title } : {},
2629
+ ...bodyHtml ? { bodyHtml } : {},
2630
+ ...media ? { media } : {},
2631
+ ...embedHtml ? { embedHtml } : {},
2632
+ ...titleRole ? { titleRole } : {},
2633
+ ...vis
2634
+ };
2581
2635
  }
2582
- var COPYRIGHT_RE = /©|\(c\)|\bcopyright\b|rights reserved/i;
2583
- function parseFooterText(raw) {
2584
- return collectTitles(raw, []).find((t) => COPYRIGHT_RE.test(t));
2636
+ function cellFromNode(node) {
2637
+ return buildCell(node, 0, { flattened: false });
2585
2638
  }
2586
- function buildSiteConfig(siteJson, resolveLogo, resolveSocialHref) {
2587
- const j = siteJson;
2588
- const navRoot = Array.isArray(j.navigation) ? j.navigation[0] : j.navigation;
2589
- const footRoot = Array.isArray(j.footer) ? j.footer[0] : j.footer;
2590
- const items = parseNavItems(navRoot?.items);
2591
- const logoMedia = navRoot?.logo?.media;
2592
- const logoUuid = typeof logoMedia?.media === "string" ? logoMedia.media : void 0;
2593
- const logoUrl = logoUuid ? resolveLogo(logoUuid) : null;
2594
- const maxWidth = typeof logoMedia?.["max-width"] === "string" ? logoMedia["max-width"] : void 0;
2639
+ function buildCells(node, state) {
2640
+ const u = unbox(node);
2641
+ if (u.kind === "row") return u.cells.map((c) => buildCell(c.node, 0, state, c.token));
2642
+ if (u.kind === "stack") return u.children.map((c) => buildCell(c, 0, state));
2643
+ return [buildCell(u, 0, state)];
2644
+ }
2645
+ function nodeToCells(node) {
2646
+ return buildCells(node, { flattened: false });
2647
+ }
2648
+ function cellDepthExceedsTwo(node) {
2649
+ const state = { flattened: false };
2650
+ buildCells(node, state);
2651
+ return state.flattened;
2652
+ }
2653
+ function roleWrap(role, node) {
2654
+ return role ? { tag: "div", className: `txt-role-${role}`, children: [node] } : node;
2655
+ }
2656
+ function blockPayload(node) {
2657
+ const u = node;
2658
+ switch (u.kind) {
2659
+ case "row":
2660
+ return {
2661
+ tag: "div",
2662
+ style: { display: "grid" },
2663
+ children: u.cells.map((c) => blockPayload(c.node))
2664
+ };
2665
+ case "stack":
2666
+ return { tag: "div", children: u.children.map(blockPayload) };
2667
+ // Text leaves keep their Blux type role (text1…textN): the starter theme
2668
+ // sizes text ONLY through `.txt-role-textN :is(h1..h6,p)`, so a bare heading
2669
+ // in a Block-fallback band would render unstyled. Wrap in the role div (the
2670
+ // same mechanism BluxCell uses for cell titles) so block-band headings size
2671
+ // identically to grid/section headings.
2672
+ case "heading":
2673
+ return roleWrap(u.role, { tag: `h${u.level}`, html: u.html });
2674
+ case "body":
2675
+ return roleWrap(u.role, { tag: "div", html: u.html });
2676
+ case "subtitle":
2677
+ return roleWrap(u.role, { tag: "p", html: u.text });
2678
+ case "media":
2679
+ return mediaBlock(u.media);
2680
+ case "raw":
2681
+ return { html: u.html };
2682
+ case "widget":
2683
+ return { tag: "div", html: "" };
2684
+ }
2685
+ }
2686
+ function mediaBlock(m) {
2687
+ if (m.kind === "video") return { tag: "figure", html: videoTag(m) };
2688
+ const url = m.base ? `${m.base}${m.assetId}${m.ext ? `.${m.ext}` : ""}` : m.assetId;
2689
+ return { tag: "figure", image: { url, alt: "" } };
2690
+ }
2691
+ function videoTag(m) {
2692
+ const p = m.playback ?? { controls: true, playsinline: true };
2693
+ const attrs = [];
2694
+ if (p.autoplay) attrs.push("autoplay");
2695
+ if (p.loop) attrs.push("loop");
2696
+ if (p.muted || p.autoplay) attrs.push("muted");
2697
+ if (p.controls) attrs.push("controls");
2698
+ if (p.playsinline || p.autoplay) attrs.push("playsinline");
2699
+ const src = `src="${mediaCdnUrl(m) ?? m.assetId}"`;
2700
+ return `<video ${[...attrs, src].join(" ")}></video>`;
2701
+ }
2702
+ function imgTag(m, resolveUrl, resolveAlt) {
2703
+ const src = resolveUrl(m) ?? m.assetId;
2704
+ const alt = resolveAlt(m.assetId).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2705
+ return `<img src="${src}" alt="${alt}">`;
2706
+ }
2707
+
2708
+ // src/blux/catalog/feeds.ts
2709
+ var NAME_TO_TYPE = {
2710
+ products: "product",
2711
+ "equipment grid": "product",
2712
+ "center features": "product",
2713
+ team: "person",
2714
+ reps: "person",
2715
+ trainers: "person",
2716
+ events: "event",
2717
+ "donate life observances": "event",
2718
+ news: "news_article",
2719
+ "outside the lines": "news_article",
2720
+ "all projects list": "project",
2721
+ portfolio: "project",
2722
+ projects: "project"
2723
+ };
2724
+ var normalize = (name) => name.trim().toLowerCase().replace(/\s+/g, " ");
2725
+ function feedEntityType(feedName) {
2726
+ const name = normalize(feedName);
2727
+ const exact = NAME_TO_TYPE[name];
2728
+ if (exact) return exact;
2729
+ if (/\bequipment grid$/.test(name)) return "product";
2730
+ return "collection_item";
2731
+ }
2732
+ function isSkippedFeed(feedName) {
2733
+ return normalize(feedName).startsWith("do not use");
2734
+ }
2735
+
2736
+ // src/blux/catalog/classify.ts
2737
+ var baseOf = (band, opts) => {
2738
+ const st = opts?.styles?.get(band.index) ?? {};
2739
+ const def = (band.blockClass ? opts?.defaults?.get(band.blockClass) : void 0) ?? {};
2740
+ const padding = st["padding"] ?? def.padding;
2741
+ return {
2742
+ index: band.index,
2743
+ ...band.background ? { background: band.background } : {},
2744
+ ...st["background-color"] ? { backgroundColor: st["background-color"] } : {},
2745
+ ...st["min-height"] ? { minHeight: st["min-height"] } : {},
2746
+ ...st["text-align"] ? { textAlign: st["text-align"] } : {},
2747
+ ...st["vertical-align"] === "middle" ? { verticalAlign: "middle" } : {},
2748
+ ...padding ? { contentPadding: padding } : {},
2749
+ ...def.mobilePadding ? { contentPaddingMobile: def.mobilePadding } : {},
2750
+ ...def.maxWidth ? { maxContentWidth: def.maxWidth } : {},
2751
+ ...st["_column-width"] ? { columnWidth: st["_column-width"] } : {},
2752
+ ...st["_column-side"] ? { columnSide: st["_column-side"] } : {}
2753
+ };
2754
+ };
2755
+ function sliceSpecToCatalog(spec, band, opts = {}) {
2756
+ const base2 = baseOf(band, opts);
2757
+ switch (spec.slice) {
2758
+ case "Hero":
2759
+ case "TitleBand":
2760
+ case "RichText":
2761
+ return {
2762
+ slice: "BluxSection",
2763
+ ...base2,
2764
+ ...splitHeadingAndCells(band.root)
2765
+ };
2766
+ case "SplitFeature": {
2767
+ const text = cellFromNode(spec.text);
2768
+ if (spec.media.kind !== "video" && !text.media && !text.subgrid && !text.embedHtml)
2769
+ return {
2770
+ slice: "BluxMediaText",
2771
+ ...base2,
2772
+ mediaSide: spec.mediaSide,
2773
+ layoutRatio: spec.ratio,
2774
+ media: spec.media,
2775
+ ...pick(text)
2776
+ };
2777
+ return gridOrBlock(band.root, base2);
2778
+ }
2779
+ case "MediaFull":
2780
+ case "VideoFeature":
2781
+ return { slice: "BluxMedia", ...base2, media: spec.media };
2782
+ case "Gallery":
2783
+ return {
2784
+ slice: "BluxGallery",
2785
+ ...base2,
2786
+ cells: spec.media.map((m) => ({ kind: "media", media: m }))
2787
+ };
2788
+ case "Carousel":
2789
+ return {
2790
+ slice: "BluxCarousel",
2791
+ ...base2,
2792
+ ...spec.columns !== void 0 ? { columnsVisible: spec.columns } : {},
2793
+ cells: spec.slides.map(slideCell)
2794
+ };
2795
+ case "LocationMap": {
2796
+ const widget = liftMapWidget(band, opts);
2797
+ if (!widget) return blockSpec(band.root, base2);
2798
+ return { slice: "BluxSection", ...base2, cells: [], ...widget };
2799
+ }
2800
+ case "Grid":
2801
+ default: {
2802
+ const widget = liftMapWidget(band, opts);
2803
+ if (!widget) reportUnrecoveredMount(spec.root, band, opts);
2804
+ return { ...gridOrBlock(spec.root, base2), ...widget ?? {} };
2805
+ }
2806
+ }
2807
+ }
2808
+ function bandToCatalog(band, opts = {}) {
2809
+ return sliceSpecToCatalog(classifyBand(band, opts), band, opts);
2810
+ }
2811
+ function bandOrCollection(band, item, feeds, opts = {}) {
2812
+ if (!isFeedBand(item)) return bandToCatalog(band, opts);
2813
+ const where = whereOf(band, opts);
2814
+ const feedIds = item.sources.map(String);
2815
+ if (feedIds[0] === "__media") {
2816
+ for (const id of feedIds.slice(1))
2817
+ opts.diagnostics?.push({
2818
+ kind: "skipped-feed",
2819
+ where,
2820
+ message: `band ${band.index} sources "${id}" alongside __media \u2014 the media-library grid renders sources[0] only, the extra source is not consumed`
2821
+ });
2822
+ return bandToCatalog(band, opts);
2823
+ }
2824
+ if (!isEmptyish2(band.root, (n) => isFeedTemplateMount(n) || (opts.isMapMount?.(n) ?? false))) {
2825
+ opts.diagnostics?.push({
2826
+ kind: "feed-band-misalign",
2827
+ where,
2828
+ message: `band ${band.index} has parsed content but its site.json item carries feed sources \u2014 positional join misaligned, classified as content`
2829
+ });
2830
+ return bandToCatalog(band, opts);
2831
+ }
2832
+ const validIds = [];
2833
+ for (const id of feedIds) {
2834
+ if (id === "__media") {
2835
+ opts.diagnostics?.push({
2836
+ kind: "skipped-feed",
2837
+ where,
2838
+ message: `band ${band.index} sources the __media media-library sentinel alongside feed sources \u2014 it only renders as sources[0]; dropped from the collection`
2839
+ });
2840
+ continue;
2841
+ }
2842
+ const feed = feeds[id];
2843
+ const name = String(feed?.name ?? "");
2844
+ if (!feed) {
2845
+ opts.diagnostics?.push({
2846
+ kind: "skipped-feed",
2847
+ where,
2848
+ message: `band ${band.index} sources unknown feed "${id}" \u2014 the collection will resolve no documents from it`
2849
+ });
2850
+ } else if (isSkippedFeed(name)) {
2851
+ opts.diagnostics?.push({
2852
+ kind: "skipped-feed",
2853
+ where,
2854
+ message: `band ${band.index} sources feed "${name}" (marked DO NOT USE \u2014 not migrated) \u2014 the collection will resolve no documents from it`
2855
+ });
2856
+ } else {
2857
+ validIds.push(id);
2858
+ }
2859
+ }
2860
+ const keptIds = [...new Set(validIds.length ? validIds : feedIds)];
2861
+ const feedName = String(feeds[keptIds[0]]?.name ?? "");
2862
+ const widget = liftMapWidget(band, opts);
2863
+ const cfg = item.sourceConfig ?? {};
2864
+ const filterTag = cfg["filters"]?.tag;
2865
+ const sort = cfg["sort"];
2866
+ const limit = Number(cfg["limit"]);
2867
+ const mediaRatio = cropRatioOf(cfg["mediaRatio"] ?? cfg["ratio"]);
2868
+ const { heading: heading2 } = splitHeadingAndCells(band.root);
2869
+ const spec = {
2870
+ slice: "BluxCollection",
2871
+ ...baseOf(band, opts),
2872
+ ...heading2 ? { heading: heading2 } : {},
2873
+ entityType: feedEntityType(feedName),
2874
+ feedIds: keptIds,
2875
+ ...typeof filterTag === "string" && filterTag ? { filterTag } : {},
2876
+ ...typeof sort === "string" && sort ? { sort } : {},
2877
+ ...Number.isFinite(limit) && limit > 0 ? { limit } : {},
2878
+ ...mediaRatio ? { mediaRatio } : {},
2879
+ layout: item.type === "slides" ? "carousel" : "grid",
2880
+ ...cfg["scrollLoadMore"] === true ? { scrollLoadMore: true } : {},
2881
+ ...widget ?? {}
2882
+ };
2883
+ return spec;
2884
+ }
2885
+ function isFeedTemplateMount(node) {
2886
+ return node.kind === "raw" && /\bdata-exec="custom_[a-f0-9_]+"/.test(node.html) && !hasVisibleContent(node.html);
2887
+ }
2888
+ function isEmptyish2(root, ignore) {
2889
+ if (ignore?.(root)) return true;
2890
+ switch (root.kind) {
2891
+ case "heading":
2892
+ case "subtitle":
2893
+ return true;
2894
+ case "raw":
2895
+ return root.html.trim() === "";
2896
+ case "media":
2897
+ case "widget":
2898
+ return false;
2899
+ case "row": {
2900
+ const cells = ignore ? root.cells.filter((c) => !isIgnoredSubtree(c.node, ignore)) : root.cells;
2901
+ return cells.length === 0;
2902
+ }
2903
+ case "stack":
2904
+ return root.children.every((c) => isEmptyish2(c, ignore));
2905
+ default:
2906
+ return false;
2907
+ }
2908
+ }
2909
+ function findMountHtml(node, isMount) {
2910
+ if (node.kind === "raw" && isMount(node)) return node.html;
2911
+ const children = node.kind === "row" ? node.cells.map((c) => c.node) : node.kind === "stack" ? node.children : [];
2912
+ for (const c of children) {
2913
+ const found = findMountHtml(c, isMount);
2914
+ if (found !== void 0) return found;
2915
+ }
2916
+ return void 0;
2917
+ }
2918
+ function whereOf(band, opts) {
2919
+ return opts.pageUid !== void 0 ? `${opts.pageUid}:${band.index}` : String(band.index);
2920
+ }
2921
+ function liftMapWidget(band, opts) {
2922
+ const mountHtml = opts.isMapMount ? findMountHtml(band.root, opts.isMapMount) : void 0;
2923
+ if (mountHtml === void 0) return void 0;
2924
+ return {
2925
+ widgetKind: "map",
2926
+ widgetHtml: mountHtml,
2927
+ ...opts.mapConfig ? { mapConfig: opts.mapConfig } : {}
2928
+ };
2929
+ }
2930
+ function reportUnrecoveredMount(rewrittenRoot, band, opts) {
2931
+ if (!containsWidgetNode(rewrittenRoot)) return;
2932
+ opts.diagnostics?.push({
2933
+ kind: "dropped-widget",
2934
+ where: whereOf(band, opts),
2935
+ message: `band ${band.index} holds a widget mount but no mount html was recovered (no predicate, or one that no longer matches band.root) \u2014 the widget is dropped`
2936
+ });
2937
+ }
2938
+ function containsWidgetNode(node) {
2939
+ if (node.kind === "widget") return true;
2940
+ const children = node.kind === "row" ? node.cells.map((c) => c.node) : node.kind === "stack" ? node.children : [];
2941
+ return children.some(containsWidgetNode);
2942
+ }
2943
+ function isIgnoredSubtree(node, ignore) {
2944
+ if (ignore(node)) return true;
2945
+ return node.kind === "stack" && node.children.length > 0 && node.children.every((c) => isIgnoredSubtree(c, ignore));
2946
+ }
2947
+ function slideCell(s) {
2948
+ return {
2949
+ kind: "media",
2950
+ media: s.media,
2951
+ ...s.caption ? { title: `<h${s.caption.level}>${s.caption.html}</h${s.caption.level}>` } : {},
2952
+ ...s.subcaption ? { bodyHtml: s.subcaption.html } : {}
2953
+ };
2954
+ }
2955
+ function gridOrBlock(root, base2) {
2956
+ if (cellDepthExceedsTwo(root)) return blockSpec(root, base2);
2957
+ const columns = gridColumns(root);
2958
+ return {
2959
+ slice: "BluxGrid",
2960
+ ...base2,
2961
+ ...columns !== void 0 ? { columns } : {},
2962
+ ...splitHeadingAndCells(root)
2963
+ };
2964
+ }
2965
+ function blockSpec(root, base2) {
2966
+ return {
2967
+ slice: "BluxBlock",
2968
+ ...base2,
2969
+ payload: blockPayload(root),
2970
+ // Surfaced so emit uploads the payload's assets; the payload's inlined
2971
+ // urls remain CDN until the 4d migrate-time rewrite.
2972
+ media: collectMedia(root)
2973
+ };
2974
+ }
2975
+ function gridColumns(root) {
2976
+ const findRow = (n) => {
2977
+ if (n.kind === "row") return n;
2978
+ if (n.kind === "stack") {
2979
+ for (const c of n.children) {
2980
+ const r = findRow(c);
2981
+ if (r) return r;
2982
+ }
2983
+ }
2984
+ return null;
2985
+ };
2986
+ const row = findRow(root);
2987
+ if (!row || row.cells.length === 0) return void 0;
2988
+ const cols = row.cells[0]?.token.cols;
2989
+ return typeof cols === "number" ? cols : row.cells.length;
2990
+ }
2991
+ function splitHeadingAndCells(root) {
2992
+ const cells = nodeToCells(root).filter(
2993
+ (c) => c.title || c.bodyHtml || c.media || c.subgrid || c.embedHtml
2994
+ );
2995
+ const hIdx = cells.findIndex(
2996
+ (c) => c.title && !c.bodyHtml && !c.media && !c.subgrid && !c.embedHtml
2997
+ );
2998
+ const h = hIdx >= 0 ? cells[hIdx] : void 0;
2999
+ if (h)
3000
+ return {
3001
+ ...h.title ? { heading: h.title } : {},
3002
+ ...h.titleRole ? { headingRole: h.titleRole } : {},
3003
+ cells: cells.filter((_, i) => i !== hIdx)
3004
+ };
3005
+ return { cells };
3006
+ }
3007
+ function pick(c) {
3008
+ return {
3009
+ ...c.title ? { title: c.title } : {},
3010
+ ...c.bodyHtml ? { body: c.bodyHtml } : {}
3011
+ };
3012
+ }
3013
+
3014
+ // src/blux/catalog/entities.ts
3015
+ var BASE_MAIN = {
3016
+ uid: { type: "UID", config: { label: "uid" } },
3017
+ title: {
3018
+ type: "StructuredText",
3019
+ config: { label: "title", single: "heading1" }
3020
+ },
3021
+ body: {
3022
+ type: "StructuredText",
3023
+ config: {
3024
+ label: "body",
3025
+ multi: "paragraph,strong,em,hyperlink,list-item"
3026
+ }
3027
+ },
3028
+ media: {
3029
+ type: "Image",
3030
+ config: { label: "media", constraint: {}, thumbnails: [] }
3031
+ },
3032
+ gallery: {
3033
+ type: "Group",
3034
+ config: {
3035
+ label: "gallery",
3036
+ fields: {
3037
+ image: {
3038
+ type: "Image",
3039
+ config: { label: "image", constraint: {}, thumbnails: [] }
3040
+ },
3041
+ caption: { type: "Text", config: { label: "caption" } }
3042
+ }
3043
+ }
3044
+ },
3045
+ tags: { type: "Text", config: { label: "tags (comma-separated)" } },
3046
+ date: { type: "Date", config: { label: "date" } },
3047
+ link: { type: "Link", config: { label: "link", allowTargetBlank: true } }
3048
+ };
3049
+ var BASE_KEYS = /* @__PURE__ */ new Set([
3050
+ "title",
3051
+ "body",
3052
+ "media",
3053
+ "items",
3054
+ "tags",
3055
+ "date",
3056
+ "link_url",
3057
+ "uid",
3058
+ "gallery",
3059
+ "link"
3060
+ ]);
3061
+ var isStyleKey2 = (key) => key.startsWith("_");
3062
+ var EXT_FIELD_CONFIG = {
3063
+ text: (label) => ({ type: "Text", config: { label } }),
3064
+ // deriveFields convention: mirrors the base body field's model config.
3065
+ richtext: (label) => ({
3066
+ type: "StructuredText",
3067
+ config: { label, multi: "paragraph,strong,em,hyperlink,list-item" }
3068
+ }),
3069
+ boolean: (label) => ({ type: "Boolean", config: { label } }),
3070
+ number: (label) => ({ type: "Number", config: { label } }),
3071
+ group: (label) => ({
3072
+ type: "Group",
3073
+ config: {
3074
+ label,
3075
+ fields: { value: { type: "Text", config: { label: "value" } } }
3076
+ }
3077
+ })
3078
+ };
3079
+ var kindOf = (key, value) => {
3080
+ if (key === "description" && typeof value === "string") return "richtext";
3081
+ if (typeof value === "boolean") return "boolean";
3082
+ if (typeof value === "number") return "number";
3083
+ if (Array.isArray(value)) return "group";
3084
+ return "text";
3085
+ };
3086
+ var typeLabel = (type) => type.split("_").map((w) => w ? w[0].toUpperCase() + w.slice(1) : w).join(" ");
3087
+ var mediaUuidOf = (v) => {
3088
+ if (v && typeof v === "object") {
3089
+ const m = v.media;
3090
+ if (typeof m === "string" && m) return m;
3091
+ }
3092
+ return void 0;
3093
+ };
3094
+ function extEntries(record) {
3095
+ return Object.entries(record).filter(
3096
+ ([key, value]) => !isStyleKey2(key) && !BASE_KEYS.has(key) && value !== void 0
3097
+ );
3098
+ }
3099
+ function normalizeRecord(record) {
3100
+ if (record.disable === void 0) return record;
3101
+ const { disable, ...rest } = record;
3102
+ return {
3103
+ ...rest,
3104
+ disabled: record.disabled === true || disable === true ? true : record.disabled ?? disable
3105
+ };
3106
+ }
3107
+ var BARE_SLUG_RE = /^[a-z0-9_-]+$/i;
3108
+ var EXTERNAL_RE = /^https?:\/\//i;
3109
+ function recordUid2(record) {
3110
+ const url = typeof record.url === "string" ? record.url.trim() : "";
3111
+ const fromUrl = url.replace(/^\/+products\/+/i, "").replace(/^\/+|\/+$/g, "");
3112
+ if (fromUrl && BARE_SLUG_RE.test(fromUrl)) return fromUrl.toLowerCase();
3113
+ return String(record.title ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3114
+ }
3115
+ var DATE_RE = /^(\d{4})-(\d{1,2})-(\d{1,2})$/;
3116
+ var stripTime = (s) => s.replace(/(?:T|\s+)\d{1,2}:\d{2}\S*$/, "").trim();
3117
+ function normalizeDate(raw, feedVote) {
3118
+ if (typeof raw !== "string") return { issue: "unparseable" };
3119
+ const trimmed = stripTime(raw.trim());
3120
+ const m = DATE_RE.exec(trimmed);
3121
+ if (!m) return { issue: "unparseable" };
3122
+ const year = m[1];
3123
+ const mid = Number(m[2]);
3124
+ const last = Number(m[3]);
3125
+ const build = (month, day) => {
3126
+ if (month < 1 || month > 12 || day < 1 || day > 31) return void 0;
3127
+ const d = new Date(Date.UTC(Number(year), month - 1, day));
3128
+ if (d.getUTCFullYear() !== Number(year) || d.getUTCMonth() !== month - 1 || d.getUTCDate() !== day)
3129
+ return void 0;
3130
+ return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
3131
+ };
3132
+ if (/^\d{4}-\d{2}-\d{2}$/.test(trimmed) && build(mid, last) === trimmed) return { date: trimmed };
3133
+ if (mid > 12 && last > 12) return { issue: "unparseable" };
3134
+ if (mid > 12 || last > 12) {
3135
+ const date2 = mid > 12 ? build(last, mid) : build(mid, last);
3136
+ return date2 ? { date: date2 } : { issue: "unparseable" };
3137
+ }
3138
+ const date = feedVote === "year-day-month" ? build(last, mid) : build(mid, last);
3139
+ if (date === void 0) return { issue: "unparseable" };
3140
+ return feedVote ? { date } : { date, issue: "ambiguous" };
3141
+ }
3142
+ function feedDateOrientation(items) {
3143
+ let ymd = 0;
3144
+ let ydm = 0;
3145
+ for (const it of items) {
3146
+ const raw = it && typeof it === "object" ? it.date : void 0;
3147
+ if (typeof raw !== "string") continue;
3148
+ const m = DATE_RE.exec(stripTime(raw.trim()));
3149
+ if (!m) continue;
3150
+ const mid = Number(m[2]);
3151
+ const last = Number(m[3]);
3152
+ if (mid > 12 && mid <= 31 && last >= 1 && last <= 12) ydm++;
3153
+ else if (last > 12 && last <= 31 && mid >= 1 && mid <= 12) ymd++;
3154
+ }
3155
+ if (ydm > ymd) return "year-day-month";
3156
+ if (ymd > ydm) return "year-month-day";
3157
+ return null;
3158
+ }
3159
+ var titleOf = (record) => typeof record.title === "string" && record.title.trim() ? record.title.trim() : "(untitled)";
3160
+ function recordToDoc(record, type, uid, ctx) {
3161
+ const data = {};
3162
+ const media = [];
3163
+ if (typeof record.title === "string" && record.title.trim())
3164
+ data.title = richText(`<h1>${record.title}</h1>`);
3165
+ if (typeof record.body === "string" && record.body.trim())
3166
+ data.body = richText(demoteHeadingsHtml(record.body));
3167
+ const mainUuid = mediaUuidOf(record.media);
3168
+ if (mainUuid) {
3169
+ data.media = assetRef(mainUuid);
3170
+ media.push({ kind: "image", assetId: mainUuid });
3171
+ }
3172
+ if (Array.isArray(record.items)) {
3173
+ const gallery = [];
3174
+ for (const it of record.items) {
3175
+ const item = it && typeof it === "object" ? it : void 0;
3176
+ const uuid = item ? mediaUuidOf(item.media) : void 0;
3177
+ if (!uuid) continue;
3178
+ media.push({ kind: "image", assetId: uuid });
3179
+ const caption = typeof item?.caption === "string" ? item.caption : typeof item?.title === "string" ? item.title : "";
3180
+ gallery.push({ image: assetRef(uuid), caption });
3181
+ }
3182
+ if (gallery.length) data.gallery = gallery;
3183
+ }
3184
+ if (Array.isArray(record.tags))
3185
+ data.tags = record.tags.filter((t) => typeof t === "string").join(",");
3186
+ else if (typeof record.tags === "string" && record.tags) data.tags = record.tags;
3187
+ if (record.date != null && record.date !== "") {
3188
+ const norm = normalizeDate(record.date, ctx.dateVote);
3189
+ if (norm.date !== void 0) data.date = norm.date;
3190
+ if (norm.issue === "ambiguous")
3191
+ ctx.diagnostics.push({
3192
+ kind: "ambiguous-date",
3193
+ where: ctx.feedId,
3194
+ message: `record "${titleOf(record)}" date "${String(record.date)}" is orientation-ambiguous with no feed evidence \u2014 read as year-month-day "${norm.date}"`
3195
+ });
3196
+ else if (norm.issue === "unparseable")
3197
+ ctx.diagnostics.push({
3198
+ kind: "malformed-feed-field",
3199
+ where: ctx.feedId,
3200
+ message: `record "${titleOf(record)}" date "${String(record.date)}" does not normalize to YYYY-MM-DD \u2014 omitted`
3201
+ });
3202
+ }
3203
+ const urlVal = typeof record.url === "string" ? record.url.trim() : "";
3204
+ const linkUrlVal = typeof record.link_url === "string" ? record.link_url.trim() : "";
3205
+ const external = [urlVal, linkUrlVal].find((v) => v && EXTERNAL_RE.test(v));
3206
+ if (external) data.link = { link_type: "Web", url: external };
3207
+ if (linkUrlVal && !EXTERNAL_RE.test(linkUrlVal))
3208
+ ctx.diagnostics.push({
3209
+ kind: "malformed-feed-field",
3210
+ where: ctx.feedId,
3211
+ message: `record "${titleOf(record)}" link_url "${linkUrlVal}" is not an external http(s) url \u2014 dropped (the base link field is external-only)`
3212
+ });
3213
+ for (const [key, value] of extEntries(record)) {
3214
+ if (ctx.extKinds.get(key) === "group")
3215
+ data[key] = (Array.isArray(value) ? value : [value]).map((x) => ({
3216
+ value: String(x)
3217
+ }));
3218
+ else if (key === "description" && typeof value === "string")
3219
+ data[key] = richText(demoteHeadingsHtml(value));
3220
+ else if (Array.isArray(value))
3221
+ data[key] = value.map((x) => ({ value: String(x) }));
3222
+ else data[key] = value;
3223
+ }
3224
+ return { doc: { type, uid, data }, media };
3225
+ }
3226
+ function buildEntityEmit(feeds) {
3227
+ const diagnostics = [];
3228
+ const media = [];
3229
+ const typeOrder = [];
3230
+ const docsByType = /* @__PURE__ */ new Map();
3231
+ const extByType = /* @__PURE__ */ new Map();
3232
+ const kept = [];
3233
+ for (const [feedId, feed] of Object.entries(feeds)) {
3234
+ if (!feed) continue;
3235
+ const name = String(feed.name ?? "");
3236
+ if (isSkippedFeed(name)) {
3237
+ diagnostics.push({
3238
+ kind: "skipped-feed",
3239
+ where: feedId,
3240
+ message: `feed "${name}" is marked DO NOT USE \u2014 not migrated`
3241
+ });
3242
+ continue;
3243
+ }
3244
+ const type = feedEntityType(name);
3245
+ if (!docsByType.has(type)) {
3246
+ typeOrder.push(type);
3247
+ docsByType.set(type, /* @__PURE__ */ new Map());
3248
+ extByType.set(type, /* @__PURE__ */ new Map());
3249
+ }
3250
+ const extKinds = extByType.get(type);
3251
+ const items = Array.isArray(feed.items) ? feed.items : [];
3252
+ kept.push({ feedId, type, items });
3253
+ for (const item of items) {
3254
+ if (!item || typeof item !== "object") continue;
3255
+ const record = normalizeRecord(item);
3256
+ for (const [key, value] of extEntries(record)) {
3257
+ const next = kindOf(key, value);
3258
+ const prev = extKinds.get(key);
3259
+ if (!prev || prev === "text" || next === "group") extKinds.set(key, next);
3260
+ }
3261
+ }
3262
+ }
3263
+ for (const { feedId, type, items } of kept) {
3264
+ const byUid = docsByType.get(type);
3265
+ const extKinds = extByType.get(type);
3266
+ const dateVote = feedDateOrientation(items);
3267
+ items.forEach((item, i) => {
3268
+ if (!item || typeof item !== "object") return;
3269
+ const record = normalizeRecord(item);
3270
+ const uid = recordUid2(record) || `item-${i}`;
3271
+ const { doc, media: recMedia } = recordToDoc(record, type, uid, {
3272
+ feedId,
3273
+ dateVote,
3274
+ diagnostics,
3275
+ extKinds
3276
+ });
3277
+ media.push(...recMedia);
3278
+ const disabled = record.disabled === true;
3279
+ const existing = byUid.get(uid);
3280
+ const dropped = !existing || existing.disabled && !disabled ? existing : { title: titleOf(record) };
3281
+ if (dropped)
3282
+ diagnostics.push({
3283
+ kind: "uid-collision",
3284
+ where: feedId,
3285
+ message: `record "${dropped.title}" collides on uid "${uid}" \u2014 dropped (enabled beats disabled, else first-seen wins)`
3286
+ });
3287
+ if (!existing || existing.disabled && !disabled)
3288
+ byUid.set(uid, { doc, disabled, title: titleOf(record) });
3289
+ });
3290
+ }
3291
+ const documents = [];
3292
+ const customTypes = [];
3293
+ for (const type of typeOrder) {
3294
+ for (const { doc } of docsByType.get(type).values()) documents.push(doc);
3295
+ const Main = { ...BASE_MAIN };
3296
+ for (const [key, kind] of extByType.get(type)) Main[key] = EXT_FIELD_CONFIG[kind](key);
3297
+ const label = typeLabel(type);
3298
+ customTypes.push({
3299
+ id: type,
3300
+ label,
3301
+ repeatable: true,
3302
+ json: {
3303
+ id: type,
3304
+ label,
3305
+ format: "custom",
3306
+ repeatable: true,
3307
+ status: true,
3308
+ json: { Main }
3309
+ }
3310
+ });
3311
+ }
3312
+ return { documents, customTypes, media, diagnostics };
3313
+ }
3314
+
3315
+ // src/blux/catalog/rewrite-doc-urls.ts
3316
+ var CDN_URL_RE = /https?:\/\/[a-z0-9.-]*cloudfront\.net\/[^\s"'\\)]+/g;
3317
+ function rewriteValueUrls(value, urlByCdn) {
3318
+ let rewritten = 0;
3319
+ const unmatched = /* @__PURE__ */ new Set();
3320
+ const entries = [...urlByCdn].sort(([a], [b]) => b.length - a.length);
3321
+ const swapString = (s) => {
3322
+ if (!s.includes("cloudfront.net")) return s;
3323
+ let out = s;
3324
+ for (const [cdn, prismic] of entries) {
3325
+ if (!out.includes(cdn)) continue;
3326
+ const parts = out.split(cdn);
3327
+ rewritten += parts.length - 1;
3328
+ out = parts.join(prismic);
3329
+ }
3330
+ for (const m of out.matchAll(CDN_URL_RE)) unmatched.add(m[0]);
3331
+ return out;
3332
+ };
3333
+ const walk = (v) => {
3334
+ if (typeof v === "string") return swapString(v);
3335
+ if (Array.isArray(v)) return v.map(walk);
3336
+ if (v && typeof v === "object")
3337
+ return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, walk(x)]));
3338
+ return v;
3339
+ };
3340
+ return { value: walk(value), rewritten, unmatched: [...unmatched] };
3341
+ }
3342
+ function collectCdnUrls(value) {
3343
+ const found = /* @__PURE__ */ new Set();
3344
+ const walk = (v) => {
3345
+ if (typeof v === "string") {
3346
+ if (!v.includes("cloudfront.net")) return;
3347
+ for (const m of v.matchAll(CDN_URL_RE)) found.add(m[0]);
3348
+ } else if (Array.isArray(v)) {
3349
+ v.forEach(walk);
3350
+ } else if (v && typeof v === "object") {
3351
+ Object.values(v).forEach(walk);
3352
+ }
3353
+ };
3354
+ walk(value);
3355
+ return [...found];
3356
+ }
3357
+ function rewriteDocUrls(documents, urlByCdn) {
3358
+ const r = rewriteValueUrls(documents, urlByCdn);
3359
+ return { documents: r.value, rewritten: r.rewritten, unmatched: r.unmatched };
3360
+ }
3361
+
3362
+ // src/blux/catalog/emit.ts
3363
+ function clampHeading(level, min, max) {
3364
+ return Math.min(Math.max(level, min), max);
3365
+ }
3366
+ function clampHeadingHtml(html, min, max) {
3367
+ const m = /^<h([1-6])>([\s\S]*)<\/h\1>$/.exec(html.trim());
3368
+ if (!m) return html;
3369
+ const level = clampHeading(Number(m[1]), min, max);
3370
+ return `<h${level}>${m[2]}</h${level}>`;
3371
+ }
3372
+ var MOUNT_RE = /data-exec="(custom_[a-f0-9_]+)"/;
3373
+ function reportDrop(ctx, html) {
3374
+ const uuid = MOUNT_RE.exec(html)?.[1];
3375
+ ctx.diagnostics?.push({
3376
+ kind: "dropped-widget",
3377
+ where: ctx.pageUid !== void 0 ? `${ctx.pageUid}:${ctx.index}` : `band ${ctx.index}`,
3378
+ message: uuid ? `custom widget ${uuid} is a behavior script (no visible content) \u2014 not migrated` : "embed html has no visible content after sanitize \u2014 not migrated"
3379
+ });
3380
+ }
3381
+ function cellToItem(cell, ctx) {
3382
+ const video = cell.media?.kind === "video" ? videoTag(cell.media) : void 0;
3383
+ const embedHtml = cell.embedHtml ? sanitizeHtml(cell.embedHtml) : void 0;
3384
+ const embeds = [video, embedHtml].filter((s) => Boolean(s && s.trim()));
3385
+ return {
3386
+ kind: cell.kind,
3387
+ ...cell.title ? { title: richText(clampHeadingHtml(cell.title, 3, 4)) } : {},
3388
+ // body_html is a PLAIN Text string (no richText marker) rendered via {@html}
3389
+ // on the starter side, so it IS sanitized here — the same boundary treatment
3390
+ // as embed_html and the BluxBlock payload. sanitizeHtml is a scalpel: it
3391
+ // strips <script>/on*=/javascript: only and passes ALL other markup through
3392
+ // verbatim, including the txt-role-* class the roled render depends on — so
3393
+ // sanitizing closes the {@html} XSS surface at zero fidelity cost. Input is
3394
+ // first-party parser structured-text html (heading/body/subtitle); raw/embed
3395
+ // html still routes through the sanitized embedHtml path.
3396
+ ...cell.bodyHtml ? { body_html: sanitizeHtml(cell.bodyHtml) } : {},
3397
+ // A subgrid (depth-2) image can't ride an Image field — the Migration API
3398
+ // can't resolve a doubly-nested Image ref — so it emits `image_embed` (a
3399
+ // url-based <img>, src rewritten to Prismic at migrate). Top-level images
3400
+ // stay Image fields (they resolve fine + keep PrismicImage's srcset).
3401
+ ...cell.media && cell.media.kind !== "video" ? ctx.inSubgrid ? {
3402
+ image_embed: sanitizeHtml(
3403
+ imgTag(cell.media, ctx.resolveUrl ?? mediaCdnUrl, ctx.resolveAlt ?? (() => ""))
3404
+ )
3405
+ } : { media: assetRef(cell.media.assetId) } : {},
3406
+ ...cell.mediaRatio ? { media_ratio: cell.mediaRatio } : {},
3407
+ // Per-cell visual fields (Blux catalog visual layer). `cover`/`valign` are
3408
+ // Text flags emitting the literal "on" (mirrors scroll_load_more).
3409
+ ...cell.width ? { width: cell.width } : {},
3410
+ ...cell.spacing !== void 0 ? { spacing: cell.spacing } : {},
3411
+ ...cell.cover ? { cover: "on" } : {},
3412
+ ...cell.valign ? { valign: "on" } : {},
3413
+ ...cell.backgroundColor ? { background_color: cell.backgroundColor } : {},
3414
+ ...cell.contentPadding ? { content_padding: cell.contentPadding } : {},
3415
+ ...cell.titleRole ? { title_role: cell.titleRole } : {},
3416
+ ...embeds.length ? { embed_html: embeds.join("\n") } : {},
3417
+ ...cell.subgrid ? { subgrid: emitCells(cell.subgrid, { ...ctx, inSubgrid: true }) } : {}
3418
+ };
3419
+ }
3420
+ function emitCells(cells, ctx) {
3421
+ const kept = [];
3422
+ for (const c of cells) {
3423
+ if (c.kind === "embed" && c.embedHtml && !hasVisibleContent(c.embedHtml)) {
3424
+ reportDrop(ctx, c.embedHtml);
3425
+ continue;
3426
+ }
3427
+ kept.push(cellToItem(c, ctx));
3428
+ }
3429
+ return kept;
3430
+ }
3431
+ function sliceOf(type, primary) {
3432
+ return { slice_type: type, variation: "default", items: [], primary };
3433
+ }
3434
+ function heading(spec) {
3435
+ return "heading" in spec && spec.heading ? { heading: richText(clampHeadingHtml(spec.heading, 2, 3)) } : {};
3436
+ }
3437
+ function widgetFields(spec) {
3438
+ if (!spec.widgetHtml) return {};
3439
+ const clean = sanitizeHtml(spec.widgetHtml);
3440
+ const html = spec.mapConfig ? `<div class="blux-map" data-map-config='${JSON.stringify(spec.mapConfig).replace(/'/g, "&#39;")}'>${clean}</div>` : clean;
3441
+ return {
3442
+ ...spec.widgetKind ? { widget_kind: spec.widgetKind } : {},
3443
+ widget_html: html
3444
+ };
3445
+ }
3446
+ function bandVisualCore(spec) {
3447
+ return {
3448
+ ...spec.minHeight ? { min_height: spec.minHeight } : {},
3449
+ ...spec.maxContentWidth ? { max_content_width: spec.maxContentWidth } : {},
3450
+ ...spec.verticalAlign ? { vertical_align: spec.verticalAlign } : {}
3451
+ };
3452
+ }
3453
+ function bandVisual(spec) {
3454
+ return {
3455
+ ...spec.minHeight ? { min_height: spec.minHeight } : {},
3456
+ ...spec.contentPadding ? { content_padding: spec.contentPadding } : {},
3457
+ ...spec.contentPaddingMobile ? { content_padding_mobile: spec.contentPaddingMobile } : {},
3458
+ ...spec.maxContentWidth ? { max_content_width: spec.maxContentWidth } : {},
3459
+ ...spec.verticalAlign ? { vertical_align: spec.verticalAlign } : {},
3460
+ ...spec.textAlign ? { text_align: spec.textAlign } : {},
3461
+ ...spec.columnWidth ? { column_width: spec.columnWidth } : {},
3462
+ ...spec.columnSide ? { column_side: spec.columnSide } : {},
3463
+ ...spec.headingRole ? { heading_role: spec.headingRole } : {}
3464
+ };
3465
+ }
3466
+ function catalogSpecToPlanSlice(spec, diagnostics, pageUid, media) {
3467
+ const ctx = {
3468
+ index: spec.index,
3469
+ ...pageUid !== void 0 ? { pageUid } : {},
3470
+ ...diagnostics ? { diagnostics } : {},
3471
+ ...media ? { resolveUrl: media.resolveUrl, resolveAlt: media.resolveAlt } : {}
3472
+ };
3473
+ const bg = spec.background ? { background_image: assetRef(spec.background.assetId) } : {};
3474
+ const bgc = spec.backgroundColor ? { background_color: spec.backgroundColor } : {};
3475
+ switch (spec.slice) {
3476
+ case "BluxSection":
3477
+ return sliceOf("blux_section", {
3478
+ ...bg,
3479
+ ...bgc,
3480
+ ...bandVisual(spec),
3481
+ ...heading(spec),
3482
+ ...widgetFields(spec),
3483
+ cells: emitCells(spec.cells, ctx)
3484
+ });
3485
+ case "BluxGrid":
3486
+ return sliceOf("blux_grid", {
3487
+ ...bg,
3488
+ ...bgc,
3489
+ ...bandVisual(spec),
3490
+ ...heading(spec),
3491
+ ...widgetFields(spec),
3492
+ ...spec.columns ? { columns: spec.columns } : {},
3493
+ cells: emitCells(spec.cells, ctx)
3494
+ });
3495
+ case "BluxGallery":
3496
+ return sliceOf("blux_gallery", {
3497
+ ...bg,
3498
+ ...bgc,
3499
+ ...bandVisualCore(spec),
3500
+ ...heading(spec),
3501
+ cells: emitCells(spec.cells, ctx)
3502
+ });
3503
+ case "BluxCarousel":
3504
+ return sliceOf("blux_carousel", {
3505
+ ...bg,
3506
+ ...bgc,
3507
+ ...bandVisualCore(spec),
3508
+ ...heading(spec),
3509
+ ...spec.columnsVisible ? { columns_visible: spec.columnsVisible } : {},
3510
+ cells: emitCells(spec.cells, ctx)
3511
+ });
3512
+ case "BluxMedia":
3513
+ return sliceOf("blux_media", {
3514
+ ...bg,
3515
+ ...bgc,
3516
+ // Video plays from its CDN url via `video_embed` — an Image-field
3517
+ // marker would render a broken <img> in the starter (PrismicImage).
3518
+ ...spec.media.kind === "video" ? { video_embed: videoTag(spec.media) } : { media: assetRef(spec.media.assetId) },
3519
+ ...spec.caption ? { caption: richText(spec.caption) } : {}
3520
+ });
3521
+ case "BluxMediaText":
3522
+ return sliceOf("blux_media_text", {
3523
+ ...bg,
3524
+ ...bgc,
3525
+ media: assetRef(spec.media.assetId),
3526
+ media_side: spec.mediaSide,
3527
+ ...spec.layoutRatio ? { layout_ratio: spec.layoutRatio } : {},
3528
+ ...spec.title ? { title: richText(clampHeadingHtml(spec.title, 3, 4)) } : {},
3529
+ ...spec.body ? { body: richText(spec.body) } : {}
3530
+ });
3531
+ case "BluxCollection":
3532
+ return sliceOf("blux_collection", {
3533
+ ...bg,
3534
+ ...bgc,
3535
+ ...heading(spec),
3536
+ ...widgetFields(spec),
3537
+ collection_type: spec.entityType,
3538
+ feed_ids: spec.feedIds.join(","),
3539
+ ...spec.filterTag ? { filter_tag: spec.filterTag } : {},
3540
+ ...spec.sort ? { sort: spec.sort } : {},
3541
+ ...spec.limit ? { limit: spec.limit } : {},
3542
+ ...spec.mediaRatio ? { media_ratio: spec.mediaRatio } : {},
3543
+ layout: spec.layout,
3544
+ ...spec.scrollLoadMore ? { scroll_load_more: "on" } : {}
3545
+ });
3546
+ case "BluxBlock": {
3547
+ const wrapStyle = {
3548
+ ...spec.background ? {
3549
+ "background-image": `url(${mediaCdnUrl(spec.background) ?? spec.background.assetId})`
3550
+ } : {},
3551
+ ...spec.backgroundColor ? { "background-color": spec.backgroundColor } : {}
3552
+ };
3553
+ const clean = sanitizePayload(spec.payload, ctx);
3554
+ const payload = Object.keys(wrapStyle).length ? { tag: "div", style: wrapStyle, children: [clean] } : clean;
3555
+ return sliceOf("blux_block", {
3556
+ ...widgetFields(spec),
3557
+ payload: JSON.stringify(payload)
3558
+ });
3559
+ }
3560
+ }
3561
+ }
3562
+ function sanitizePayload(node, ctx) {
3563
+ if (node.html !== void 0 && MOUNT_RE.test(node.html) && !hasVisibleContent(node.html)) {
3564
+ reportDrop(ctx, node.html);
3565
+ }
3566
+ return {
3567
+ ...node,
3568
+ ...node.html !== void 0 ? { html: sanitizeHtml(node.html) } : {},
3569
+ ...node.children ? { children: node.children.map((c) => sanitizePayload(c, ctx)) } : {}
3570
+ };
3571
+ }
3572
+ function specMedia(spec) {
3573
+ const out = [];
3574
+ if (spec.background) out.push(spec.background);
3575
+ const walk = (cells) => {
3576
+ for (const c of cells) {
3577
+ if (c.media) out.push(c.media);
3578
+ if (c.subgrid) walk(c.subgrid);
3579
+ }
3580
+ };
3581
+ switch (spec.slice) {
3582
+ case "BluxMedia":
3583
+ case "BluxMediaText":
3584
+ out.push(spec.media);
3585
+ break;
3586
+ case "BluxSection":
3587
+ case "BluxGrid":
3588
+ case "BluxGallery":
3589
+ case "BluxCarousel":
3590
+ walk(spec.cells);
3591
+ break;
3592
+ case "BluxBlock":
3593
+ out.push(...spec.media);
3594
+ break;
3595
+ }
3596
+ return out;
3597
+ }
3598
+ function buildCatalogPlan(pages, ir, feeds) {
3599
+ const diagnostics = [...ir.diagnostics ?? []];
3600
+ const entity = feeds ? buildEntityEmit(feeds) : null;
3601
+ const assetById = new Map(ir.assets.map((a) => [a.id, a]));
3602
+ const sourceUrlById = new Map(ir.assets.map((a) => [a.id, a.sourceUrl]));
3603
+ const mediaResolvers = {
3604
+ resolveUrl: (m) => mediaUrl(m, sourceUrlById),
3605
+ resolveAlt: (id) => assetById.get(id)?.alt ?? ""
3606
+ };
3607
+ const documents = pages.map((p) => ({
3608
+ type: CATALOG_PAGE_TYPE,
3609
+ uid: p.uid,
3610
+ data: {
3611
+ title: richText(`<h1>${p.title}</h1>`),
3612
+ // SEO & Metadata tab (catalog_page's second tab — see catalog-page-type.ts).
3613
+ // Field types match the starter render's reads (blux-catalog/page-doc.ts
3614
+ // pageMeta): meta_title/meta_description are Text → PLAIN strings (never a
3615
+ // richtext marker); meta_image is an Image field → an `{__asset_id}` marker
3616
+ // like cell media. meta_title defaults to the page title (a sensible SEO
3617
+ // default, always present); the other two omit when the Blux page has no
3618
+ // value (the-pointe's description is "" → meta_description omitted).
3619
+ meta_title: p.title,
3620
+ ...p.description ? { meta_description: p.description } : {},
3621
+ ...p.socialImageId ? { meta_image: assetRef(p.socialImageId) } : {},
3622
+ slices: p.specs.map((s) => catalogSpecToPlanSlice(s, diagnostics, p.uid, mediaResolvers))
3623
+ }
3624
+ }));
3625
+ if (entity) {
3626
+ documents.push(...entity.documents);
3627
+ diagnostics.push(...entity.diagnostics);
3628
+ }
3629
+ const resolve = (m) => {
3630
+ const asset = assetById.get(m.assetId);
3631
+ const url = mediaUrl(m, sourceUrlById);
3632
+ return url ? { id: m.assetId, url, alt: asset?.alt ?? "" } : null;
3633
+ };
3634
+ const seen = /* @__PURE__ */ new Set();
3635
+ const assets = [];
3636
+ const socialMedia = pages.filter((p) => p.socialImageId).map((p) => ({ kind: "image", assetId: p.socialImageId }));
3637
+ const allMedia = [
3638
+ ...pages.flatMap((p) => p.specs).flatMap(specMedia),
3639
+ ...socialMedia,
3640
+ ...entity?.media ?? []
3641
+ ];
3642
+ for (const m of allMedia) {
3643
+ if (seen.has(m.assetId)) continue;
3644
+ seen.add(m.assetId);
3645
+ const a = resolve(m);
3646
+ if (a) assets.push(a);
3647
+ else
3648
+ diagnostics.push({
3649
+ kind: "unresolved-asset",
3650
+ where: m.assetId,
3651
+ message: `media ${m.assetId} has no CDN base nor IR source url \u2014 not uploaded`
3652
+ });
3653
+ }
3654
+ const registeredUrls = new Set(assets.map((a) => a.url));
3655
+ for (const url of collectCdnUrls(documents)) {
3656
+ if (registeredUrls.has(url)) continue;
3657
+ const file = (url.split("/").pop() ?? url).split("?")[0];
3658
+ if (!/\.[a-z0-9]{2,4}$/i.test(file)) continue;
3659
+ registeredUrls.add(url);
3660
+ assets.push({ id: file.replace(/\.[^.]+$/, ""), url, alt: "" });
3661
+ }
3662
+ return {
3663
+ // The catalog_page type ALWAYS ships (the migration owns the type its
3664
+ // documents use); entity extension types (product/person/…) append. No
3665
+ // id collision — entity types are `format:"custom"`, never "catalog_page".
3666
+ customTypes: [catalogPageCustomType(), ...entity ? entity.customTypes : []],
3667
+ documents,
3668
+ assets,
3669
+ stylesManifest: [],
3670
+ diagnostics
3671
+ };
3672
+ }
3673
+
3674
+ // src/blux/catalog/resolve-fixture.ts
3675
+ import { htmlAsRichText } from "@prismicio/migrate";
3676
+ function resolveFixture(plan) {
3677
+ const assetById = new Map(plan.assets.map((a) => [a.id, a]));
3678
+ const missing = /* @__PURE__ */ new Set();
3679
+ const walk = (v) => {
3680
+ if (Array.isArray(v)) return v.map(walk);
3681
+ if (v && typeof v === "object") {
3682
+ const o = v;
3683
+ if (typeof o.__richtext_html === "string") return htmlAsRichText(o.__richtext_html).result;
3684
+ if (typeof o.__asset_id === "string") {
3685
+ const a = assetById.get(o.__asset_id);
3686
+ if (!a) {
3687
+ missing.add(o.__asset_id);
3688
+ return null;
3689
+ }
3690
+ return { url: a.url, alt: a.alt, dimensions: { width: 1600, height: 1200 } };
3691
+ }
3692
+ return Object.fromEntries(Object.entries(o).map(([k, x]) => [k, walk(x)]));
3693
+ }
3694
+ return v;
3695
+ };
3696
+ const resolved = plan.documents.map((d) => ({
3697
+ ...d,
3698
+ data: walk(d.data)
3699
+ }));
3700
+ const documents = resolved.filter((d) => d.type === CATALOG_PAGE_TYPE);
3701
+ const collections = {};
3702
+ for (const d of resolved) {
3703
+ if (d.type === CATALOG_PAGE_TYPE) continue;
3704
+ (collections[d.type] ??= []).push(d);
3705
+ }
3706
+ return { documents, collections, missingAssets: [...missing] };
3707
+ }
3708
+
3709
+ // src/blux/emit/site-config.ts
3710
+ function parseNavItems(items) {
3711
+ if (!Array.isArray(items)) return [];
3712
+ const out = [];
3713
+ for (const raw of items) {
3714
+ const label = typeof raw?.title === "string" ? raw.title.trim() : "";
3715
+ if (!label) continue;
3716
+ const href = typeof raw?.link === "string" ? raw.link : "";
3717
+ const children = parseNavItems(raw?.items);
3718
+ out.push({ label, href, ...children.length ? { children } : {} });
3719
+ }
3720
+ return out;
3721
+ }
3722
+ var SOCIAL_DOMAINS = {
3723
+ facebook: ["facebook.com", "fb.com"],
3724
+ twitter: ["twitter.com", "x.com"],
3725
+ instagram: ["instagram.com"],
3726
+ pinterest: ["pinterest.com"],
3727
+ linkedin: ["linkedin.com"],
3728
+ "linkedin-company": ["linkedin.com"],
3729
+ youtube: ["youtube.com", "youtu.be"],
3730
+ reddit: ["reddit.com"]
3731
+ };
3732
+ function hostMatches(host, domain) {
3733
+ return host === domain || host.endsWith("." + domain);
3734
+ }
3735
+ function hostOf(url) {
3736
+ try {
3737
+ return new URL(url).hostname.toLowerCase();
3738
+ } catch {
3739
+ return "";
3740
+ }
3741
+ }
3742
+ function footerRegion(html) {
3743
+ const lower = html.toLowerCase();
3744
+ const start = lower.lastIndexOf("<footer");
3745
+ if (start === -1) return html;
3746
+ const end = lower.indexOf("</footer>", start);
3747
+ return end === -1 ? html.slice(start) : html.slice(start, end + "</footer>".length);
3748
+ }
3749
+ function socialHrefResolverFromHtml(htmls) {
3750
+ const hrefs = [];
3751
+ const re = /href\s*=\s*["']([^"']+)["']/gi;
3752
+ for (const html of htmls) {
3753
+ if (typeof html !== "string") continue;
3754
+ const region = footerRegion(html);
3755
+ let m;
3756
+ while (m = re.exec(region)) {
3757
+ const url = m[1];
3758
+ if (url && /^https?:\/\//i.test(url)) hrefs.push(url);
3759
+ }
3760
+ }
3761
+ return (network) => {
3762
+ const domains = SOCIAL_DOMAINS[network];
3763
+ if (!domains) return void 0;
3764
+ return hrefs.find((u) => {
3765
+ const host = hostOf(u);
3766
+ return host && domains.some((d) => hostMatches(host, d));
3767
+ });
3768
+ };
3769
+ }
3770
+ function parseSocials(raw, resolveSocialHref) {
3771
+ const out = [];
3772
+ const items = Array.isArray(raw) ? raw : [];
3773
+ for (const it of items) {
3774
+ const media = it?.media;
3775
+ if (!media || media.type !== "social") continue;
3776
+ const networks = media.networks;
3777
+ if (!networks || typeof networks !== "object") continue;
3778
+ const urls = media.urls ?? {};
3779
+ for (const [network, on] of Object.entries(networks)) {
3780
+ if (on !== true) continue;
3781
+ const fromExport = typeof urls[network] === "string" ? urls[network] : void 0;
3782
+ const url = fromExport ?? resolveSocialHref?.(network);
3783
+ out.push({ network, ...url ? { href: url } : {} });
3784
+ }
3785
+ }
3786
+ return out;
3787
+ }
3788
+ function collectTitles(node, acc) {
3789
+ if (Array.isArray(node)) {
3790
+ for (const n of node) collectTitles(n, acc);
3791
+ return acc;
3792
+ }
3793
+ if (node && typeof node === "object") {
3794
+ const rec = node;
3795
+ if (typeof rec.title === "string" && rec.title.trim()) acc.push(rec.title.trim());
3796
+ for (const k of Object.keys(rec)) if (k !== "title") collectTitles(rec[k], acc);
3797
+ }
3798
+ return acc;
3799
+ }
3800
+ var COPYRIGHT_RE = /©|\(c\)|\bcopyright\b|rights reserved/i;
3801
+ function parseFooterText(raw) {
3802
+ return collectTitles(raw, []).find((t) => COPYRIGHT_RE.test(t));
3803
+ }
3804
+ function buildSiteConfig(siteJson, resolveLogo, resolveSocialHref) {
3805
+ const j = siteJson;
3806
+ const navRoot = Array.isArray(j.navigation) ? j.navigation[0] : j.navigation;
3807
+ const footRoot = Array.isArray(j.footer) ? j.footer[0] : j.footer;
3808
+ const items = parseNavItems(navRoot?.items);
3809
+ const logoMedia = navRoot?.logo?.media;
3810
+ const logoUuid = typeof logoMedia?.media === "string" ? logoMedia.media : void 0;
3811
+ const logoUrl = logoUuid ? resolveLogo(logoUuid) : null;
3812
+ const maxWidth = typeof logoMedia?.["max-width"] === "string" ? logoMedia["max-width"] : void 0;
2595
3813
  const socials = parseSocials(footRoot?.items, resolveSocialHref);
2596
3814
  const text = parseFooterText(footRoot?.items);
2597
3815
  return {
@@ -2606,6 +3824,63 @@ function buildSiteConfig(siteJson, resolveLogo, resolveSocialHref) {
2606
3824
  };
2607
3825
  }
2608
3826
 
3827
+ // src/blux/catalog/chrome.ts
3828
+ function displayTitle(title) {
3829
+ if (typeof title !== "string") return "";
3830
+ return title.replace(/&nbsp;/gi, " ").replace(/&amp;/gi, "&").trim();
3831
+ }
3832
+ function asHref(link) {
3833
+ return typeof link === "string" && link.trim() ? link.trim() : void 0;
3834
+ }
3835
+ function asImage(media, resolveLogo) {
3836
+ const uuid = typeof media?.media === "string" ? media.media : void 0;
3837
+ const url = uuid ? resolveLogo(uuid) : null;
3838
+ if (!url) return void 0;
3839
+ const maxWidth = typeof media?.["max-width"] === "string" ? media["max-width"] : void 0;
3840
+ return { url, ...maxWidth ? { maxWidth } : {} };
3841
+ }
3842
+ function buildChrome(siteJson, resolveLogo) {
3843
+ const sc = buildSiteConfig(siteJson, resolveLogo);
3844
+ const j = siteJson;
3845
+ const navRoot = Array.isArray(j.navigation) ? j.navigation[0] : j.navigation;
3846
+ const rawNavItems = Array.isArray(navRoot?.items) ? navRoot.items : [];
3847
+ const hidden = rawNavItems.filter((r) => r?.hideTitle === true);
3848
+ const hiddenLabels = new Set(
3849
+ hidden.map((r) => typeof r.title === "string" ? r.title.trim() : "").filter(Boolean)
3850
+ );
3851
+ const items = sc.nav.items.filter((i) => !hiddenLabels.has(i.label));
3852
+ let logo = sc.nav.logo;
3853
+ if (!logo) {
3854
+ for (const r of hidden) {
3855
+ const image = asImage(r.media, resolveLogo);
3856
+ if (image) {
3857
+ logo = image;
3858
+ break;
3859
+ }
3860
+ }
3861
+ }
3862
+ const footRoot = Array.isArray(j.footer) ? j.footer[0] : j.footer;
3863
+ const rawCols = Array.isArray(footRoot?.items) ? footRoot.items : [];
3864
+ const columns = rawCols.flatMap((col) => {
3865
+ const subs = Array.isArray(col?.items) ? col.items : [];
3866
+ const colItems = subs.flatMap((it) => {
3867
+ const href = asHref(it?.link);
3868
+ if (typeof it?.media?.media === "string") {
3869
+ const image = asImage(it.media, resolveLogo);
3870
+ if (!image) return [];
3871
+ const alt = displayTitle(it.title ?? it.text);
3872
+ return [{ image: { ...image, ...alt ? { alt } : {} }, ...href ? { href } : {} }];
3873
+ }
3874
+ if (it?.hideTitle === true) return [];
3875
+ const text = displayTitle(it?.title);
3876
+ if (!text) return [];
3877
+ return [href ? { text, href } : { text }];
3878
+ });
3879
+ return colItems.length ? [{ items: colItems }] : [];
3880
+ });
3881
+ return { nav: { ...logo ? { logo } : {}, items }, footer: { columns } };
3882
+ }
3883
+
2609
3884
  // src/blux/emit/validate-layout.ts
2610
3885
  function tokKey(t) {
2611
3886
  return `${t.cols}${t.ratio !== void 0 ? `r${t.ratio}` : ""}${t.spacing !== void 0 ? `s${t.spacing}` : ""}`;
@@ -2909,12 +4184,7 @@ async function runBluxCommand(action, dir, opts) {
2909
4184
  await mkdir(join(out, "customtypes"), { recursive: true });
2910
4185
  await writeFile(join(out, "ir.json"), JSON.stringify(ir, null, 2));
2911
4186
  await writeFile(join(out, "migration-plan.json"), JSON.stringify(plan, null, 2));
2912
- const rolesCss = emitRolesCss(ir.theme);
2913
- const buttonsCss = emitButtonsCss(ir.theme);
2914
- await writeFile(
2915
- join(out, "theme.css"),
2916
- emitThemeCss(ir.theme) + (rolesCss ? "\n" + rolesCss : "") + (buttonsCss ? "\n" + buttonsCss : "")
2917
- );
4187
+ await writeFile(join(out, "theme.css"), emitThemeCssFile(ir.theme));
2918
4188
  await writeFile(join(out, "review-manifest.json"), JSON.stringify(manifest, null, 2));
2919
4189
  await writeFile(
2920
4190
  join(out, "styles-manifest.json"),
@@ -2950,7 +4220,7 @@ async function runBluxCommand(action, dir, opts) {
2950
4220
  }
2951
4221
  const planPath = dir.endsWith(".json") ? dir : join(dir, "migration-plan.json");
2952
4222
  const plan = JSON.parse(await readFile(planPath, "utf-8"));
2953
- const { pushCustomTypes, runMigration } = await import("./run-migration-LJNNZWIV.js");
4223
+ const { pushCustomTypes, runMigration } = await import("./run-migration-WP57U5XO.js");
2954
4224
  const pushed = await pushCustomTypes(plan.customTypes);
2955
4225
  const r = await runMigration(plan, (line) => process.stderr.write(`${line}
2956
4226
  `));
@@ -2975,6 +4245,77 @@ assets: ${r.assetsUploaded} uploaded, ${r.assetsReused} reused | documents: ${r.
2975
4245
  code: 0
2976
4246
  };
2977
4247
  }
4248
+ if (action === "migrate-catalog") {
4249
+ if (!dir) return { output: "blux migrate-catalog needs the catalog --out dir.", code: 1 };
4250
+ let plan;
4251
+ try {
4252
+ plan = JSON.parse(await readFile(join(dir, "migration-plan.json"), "utf-8"));
4253
+ } catch (err) {
4254
+ return {
4255
+ output: `could not read migration-plan.json in ${dir}: ${err.message}`,
4256
+ code: 1
4257
+ };
4258
+ }
4259
+ const lines = [];
4260
+ const log = (line) => {
4261
+ lines.push(line);
4262
+ process.stderr.write(`${line}
4263
+ `);
4264
+ };
4265
+ const warnings = [];
4266
+ const warn = (line) => {
4267
+ warnings.push(line);
4268
+ log(line);
4269
+ };
4270
+ const { pushCustomTypes, runMigration } = await import("./run-migration-WP57U5XO.js");
4271
+ try {
4272
+ const pushed = plan.customTypes.length ? await pushCustomTypes(plan.customTypes) : [];
4273
+ const assetsPass = await runMigration({ ...plan, documents: [] }, log);
4274
+ const r = rewriteDocUrls(plan.documents, assetsPass.assetUrlByCdn);
4275
+ if (r.unmatched.length) {
4276
+ warn(
4277
+ `WARNING: ${r.unmatched.length} CDN url(s) survived the rewrite: ${r.unmatched.slice(0, 5).join(", ")}`
4278
+ );
4279
+ }
4280
+ let chromeRewritten = 0;
4281
+ const chromeUnmatched = [];
4282
+ const siteConfigPath = join(dir, "site-config.json");
4283
+ try {
4284
+ const parsed = JSON.parse(await readFile(siteConfigPath, "utf-8"));
4285
+ const sc = rewriteValueUrls(parsed, assetsPass.assetUrlByCdn);
4286
+ await writeFile(siteConfigPath, JSON.stringify(sc.value, null, 2) + "\n");
4287
+ chromeRewritten = sc.rewritten;
4288
+ chromeUnmatched.push(...sc.unmatched);
4289
+ if (sc.unmatched.length) {
4290
+ warn(
4291
+ `WARNING: ${sc.unmatched.length} chrome CDN url(s) survived the rewrite: ${sc.unmatched.slice(0, 5).join(", ")}`
4292
+ );
4293
+ }
4294
+ } catch (err) {
4295
+ if (err.code !== "ENOENT") throw err;
4296
+ }
4297
+ const docsPass = await runMigration({ ...plan, documents: r.documents }, log);
4298
+ if (docsPass.missingAssets.length) {
4299
+ warn(`WARNING missing assets: ${docsPass.missingAssets.join(", ")}`);
4300
+ }
4301
+ return {
4302
+ output: [
4303
+ `custom types pushed: ${pushed.join(", ") || "none"}`,
4304
+ ...warnings,
4305
+ `migrate-catalog: assets ${assetsPass.assetsUploaded} uploaded/${assetsPass.assetsReused} reused; urls rewritten ${r.rewritten} (${r.unmatched.length} unmatched); chrome urls rewritten ${chromeRewritten} (${chromeUnmatched.length} unmatched); docs ${docsPass.docsCreated} created/${docsPass.docsUpdated} updated (publish the migration release in the dashboard)`
4306
+ ].join("\n"),
4307
+ // No warning blocks phase 2 — the docs land in an UNPUBLISHED migration
4308
+ // release, so a non-zero exit reaches the operator before anything goes
4309
+ // live.
4310
+ code: r.unmatched.length || chromeUnmatched.length || docsPass.missingAssets.length ? 1 : 0
4311
+ };
4312
+ } catch (err) {
4313
+ return {
4314
+ output: [...lines, `migrate-catalog failed: ${err.message}`].join("\n"),
4315
+ code: 1
4316
+ };
4317
+ }
4318
+ }
2978
4319
  if (action === "validate") {
2979
4320
  if (!dir) return { output: "blux validate needs a Blux export directory.", code: 1 };
2980
4321
  let exportHtml;
@@ -3082,13 +4423,7 @@ assets: ${r.assetsUploaded} uploaded, ${r.assetsReused} reused | documents: ${r.
3082
4423
  join(outDir, "blux-presentation.json"),
3083
4424
  JSON.stringify(presentation, null, 2) + "\n"
3084
4425
  );
3085
- {
3086
- const buttonsCss = emitButtonsCss(ir.theme);
3087
- await writeFile(
3088
- join(outDir, "theme.css"),
3089
- emitThemeCss(ir.theme) + "\n" + emitRolesCss(ir.theme) + (buttonsCss ? "\n" + buttonsCss : "")
3090
- );
3091
- }
4426
+ await writeFile(join(outDir, "theme.css"), emitThemeCssFile(ir.theme));
3092
4427
  const mapConfigs = Object.fromEntries(
3093
4428
  pages.filter((p) => p.mapConfig).map((p) => [p.uid, p.mapConfig])
3094
4429
  );
@@ -3169,12 +4504,267 @@ skipped (no html): ${missing.map((d) => d.where).join(", ")}` : "") + faviconLin
3169
4504
  code: 0
3170
4505
  };
3171
4506
  }
4507
+ if (action === "catalog") {
4508
+ if (!dir) return { output: "blux catalog needs a Blux export directory.", code: 1 };
4509
+ let siteJson;
4510
+ try {
4511
+ siteJson = JSON.parse(await readFile(join(dir, "site.json"), "utf-8"));
4512
+ } catch (err) {
4513
+ return { output: `could not read export in ${dir}: ${err.message}`, code: 1 };
4514
+ }
4515
+ const feeds = siteJson.feeds ?? {};
4516
+ const pageContentByIndex = siteJson?.content?.pages;
4517
+ const classifyDiagnostics = [];
4518
+ const pages = [];
4519
+ const htmls = [];
4520
+ const blockDefaults = blockClassDefaults(siteJson);
4521
+ for (const [pageIndex, p] of sitePages(siteJson).entries()) {
4522
+ const file = p.path ? join(dir, p.path, "index.html") : join(dir, "index.html");
4523
+ let html;
4524
+ try {
4525
+ html = await readFile(file, "utf-8");
4526
+ } catch {
4527
+ continue;
4528
+ }
4529
+ htmls.push(html);
4530
+ const mapConfig = extractMapConfig(html);
4531
+ const blockStyles = blockStylesByIndex(siteJson, pageIndex);
4532
+ const catalogOpts = mapConfig ? {
4533
+ isMapMount: makeIsMapMount(mapConfig),
4534
+ mapConfig,
4535
+ diagnostics: classifyDiagnostics,
4536
+ pageUid: p.uid,
4537
+ styles: blockStyles,
4538
+ defaults: blockDefaults
4539
+ } : {
4540
+ diagnostics: classifyDiagnostics,
4541
+ pageUid: p.uid,
4542
+ styles: blockStyles,
4543
+ defaults: blockDefaults
4544
+ };
4545
+ const pageContent = pageContentByIndex?.[pageIndex];
4546
+ const pageItems = pageContent?.items;
4547
+ const bands = parseGridBands(html);
4548
+ const specs = bands.map(
4549
+ (b) => bandOrCollection(b, pageItems?.[b.index], feeds, catalogOpts)
4550
+ );
4551
+ const bandIdx = new Set(bands.map((b) => b.index));
4552
+ for (const [i, it] of (pageItems ?? []).entries()) {
4553
+ if (!isFeedBand(it) || bandIdx.has(i)) continue;
4554
+ const firstSource = String(it.sources[0]);
4555
+ const feedName = String(feeds[firstSource]?.name ?? firstSource);
4556
+ classifyDiagnostics.push({
4557
+ kind: "feed-band-misalign",
4558
+ where: `${p.uid}:${i}`,
4559
+ message: `feed item at index ${i} ("${feedName}") has no matching band \u2014 collection not emitted`
4560
+ });
4561
+ }
4562
+ const description = typeof pageContent?.description === "string" ? pageContent.description : void 0;
4563
+ const socialMedia = pageContent?.media?.media;
4564
+ const socialImageId = typeof socialMedia === "string" && socialMedia ? socialMedia : void 0;
4565
+ pages.push({
4566
+ uid: p.uid,
4567
+ title: p.title,
4568
+ specs,
4569
+ ...description ? { description } : {},
4570
+ ...socialImageId ? { socialImageId } : {}
4571
+ });
4572
+ }
4573
+ if (!pages.length) {
4574
+ return { output: `could not read any page html in ${dir}`, code: 1 };
4575
+ }
4576
+ const ir = assembleIR({ siteJson, htmls });
4577
+ const plan = buildCatalogPlan(
4578
+ pages,
4579
+ {
4580
+ assets: ir.assets.map((a) => ({
4581
+ id: a.id,
4582
+ url: a.sourceUrl ?? "",
4583
+ alt: a.alt,
4584
+ sourceUrl: a.sourceUrl
4585
+ })),
4586
+ diagnostics: [...classifyDiagnostics, ...ir.diagnostics]
4587
+ },
4588
+ feeds
4589
+ );
4590
+ const outDir = opts.out ?? join(dir, "blux-out");
4591
+ await mkdir(outDir, { recursive: true });
4592
+ const assetById = new Map(ir.assets.map((a) => [a.id, a]));
4593
+ const planUrlById = new Map(plan.assets.map((a) => [a.id, a.url]));
4594
+ const chromeBase = feedAssetBase(htmls, ir.meta.bluxSiteId);
4595
+ const isImageUrl = (u) => /\.(png|jpe?g|gif|webp|svg|avif|ico)$/i.test(u);
4596
+ const chromeBaseCorroborated = plan.assets.some(
4597
+ (a) => a.url.startsWith(chromeBase) && isImageUrl(a.url)
4598
+ );
4599
+ const chromeOnlyAssets = /* @__PURE__ */ new Map();
4600
+ const chrome = buildChrome(siteJson, (uuid) => {
4601
+ const fromPlan = planUrlById.get(uuid);
4602
+ if (fromPlan !== void 0) return fromPlan;
4603
+ const a = assetById.get(uuid);
4604
+ if (!a) return null;
4605
+ if (a.sourceUrl) {
4606
+ if (a.sourceUrl.includes("cloudfront.net") && !chromeOnlyAssets.has(uuid))
4607
+ chromeOnlyAssets.set(uuid, { id: uuid, url: a.sourceUrl, alt: a.alt ?? "" });
4608
+ return a.sourceUrl;
4609
+ }
4610
+ const ext = a.name.match(/\.([a-z0-9]{2,4})$/i)?.[1]?.toLowerCase() ?? extFor(a.mime, a.name);
4611
+ if (!ext) return null;
4612
+ const url = `${chromeBase}${uuid}.${ext}`;
4613
+ if (chromeBaseCorroborated && !chromeOnlyAssets.has(uuid))
4614
+ chromeOnlyAssets.set(uuid, { id: uuid, url, alt: a.alt ?? "" });
4615
+ return url;
4616
+ });
4617
+ plan.assets.push(...chromeOnlyAssets.values());
4618
+ await writeFile(join(outDir, "migration-plan.json"), JSON.stringify(plan, null, 2));
4619
+ await writeFile(
4620
+ join(outDir, "render-fixture.json"),
4621
+ JSON.stringify(resolveFixture(plan), null, 2)
4622
+ );
4623
+ await writeFile(join(outDir, "site-config.json"), JSON.stringify(chrome, null, 2) + "\n");
4624
+ await writeFile(join(outDir, "theme.css"), emitThemeCssFile(ir.theme));
4625
+ const totalBands = pages.reduce((n, p) => n + p.specs.length, 0);
4626
+ return {
4627
+ output: `Cataloged ${pages.length} pages / ${totalBands} bands \u2192 ${join(outDir, "migration-plan.json")} (${plan.documents.length} page documents)`,
4628
+ code: 0
4629
+ };
4630
+ }
4631
+ if (action === "freeze") {
4632
+ if (!dir) return { output: "blux freeze needs a Blux export directory.", code: 1 };
4633
+ const { freezeSite, emitFrozen } = await import("./freeze-Y5OJFOST.js");
4634
+ const { emitFavicon } = await import("./favicon-SRHSJZ62.js");
4635
+ const site = (opts.site ?? basename(dir)).replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
4636
+ const outDir = opts.out ?? join(dir, "frozen-out");
4637
+ let extraSlots;
4638
+ if (opts.extraSlots) {
4639
+ extraSlots = JSON.parse(await readFile(opts.extraSlots, "utf-8"));
4640
+ }
4641
+ const result = await freezeSite({
4642
+ indexHtmlPath: join(dir, "index.html"),
4643
+ site,
4644
+ ...extraSlots === void 0 ? {} : { extraSlots }
4645
+ });
4646
+ const paths = await emitFrozen(outDir, result);
4647
+ let favicon;
4648
+ try {
4649
+ const exportHtml = await readFile(join(dir, "index.html"), "utf-8");
4650
+ const fav = await emitFavicon(exportHtml, dir, outDir);
4651
+ favicon = fav ? `, favicon.png` : "";
4652
+ } catch (err) {
4653
+ favicon = ` (favicon FAILED: ${err.message})`;
4654
+ }
4655
+ const imgs = result.manifest.slots.filter((s) => s.kind === "image").length;
4656
+ const txts = result.manifest.slots.filter((s) => s.kind === "text").length;
4657
+ const map = result.mapConfig ? `, map (${result.mapConfig.layers.length} layers)` : "";
4658
+ return {
4659
+ output: `froze ${site}: ${imgs} image slots, ${txts} text slots, ${result.manifest.fontLinks.length} font links${map} \u2192 ${paths.template}, ${paths.manifest}${favicon}`,
4660
+ code: 0
4661
+ };
4662
+ }
4663
+ if (action === "migrate-frozen") {
4664
+ if (!dir) return { output: "blux migrate-frozen needs the frozen --out dir.", code: 1 };
4665
+ const outDir = opts.out ?? dir;
4666
+ let manifestPath;
4667
+ if (opts.site) {
4668
+ const site = opts.site.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
4669
+ manifestPath = join(outDir, `${site}.slots.json`);
4670
+ } else {
4671
+ const found = (await glob(["*.slots.json"], { cwd: outDir, absolute: true })).sort();
4672
+ if (!found.length)
4673
+ return { output: `no *.slots.json in ${outDir} \u2014 run 'blux freeze' first.`, code: 1 };
4674
+ manifestPath = found[0];
4675
+ }
4676
+ let manifest;
4677
+ try {
4678
+ manifest = JSON.parse(await readFile(manifestPath, "utf-8"));
4679
+ } catch (err) {
4680
+ return { output: `could not read ${manifestPath}: ${err.message}`, code: 1 };
4681
+ }
4682
+ const assetIdByUrl = /* @__PURE__ */ new Map();
4683
+ const assets = [];
4684
+ const assetFor = (url) => {
4685
+ let id = assetIdByUrl.get(url);
4686
+ if (!id) {
4687
+ id = `frozen-asset-${assets.length}`;
4688
+ assetIdByUrl.set(url, id);
4689
+ assets.push({ id, url, alt: "" });
4690
+ }
4691
+ return id;
4692
+ };
4693
+ const isImageUrl = (u) => /\.(jpe?g|png|gif|webp|svg|avif)(\?|$)/i.test(u);
4694
+ const mediaSlots = /* @__PURE__ */ new Map();
4695
+ const slotRows = manifest.slots.map((s) => {
4696
+ if (s.kind === "image" && s.url) {
4697
+ if (!isImageUrl(s.url)) mediaSlots.set(s.key, s.url);
4698
+ return { key: s.key, kind: s.kind, image: assetRef(assetFor(s.url)) };
4699
+ }
4700
+ return { key: s.key, kind: s.kind, text: richText(`<p>${s.text ?? ""}</p>`) };
4701
+ });
4702
+ const data = {
4703
+ title: manifest.title,
4704
+ meta_title: manifest.metaTitle ?? "",
4705
+ meta_description: "",
4706
+ slots: slotRows
4707
+ };
4708
+ if (manifest.metaImageUrl) data.meta_image = assetRef(assetFor(manifest.metaImageUrl));
4709
+ const doc = { type: FROZEN_PAGE_TYPE, uid: manifest.uid, data };
4710
+ const plan = {
4711
+ customTypes: [frozenPageCustomType()],
4712
+ documents: [doc],
4713
+ assets,
4714
+ stylesManifest: [],
4715
+ diagnostics: []
4716
+ };
4717
+ const imageSlots = manifest.slots.filter((s) => s.kind === "image" && s.url).length;
4718
+ const lines = [];
4719
+ const log = (line) => {
4720
+ lines.push(line);
4721
+ process.stderr.write(`${line}
4722
+ `);
4723
+ };
4724
+ const { pushCustomTypes, runMigration } = await import("./run-migration-WP57U5XO.js");
4725
+ try {
4726
+ const pushed = await pushCustomTypes(plan.customTypes);
4727
+ const assetsPass = await runMigration({ ...plan, documents: [] }, log);
4728
+ const unresolved = assets.filter((a) => !assetsPass.assetUrlByCdn.has(a.url));
4729
+ if (unresolved.length) {
4730
+ return {
4731
+ output: `frozen migrate aborted: ${unresolved.length} image url(s) did not resolve to a Prismic asset (first: ${unresolved[0].url}).`,
4732
+ code: 1
4733
+ };
4734
+ }
4735
+ for (const row of slotRows) {
4736
+ const cdn = mediaSlots.get(row.key);
4737
+ if (cdn) {
4738
+ delete row.image;
4739
+ row.media_url = assetsPass.assetUrlByCdn.get(cdn) ?? "";
4740
+ }
4741
+ }
4742
+ const docsPass = await runMigration(plan, log);
4743
+ if (docsPass.missingAssets.length) {
4744
+ return {
4745
+ output: `frozen migrate aborted: ${docsPass.missingAssets.length} asset ref(s) went unresolved in the doc (first: ${docsPass.missingAssets[0]}).`,
4746
+ code: 1
4747
+ };
4748
+ }
4749
+ const docWord = docsPass.docsUpdated ? "updated" : "created";
4750
+ return {
4751
+ output: `frozen_page pushed: ${pushed.join(", ") || "none"}; assets ${assetsPass.assetsUploaded} uploaded/${assetsPass.assetsReused} reused; ${imageSlots} image slot(s) rewritten to Prismic; doc ${manifest.uid} ${docWord} \u2192 ${process.env.PRISMIC_REPOSITORY_NAME} (publish the migration release in the dashboard)`,
4752
+ code: 0
4753
+ };
4754
+ } catch (err) {
4755
+ return {
4756
+ output: `frozen migrate failed: ${err.message}
4757
+ ${lines.join("\n")}`,
4758
+ code: 1
4759
+ };
4760
+ }
4761
+ }
3172
4762
  return {
3173
- output: `unknown blux action '${action}'. Use: emit, migrate, validate, grid, convert.`,
4763
+ output: `unknown blux action '${action}'. Use: emit, migrate, migrate-catalog, migrate-frozen, validate, grid, convert, catalog, freeze.`,
3174
4764
  code: 1
3175
4765
  };
3176
4766
  }
3177
4767
  export {
3178
4768
  runBluxCommand
3179
4769
  };
3180
- //# sourceMappingURL=blux-C24OVDDW.js.map
4770
+ //# sourceMappingURL=blux-RIJBRVCF.js.map