@reddoorla/maintenance 0.71.0 → 0.74.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 (126) hide show
  1. package/README.md +15 -14
  2. package/dist/airtable-6L3JD7FY.js +9 -0
  3. package/dist/{announce-3JE5FKW7.js → announce-V6EYPPUR.js} +6 -6
  4. package/dist/{blux-FKZYMKLF.js → blux-OL5HKP26.js} +950 -69
  5. package/dist/blux-OL5HKP26.js.map +1 -0
  6. package/dist/{bump-deps-K3JMLMBB.js → bump-deps-OQR3IXLN.js} +2 -2
  7. package/dist/chunk-2OP4JKIQ.js +19 -0
  8. package/dist/chunk-2OP4JKIQ.js.map +1 -0
  9. package/dist/{chunk-5QC5KJHR.js → chunk-3PU2QWOJ.js} +2 -2
  10. package/dist/{chunk-Y7LZHPDF.js → chunk-4BHNP6Y2.js} +2 -2
  11. package/dist/{chunk-KOAGMTRI.js → chunk-6OYICQSJ.js} +47 -3
  12. package/dist/chunk-6OYICQSJ.js.map +1 -0
  13. package/dist/{chunk-WN4NEHE7.js → chunk-D4FVAIEJ.js} +5 -14
  14. package/dist/chunk-D4FVAIEJ.js.map +1 -0
  15. package/dist/{chunk-YWRQEDB3.js → chunk-GXVX4HUH.js} +46 -7
  16. package/dist/chunk-GXVX4HUH.js.map +1 -0
  17. package/dist/{chunk-J2SHWT2O.js → chunk-GYNRZCM6.js} +7 -2
  18. package/dist/{chunk-J2SHWT2O.js.map → chunk-GYNRZCM6.js.map} +1 -1
  19. package/dist/{chunk-VR246V3G.js → chunk-HBFMTM2E.js} +6 -6
  20. package/dist/chunk-HBFMTM2E.js.map +1 -0
  21. package/dist/{chunk-PSJQE6T6.js → chunk-I55YWV6T.js} +4 -4
  22. package/dist/{chunk-52TDNN7K.js → chunk-IDBMYDVH.js} +4 -4
  23. package/dist/{chunk-AYM2EFSK.js → chunk-JCITWZQR.js} +14 -14
  24. package/dist/chunk-JCITWZQR.js.map +1 -0
  25. package/dist/{chunk-CMQMNFFJ.js → chunk-JR67FZVC.js} +7 -3
  26. package/dist/chunk-JR67FZVC.js.map +1 -0
  27. package/dist/{chunk-MPRLMGJU.js → chunk-KKL3RYFQ.js} +2 -2
  28. package/dist/{chunk-2PWQ6MPG.js → chunk-KLVMJYZZ.js} +2 -2
  29. package/dist/{chunk-LPMQXS2N.js → chunk-LJPCMXR7.js} +1 -2
  30. package/dist/chunk-LJPCMXR7.js.map +1 -0
  31. package/dist/{chunk-6J25IIAI.js → chunk-NUIBQPB3.js} +187 -72
  32. package/dist/chunk-NUIBQPB3.js.map +1 -0
  33. package/dist/{chunk-M7HOQ4W7.js → chunk-PERP7LSS.js} +277 -21
  34. package/dist/chunk-PERP7LSS.js.map +1 -0
  35. package/dist/chunk-RHPDQW6D.js +29 -0
  36. package/dist/chunk-RHPDQW6D.js.map +1 -0
  37. package/dist/{chunk-F6OE6MGQ.js → chunk-SK44IFZN.js} +2 -2
  38. package/dist/cli/bin.js +31 -19
  39. package/dist/cli/bin.js.map +1 -1
  40. package/dist/cli/commands/audit.js +8 -8
  41. package/dist/configs/playwright-a11y.d.ts +1 -1
  42. package/dist/configs/playwright-a11y.js +1 -3
  43. package/dist/{convert-to-pnpm-6F4RAFEJ.js → convert-to-pnpm-OVMASATZ.js} +2 -2
  44. package/dist/{digest-GZCZYJKC.js → digest-LEMZS5ZK.js} +102 -10
  45. package/dist/digest-LEMZS5ZK.js.map +1 -0
  46. package/dist/{ensure-site-H5VMBPLF.js → ensure-site-3SOAWTZF.js} +2 -2
  47. package/dist/forms/index.d.ts +3 -2
  48. package/dist/forms/index.js +16 -9
  49. package/dist/forms/index.js.map +1 -1
  50. package/dist/{github-signals-SQECQNST.js → github-signals-TF3VSNIN.js} +5 -5
  51. package/dist/{health-endpoint-QXWM6GW2.js → health-endpoint-JZGYCMTV.js} +2 -2
  52. package/dist/index.d.ts +108 -22
  53. package/dist/index.js +127 -35
  54. package/dist/index.js.map +1 -1
  55. package/dist/{init-KQOIPLVT.js → init-7PTP4O2K.js} +5 -5
  56. package/dist/{launch-RX4VI7F2.js → launch-W33FWWOZ.js} +9 -9
  57. package/dist/{lighthouse-airtable-TTXULEGU.js → lighthouse-airtable-ZXNMDEPE.js} +3 -3
  58. package/dist/{onboard-TFBL62J3.js → onboard-PX36YDZA.js} +2 -2
  59. package/dist/{orchestrate-DYA4XAQJ.js → orchestrate-NYRYEKQM.js} +5 -5
  60. package/dist/{preflight-S3GYME3Q.js → preflight-JCQFKB6M.js} +7 -7
  61. package/dist/{renovate-dispatch-MZPAWVBL.js → renovate-dispatch-6II2JVOX.js} +29 -27
  62. package/dist/renovate-dispatch-6II2JVOX.js.map +1 -0
  63. package/dist/{report-BLZ67QAQ.js → report-POHQEV6F.js} +24 -8
  64. package/dist/report-POHQEV6F.js.map +1 -0
  65. package/dist/screenouts-7P5GOZMV.js +57 -0
  66. package/dist/screenouts-7P5GOZMV.js.map +1 -0
  67. package/dist/{self-updating-GSH427XP.js → self-updating-2YSVNMPP.js} +2 -2
  68. package/dist/{selftest-KPTL6KC4.js → selftest-OO2WDSDM.js} +12 -9
  69. package/dist/selftest-OO2WDSDM.js.map +1 -0
  70. package/dist/{smoke-suite-GP3W57JP.js → smoke-suite-CSP23NZM.js} +2 -2
  71. package/dist/spam-classifier-VG4ICIWS.js +217 -0
  72. package/dist/spam-classifier-VG4ICIWS.js.map +1 -0
  73. package/dist/submissions-BNRGY3JQ.js +122 -0
  74. package/dist/submissions-BNRGY3JQ.js.map +1 -0
  75. package/dist/submissions-MDKWJYMM.js +281 -0
  76. package/dist/submissions-MDKWJYMM.js.map +1 -0
  77. package/dist/{svelte-codemods-GYZZX742.js → svelte-codemods-V54KOBWU.js} +2 -2
  78. package/dist/{sync-configs-CQNOTJ2B.js → sync-configs-UBCJRASZ.js} +2 -2
  79. package/dist/{upgrade-RTAJZD56.js → upgrade-WR6ICPAG.js} +2 -2
  80. package/dist/{websites-SVI4KYSH.js → websites-U6642XSE.js} +10 -2
  81. package/dist/{write-audits-to-airtable-3XFNWMUE.js → write-audits-to-airtable-22T6D733.js} +4 -4
  82. package/package.json +1 -1
  83. package/dist/airtable-2PJ4NSAV.js +0 -9
  84. package/dist/blux-FKZYMKLF.js.map +0 -1
  85. package/dist/chunk-6J25IIAI.js.map +0 -1
  86. package/dist/chunk-AYM2EFSK.js.map +0 -1
  87. package/dist/chunk-CMQMNFFJ.js.map +0 -1
  88. package/dist/chunk-KOAGMTRI.js.map +0 -1
  89. package/dist/chunk-LPMQXS2N.js.map +0 -1
  90. package/dist/chunk-M7HOQ4W7.js.map +0 -1
  91. package/dist/chunk-QTHKOIFJ.js +0 -13
  92. package/dist/chunk-QTHKOIFJ.js.map +0 -1
  93. package/dist/chunk-VR246V3G.js.map +0 -1
  94. package/dist/chunk-WN4NEHE7.js.map +0 -1
  95. package/dist/chunk-YWRQEDB3.js.map +0 -1
  96. package/dist/digest-GZCZYJKC.js.map +0 -1
  97. package/dist/renovate-dispatch-MZPAWVBL.js.map +0 -1
  98. package/dist/report-BLZ67QAQ.js.map +0 -1
  99. package/dist/selftest-KPTL6KC4.js.map +0 -1
  100. /package/dist/{airtable-2PJ4NSAV.js.map → airtable-6L3JD7FY.js.map} +0 -0
  101. /package/dist/{announce-3JE5FKW7.js.map → announce-V6EYPPUR.js.map} +0 -0
  102. /package/dist/{bump-deps-K3JMLMBB.js.map → bump-deps-OQR3IXLN.js.map} +0 -0
  103. /package/dist/{chunk-5QC5KJHR.js.map → chunk-3PU2QWOJ.js.map} +0 -0
  104. /package/dist/{chunk-Y7LZHPDF.js.map → chunk-4BHNP6Y2.js.map} +0 -0
  105. /package/dist/{chunk-PSJQE6T6.js.map → chunk-I55YWV6T.js.map} +0 -0
  106. /package/dist/{chunk-52TDNN7K.js.map → chunk-IDBMYDVH.js.map} +0 -0
  107. /package/dist/{chunk-MPRLMGJU.js.map → chunk-KKL3RYFQ.js.map} +0 -0
  108. /package/dist/{chunk-2PWQ6MPG.js.map → chunk-KLVMJYZZ.js.map} +0 -0
  109. /package/dist/{chunk-F6OE6MGQ.js.map → chunk-SK44IFZN.js.map} +0 -0
  110. /package/dist/{convert-to-pnpm-6F4RAFEJ.js.map → convert-to-pnpm-OVMASATZ.js.map} +0 -0
  111. /package/dist/{ensure-site-H5VMBPLF.js.map → ensure-site-3SOAWTZF.js.map} +0 -0
  112. /package/dist/{github-signals-SQECQNST.js.map → github-signals-TF3VSNIN.js.map} +0 -0
  113. /package/dist/{health-endpoint-QXWM6GW2.js.map → health-endpoint-JZGYCMTV.js.map} +0 -0
  114. /package/dist/{init-KQOIPLVT.js.map → init-7PTP4O2K.js.map} +0 -0
  115. /package/dist/{launch-RX4VI7F2.js.map → launch-W33FWWOZ.js.map} +0 -0
  116. /package/dist/{lighthouse-airtable-TTXULEGU.js.map → lighthouse-airtable-ZXNMDEPE.js.map} +0 -0
  117. /package/dist/{onboard-TFBL62J3.js.map → onboard-PX36YDZA.js.map} +0 -0
  118. /package/dist/{orchestrate-DYA4XAQJ.js.map → orchestrate-NYRYEKQM.js.map} +0 -0
  119. /package/dist/{preflight-S3GYME3Q.js.map → preflight-JCQFKB6M.js.map} +0 -0
  120. /package/dist/{self-updating-GSH427XP.js.map → self-updating-2YSVNMPP.js.map} +0 -0
  121. /package/dist/{smoke-suite-GP3W57JP.js.map → smoke-suite-CSP23NZM.js.map} +0 -0
  122. /package/dist/{svelte-codemods-GYZZX742.js.map → svelte-codemods-V54KOBWU.js.map} +0 -0
  123. /package/dist/{sync-configs-CQNOTJ2B.js.map → sync-configs-UBCJRASZ.js.map} +0 -0
  124. /package/dist/{upgrade-RTAJZD56.js.map → upgrade-WR6ICPAG.js.map} +0 -0
  125. /package/dist/{websites-SVI4KYSH.js.map → websites-U6642XSE.js.map} +0 -0
  126. /package/dist/{write-audits-to-airtable-3XFNWMUE.js.map → write-audits-to-airtable-22T6D733.js.map} +0 -0
@@ -1,4 +1,5 @@
1
1
  import {
2
+ CDN_HOSTS,
2
3
  collectAssetUrls
3
4
  } from "./chunk-SL24DCGC.js";
4
5
 
@@ -188,12 +189,26 @@ function slugify(s) {
188
189
  }
189
190
  function normalizePages(raw) {
190
191
  const diagnostics = [];
191
- const pages = raw.pages.map((p) => {
192
- const uid = slugify(String(p.title ?? ""));
192
+ const seen = /* @__PURE__ */ new Set();
193
+ const pages = raw.pages.map((p, i) => {
194
+ const urlSlug = String(p.url ?? "").trim();
195
+ const path = i === 0 ? "" : urlSlug ? slugify(urlSlug) : slugify(String(p.title ?? ""));
196
+ let uid = i === 0 ? "home" : path;
197
+ if (seen.has(uid)) {
198
+ const base2 = uid;
199
+ for (let n = 2; seen.has(uid); n++) uid = `${base2}-${n}`;
200
+ diagnostics.push({
201
+ kind: "duplicate-page-uid",
202
+ where: base2,
203
+ message: `page ${i} ("${String(p.title ?? "")}") slugs to "${base2}" already used \u2014 renamed to "${uid}"`
204
+ });
205
+ }
206
+ seen.add(uid);
193
207
  return {
194
208
  uid,
195
209
  title: String(p.title ?? ""),
196
210
  description: String(p.description ?? ""),
211
+ path,
197
212
  sections: (p.items ?? []).map((b) => sectionFromBlock(b, uid, diagnostics))
198
213
  };
199
214
  });
@@ -212,6 +227,7 @@ function normalizeTheme(raw) {
212
227
  const m = entry[innerKey] ?? {};
213
228
  const transform = cleanCssValue(m["text-transform"]);
214
229
  const tracking = cleanCssValue(m["letter-spacing"]);
230
+ const margin = cleanCssValue(m["margin"]);
215
231
  const mobileSize = cleanCssValue(m["__media_mobile_font-size"]);
216
232
  const mobileLineHeight = cleanCssValue(m["__media_mobile_line-height"]);
217
233
  textStyles.push({
@@ -224,10 +240,38 @@ function normalizeTheme(raw) {
224
240
  lineHeight: cleanCssValue(m["line-height"]) || "1.5",
225
241
  ...transform && transform !== "none" ? { transform } : {},
226
242
  ...tracking ? { letterSpacing: tracking } : {},
243
+ ...margin && margin !== "0" ? { margin } : {},
227
244
  ...mobileSize ? { mobileSize } : {},
228
245
  ...mobileLineHeight ? { mobileLineHeight } : {}
229
246
  });
230
247
  }
248
+ const cleanCssMap = (v) => {
249
+ const out = {};
250
+ for (const [k, val] of Object.entries(v ?? {})) {
251
+ if (k === "font-ident") continue;
252
+ const s = cleanCssValue(typeof val === "number" ? String(val) : val);
253
+ if (s) out[k] = s;
254
+ }
255
+ return out;
256
+ };
257
+ const buttonStyles = [];
258
+ for (const v of Object.values(raw.styles.buttons ?? {})) {
259
+ const entry = v ?? {};
260
+ const innerKey = Object.keys(entry).find((k) => /^\.buttons\d+$/.test(k));
261
+ if (!innerKey) continue;
262
+ const css = cleanCssMap(entry[innerKey]);
263
+ if (Object.keys(css).length === 0) continue;
264
+ const hover = cleanCssMap(entry[`${innerKey}:hover`]);
265
+ const active = cleanCssMap(entry[`${innerKey}:active`]);
266
+ buttonStyles.push({
267
+ role: innerKey.slice(1),
268
+ // ".buttons2" -> "buttons2"
269
+ label: str(entry._label),
270
+ css,
271
+ ...Object.keys(hover).length ? { hover } : {},
272
+ ...Object.keys(active).length ? { active } : {}
273
+ });
274
+ }
231
275
  const fonts = raw.settings.fonts ?? {};
232
276
  const roleFont = (r) => textStyles.find((t) => t.role === r)?.fontFamily ?? "";
233
277
  return {
@@ -240,7 +284,8 @@ function normalizeTheme(raw) {
240
284
  parseGoogleFonts(str(fonts.google)),
241
285
  typekitFontLoads(str(fonts.string))
242
286
  ),
243
- textStyles
287
+ textStyles,
288
+ buttonStyles
244
289
  };
245
290
  }
246
291
 
@@ -330,8 +375,17 @@ function assembleIR(input) {
330
375
  alt: String(m.name ?? "")
331
376
  };
332
377
  });
378
+ const faviconId = raw.settings.favicon?.media;
379
+ const favicon = faviconId ? { assetId: faviconId, sourceUrl: urlMap.get(faviconId) ?? null } : void 0;
380
+ if (favicon && !favicon.sourceUrl) {
381
+ diagnostics.push({
382
+ kind: "unresolved-asset",
383
+ where: favicon.assetId,
384
+ message: `no CDN url for favicon ${favicon.assetId}`
385
+ });
386
+ }
333
387
  return {
334
- meta: raw.meta,
388
+ meta: { ...raw.meta, ...favicon ? { favicon } : {} },
335
389
  theme,
336
390
  pages,
337
391
  collections,
@@ -644,6 +698,7 @@ function emitThemeCss(theme) {
644
698
  if (t.fontFamily) lines.push(` --text-${t.role}--font-family: ${t.fontFamily};`);
645
699
  if (t.transform) lines.push(` --text-${t.role}--text-transform: ${t.transform};`);
646
700
  if (t.letterSpacing) lines.push(` --text-${t.role}--letter-spacing: ${t.letterSpacing};`);
701
+ if (t.margin) lines.push(` --text-${t.role}--margin: ${t.margin};`);
647
702
  if (t.mobileSize) lines.push(` --text-${t.role}--mobile-font-size: ${t.mobileSize};`);
648
703
  if (t.mobileLineHeight)
649
704
  lines.push(` --text-${t.role}--mobile-line-height: ${t.mobileLineHeight};`);
@@ -667,12 +722,40 @@ function emitRolesCss(theme) {
667
722
  ` line-height: var(--text-${r}--line-height);`,
668
723
  ` letter-spacing: var(--text-${r}--letter-spacing, normal);`,
669
724
  ` text-transform: var(--text-${r}--text-transform, none);`,
670
- ` margin: 0;`,
725
+ ` margin: var(--text-${r}--margin, 0);`,
671
726
  `}`
672
727
  );
673
728
  }
674
729
  return lines.join("\n") + "\n";
675
730
  }
731
+ function emitButtonsCss(theme) {
732
+ if (!theme.buttonStyles.length) return "";
733
+ const lines = [
734
+ "/* Button skins \u2014 one .buttonsN per Blux button style; raw anchors carry",
735
+ " `ib middle buttonsN` verbatim. Generated by blux emit. */",
736
+ ".ib {",
737
+ " display: inline-block;",
738
+ "}",
739
+ "/* Text links (`ib middle links`) are underlined by the Blux platform CSS;",
740
+ " an inline-block box does not inherit an ancestor's text-decoration, so",
741
+ " the affordance must be declared on the anchor itself. */",
742
+ ".links {",
743
+ " text-decoration: underline;",
744
+ "}"
745
+ ];
746
+ const rule = (selector, css) => {
747
+ lines.push(`${selector} {`);
748
+ for (const [k, v] of Object.entries(css)) lines.push(` ${k}: ${v};`);
749
+ lines.push("}");
750
+ };
751
+ for (const b of theme.buttonStyles) {
752
+ if (b.label) lines.push(`/* ${b.role} \u2014 ${b.label} */`);
753
+ rule(`.${b.role}`, b.css);
754
+ if (b.hover) rule(`.${b.role}:hover`, b.hover);
755
+ if (b.active) rule(`.${b.role}:active`, b.active);
756
+ }
757
+ return lines.join("\n") + "\n";
758
+ }
676
759
 
677
760
  // src/blux/emit/review.ts
678
761
  function buildReviewManifest(ir, opts) {
@@ -769,6 +852,28 @@ function cssProp(style, prop) {
769
852
  const m = new RegExp(`(?:^|;|\\s)${prop}\\s*:\\s*([^;]+)`, "i").exec(style);
770
853
  return m?.[1]?.trim();
771
854
  }
855
+ var MARGIN_UTIL_RE = /\bmargin-(\d+)(r|l|t|b)\b/g;
856
+ var MARGIN_SIDES = { r: "right", l: "left", t: "top", b: "bottom" };
857
+ function utilityStylesFromClass(className) {
858
+ const out = {};
859
+ for (const m of className.matchAll(MARGIN_UTIL_RE)) {
860
+ const [, n, side] = m;
861
+ if (n && side) out[`margin-${MARGIN_SIDES[side]}`] = `${n}%`;
862
+ }
863
+ return out;
864
+ }
865
+ function textLeafStyle(el) {
866
+ const out = utilityStylesFromClass(el.classNames ?? "");
867
+ for (const decl of (el.getAttribute("style") ?? "").split(";")) {
868
+ const colon = decl.indexOf(":");
869
+ if (colon < 0) continue;
870
+ const prop = decl.slice(0, colon).trim().toLowerCase();
871
+ const value = decl.slice(colon + 1).trim();
872
+ if (!value) continue;
873
+ if (prop === "color" || prop === "padding" || prop.startsWith("margin")) out[prop] = value;
874
+ }
875
+ return Object.keys(out).length ? out : null;
876
+ }
772
877
  var CENTERED = /* @__PURE__ */ new Set(["center", "center center", "50% 50%", "50%"]);
773
878
  function readBgSizing(el) {
774
879
  const style = el.getAttribute("style") ?? "";
@@ -860,17 +965,93 @@ function mediaFromElement(el) {
860
965
  var DEFAULT_TOKEN = { cols: 1, raw: "grid-1" };
861
966
  var isElement = (n) => n.tagName !== void 0 && n.tagName !== null;
862
967
  var hasClass = (el, c) => el.classNames.split(/\s+/).includes(c);
968
+ var isFeedTemplate = (el) => {
969
+ const id = el.getAttribute("id") ?? "";
970
+ const style = el.getAttribute("style") ?? "";
971
+ const hidden = /display\s*:\s*none/i.test(style);
972
+ return hidden && (/-template$/.test(id) || /\{\{[a-z_]+\}\}/i.test(el.innerHTML));
973
+ };
863
974
  var isMediaHolder = (el) => hasClass(el, "block-media-holder") || hasClass(el, "camediaload") && !!el.getAttribute("data-media") || el.tagName === "VIDEO";
864
975
  var isLeafElement = (el) => hasClass(el, "block-title") || hasClass(el, "block-body") || hasClass(el, "block-subtitle") || isMediaHolder(el);
865
976
  var isLeafAnchor = (el) => el.tagName === "A" && collectStructuralChildren(el).length === 0;
866
977
  var isStructural = (el) => isLeafElement(el) || el.hasAttribute("data-exec") || // Blux custom-code embed (e.g. map mount)
867
978
  hasClass(el, "cagrid") || isLeafAnchor(el) || parseGridToken(el.classNames) !== null;
868
- function collectStructuralChildren(el) {
979
+ var isCellBoundary = (el) => hasClass(el, "block-grid-container") || hasClass(el, "block-subcontent");
980
+ function inlineBg(el) {
981
+ const c = cssProp(el.getAttribute("style") ?? "", "background-color")?.trim();
982
+ if (!c) return void 0;
983
+ const low = c.toLowerCase().replace(/\s+/g, "");
984
+ if (low === "transparent" || low === "rgba(0,0,0,0)") return void 0;
985
+ return c;
986
+ }
987
+ function inlinePadding(el) {
988
+ const c = cssProp(el.getAttribute("style") ?? "", "padding")?.trim();
989
+ if (!c) return void 0;
990
+ if (/^(0(px|%|em|rem)?\s*)+$/i.test(c)) return void 0;
991
+ return c;
992
+ }
993
+ function inlineMinHeight(el) {
994
+ const c = cssProp(el.getAttribute("style") ?? "", "min-height")?.trim();
995
+ if (!c) return void 0;
996
+ if (/^(0(px|%|em|rem|vh|vw)?|auto)$/i.test(c)) return void 0;
997
+ return c;
998
+ }
999
+ function layerBackground(el) {
1000
+ for (const child of el.childNodes) {
1001
+ if (!isElement(child) || !hasClass(child, "block-background-layer")) continue;
1002
+ const s = child.getAttribute("style") ?? "";
1003
+ const bg = (cssProp(s, "background") ?? cssProp(s, "background-color"))?.trim();
1004
+ if (bg && !/^(none|transparent)$/i.test(bg)) return bg;
1005
+ }
1006
+ return void 0;
1007
+ }
1008
+ function collectStructuralChildren(el, inherited = {}) {
869
1009
  const out = [];
1010
+ const layerBg = inherited.nested === true ? layerBackground(el) : void 0;
1011
+ const base2 = layerBg !== void 0 ? { ...inherited, layerBackground: layerBg } : inherited;
870
1012
  for (const child of el.childNodes) {
871
1013
  if (!isElement(child)) continue;
872
- if (isStructural(child)) out.push(child);
873
- else out.push(...collectStructuralChildren(child));
1014
+ if (hasClass(child, "block-background-layer")) continue;
1015
+ if (isFeedTemplate(child)) continue;
1016
+ const nested = base2.nested === true || isCellBoundary(child);
1017
+ const background = inlineBg(child) ?? base2.background;
1018
+ const padding = (nested ? inlinePadding(child) : void 0) ?? base2.padding;
1019
+ const minHeight = (nested ? inlineMinHeight(child) : void 0) ?? base2.minHeight;
1020
+ const valign = nested && hasClass(child, "valignmiddle") || base2.valign === true;
1021
+ const card = {
1022
+ ...background !== void 0 ? { background } : {},
1023
+ ...padding !== void 0 ? { padding } : {},
1024
+ ...minHeight !== void 0 ? { minHeight } : {},
1025
+ ...base2.layerBackground !== void 0 ? { layerBackground: base2.layerBackground } : {},
1026
+ ...valign ? { valign } : {},
1027
+ ...base2.fill === true ? { fill: true } : {},
1028
+ ...nested ? { nested } : {}
1029
+ };
1030
+ const group = !isStructural(child) && (hasClass(child, "block-subcontent") && parseGridToken(child.classNames) === null || nested && (inlinePadding(child) !== void 0 || inlineMinHeight(child) !== void 0 || layerBackground(child) !== void 0)) && collectStructuralChildren(child).length >= 2;
1031
+ if (isStructural(child)) {
1032
+ const inheritedCard = {
1033
+ ...base2.background !== void 0 ? { background: base2.background } : {},
1034
+ ...base2.padding !== void 0 ? { padding: base2.padding } : {},
1035
+ ...base2.minHeight !== void 0 ? { minHeight: base2.minHeight } : {},
1036
+ ...base2.layerBackground !== void 0 ? { layerBackground: base2.layerBackground } : {},
1037
+ ...base2.valign === true ? { valign: true } : {},
1038
+ ...base2.fill === true ? { fill: true } : {},
1039
+ ...nested ? { nested } : {}
1040
+ };
1041
+ out.push({
1042
+ el: child,
1043
+ ...Object.keys(inheritedCard).length > 0 ? { card: inheritedCard } : {}
1044
+ });
1045
+ } else if (group) {
1046
+ const ownLayer = !isCellBoundary(child) ? layerBackground(child) : void 0;
1047
+ const groupCard = ownLayer !== void 0 ? { ...card, layerBackground: ownLayer } : card;
1048
+ out.push({
1049
+ el: child,
1050
+ ...Object.keys(groupCard).length > 0 ? { card: groupCard } : {}
1051
+ });
1052
+ } else {
1053
+ out.push(...collectStructuralChildren(child, card));
1054
+ }
874
1055
  }
875
1056
  return out;
876
1057
  }
@@ -888,23 +1069,37 @@ function isDisabledWithin(caption, holder) {
888
1069
  }
889
1070
  return false;
890
1071
  }
891
- function parseNode(el) {
1072
+ function parseNode(el, nested = false) {
892
1073
  if (hasClass(el, "block-title") && /^H[1-6]$/.test(el.tagName ?? "")) {
893
1074
  const role = textRoleFromClass(el.classNames);
1075
+ const style = textLeafStyle(el);
894
1076
  return {
895
1077
  kind: "heading",
896
1078
  ...role ? { role } : {},
1079
+ ...style ? { style } : {},
897
1080
  level: headingLevel(el),
898
1081
  html: el.innerHTML
899
1082
  };
900
1083
  }
901
1084
  if (hasClass(el, "block-body")) {
902
1085
  const role = textRoleFromClass(el.classNames);
903
- return { kind: "body", ...role ? { role } : {}, html: el.innerHTML };
1086
+ const style = textLeafStyle(el);
1087
+ return {
1088
+ kind: "body",
1089
+ ...role ? { role } : {},
1090
+ ...style ? { style } : {},
1091
+ html: el.innerHTML
1092
+ };
904
1093
  }
905
1094
  if (hasClass(el, "block-subtitle")) {
906
1095
  const role = textRoleFromClass(el.classNames);
907
- return { kind: "subtitle", ...role ? { role } : {}, text: blockPlainText(el.innerHTML) };
1096
+ const style = textLeafStyle(el);
1097
+ return {
1098
+ kind: "subtitle",
1099
+ ...role ? { role } : {},
1100
+ ...style ? { style } : {},
1101
+ text: blockPlainText(el.innerHTML)
1102
+ };
908
1103
  }
909
1104
  if (isMediaHolder(el)) {
910
1105
  const media = mediaFromElement(el);
@@ -922,14 +1117,52 @@ function parseNode(el) {
922
1117
  if (el.tagName === "A") {
923
1118
  return { kind: "raw", html: el.outerHTML };
924
1119
  }
925
- return parseContainer(el);
926
- }
927
- function parseContainer(el) {
928
- const kids = collectStructuralChildren(el);
929
- const parsed = kids.map((k) => ({ token: parseGridToken(k.classNames), node: parseNode(k) })).filter((p) => !(p.node.kind === "raw" && p.node.html.trim() === ""));
1120
+ return parseContainer(el, nested);
1121
+ }
1122
+ var nodePainted = (style) => "background" in style || "background-color" in style || "min-height" in style;
1123
+ function withCardStyle(node, card) {
1124
+ if (!card || card.background === void 0 && card.padding === void 0 && card.minHeight === void 0 && card.layerBackground === void 0 && !card.valign && !card.fill)
1125
+ return node;
1126
+ if (node.kind === "row" || node.kind === "stack") {
1127
+ const style2 = { ...node.style ?? {} };
1128
+ if (card.background !== void 0) style2["background-color"] = card.background;
1129
+ if (card.layerBackground !== void 0) style2["background"] = card.layerBackground;
1130
+ if (card.padding !== void 0) style2.padding = card.padding;
1131
+ if (card.minHeight !== void 0) style2["min-height"] = card.minHeight;
1132
+ if (card.valign) style2["_valign"] = "middle";
1133
+ if (card.fill && nodePainted(style2)) style2["_fill"] = "column";
1134
+ if (Object.keys(style2).length === 0) return node;
1135
+ return { ...node, style: style2 };
1136
+ }
1137
+ if (node.kind === "media" && card.padding === void 0 && !card.valign) {
1138
+ if (card.minHeight !== void 0 && node.media.minHeight === void 0)
1139
+ return { ...node, media: { ...node.media, minHeight: card.minHeight } };
1140
+ return node;
1141
+ }
1142
+ if (card.padding === void 0 && card.minHeight === void 0 && card.layerBackground === void 0 && !card.valign)
1143
+ return node;
1144
+ const style = {};
1145
+ if (card.padding !== void 0) style.padding = card.padding;
1146
+ if (card.background !== void 0) style["background-color"] = card.background;
1147
+ if (card.layerBackground !== void 0) style["background"] = card.layerBackground;
1148
+ if (card.minHeight !== void 0) style["min-height"] = card.minHeight;
1149
+ if (card.valign) style["_valign"] = "middle";
1150
+ if (card.fill && nodePainted(style)) style["_fill"] = "column";
1151
+ return { kind: "stack", children: [node], style };
1152
+ }
1153
+ function parseContainer(el, nested = false) {
1154
+ const kids = collectStructuralChildren(
1155
+ el,
1156
+ isCellBoundary(el) || hasClass(el, "cagrid") ? { nested: true, ...hasClass(el, "cagridFlexHeight") ? { fill: true } : {} } : nested ? { nested: true } : {}
1157
+ );
1158
+ const parsed = kids.map((k) => ({
1159
+ token: parseGridToken(k.el.classNames),
1160
+ node: withCardStyle(parseNode(k.el, k.card?.nested === true), k.card)
1161
+ })).filter((p) => !(p.node.kind === "raw" && p.node.html.trim() === ""));
930
1162
  const isGrid = hasClass(el, "cagrid");
931
1163
  const tokenCount = parsed.filter((p) => p.token).length;
932
- if ((isGrid || tokenCount >= 2) && parsed.length > 0) {
1164
+ const hasWidthToken = parsed.some((p) => p.token && p.token.cols !== 1 && p.token.cols !== "any");
1165
+ if ((isGrid || tokenCount >= 2 || hasWidthToken) && parsed.length > 0) {
933
1166
  const cells = parsed.map((p) => ({ token: p.token ?? DEFAULT_TOKEN, node: p.node }));
934
1167
  if (hasClass(el, "caslider")) {
935
1168
  const cols = Number(el.getAttribute("data-columns"));
@@ -940,10 +1173,15 @@ function parseContainer(el) {
940
1173
  }
941
1174
  const [only] = parsed;
942
1175
  if (parsed.length === 1 && only) return only.node;
943
- if (parsed.length === 0) return { kind: "raw", html: el.innerHTML };
1176
+ if (parsed.length === 0) return { kind: "raw", html: rawInnerHtml(el) };
944
1177
  return { kind: "stack", children: parsed.map((p) => p.node) };
945
1178
  }
1179
+ function rawInnerHtml(el) {
1180
+ const html = el.innerHTML;
1181
+ return /\{\{[a-z_]+\}\}/i.test(html) ? "" : html;
1182
+ }
946
1183
  var BAND_ID_RE = /^page-block-(\d+)$/;
1184
+ var BLOCK_CLASS_RE = /\bblocks\d+\b/;
947
1185
  function bandBackground(el) {
948
1186
  if (!hasClass(el, "camediaload")) return void 0;
949
1187
  const rawId = el.getAttribute("data-media");
@@ -972,8 +1210,10 @@ function parseGridBands(html) {
972
1210
  const idStr = m[1];
973
1211
  if (idStr === void 0) continue;
974
1212
  const background = bandBackground(child);
1213
+ const blockClass = BLOCK_CLASS_RE.exec(child.classNames)?.[0];
975
1214
  bands.push({
976
1215
  index: Number(idStr),
1216
+ ...blockClass ? { blockClass } : {},
977
1217
  ...background ? { background } : {},
978
1218
  root: parseContainer(child)
979
1219
  });
@@ -1060,7 +1300,11 @@ function isEmptyRaw(node) {
1060
1300
  return text.length === 0;
1061
1301
  }
1062
1302
  function base(band) {
1063
- return { index: band.index, ...band.background ? { background: band.background } : {} };
1303
+ return {
1304
+ index: band.index,
1305
+ ...band.blockClass ? { blockClass: band.blockClass } : {},
1306
+ ...band.background ? { background: band.background } : {}
1307
+ };
1064
1308
  }
1065
1309
  function nodeText(node) {
1066
1310
  switch (node.kind) {
@@ -1077,8 +1321,18 @@ function nodeText(node) {
1077
1321
  return "";
1078
1322
  }
1079
1323
  }
1324
+ function unboxed(node) {
1325
+ let n = node;
1326
+ while (n.kind === "stack" && n.children.length === 1) {
1327
+ const only = n.children[0];
1328
+ if (!only) break;
1329
+ n = only;
1330
+ }
1331
+ return n;
1332
+ }
1080
1333
  function pureCellMedia(cell) {
1081
- return cell.node.kind === "media" ? cell.node.media : null;
1334
+ const n = unboxed(cell.node);
1335
+ return n.kind === "media" ? n.media : null;
1082
1336
  }
1083
1337
  function textRoleMeta(heading, subtitle) {
1084
1338
  return {
@@ -1093,6 +1347,12 @@ function cellRatio(cell) {
1093
1347
  if (t.cols === "any") return 50;
1094
1348
  return Math.round(100 / t.cols);
1095
1349
  }
1350
+ var isTextNode = (n) => n.kind === "heading" || n.kind === "body" || n.kind === "subtitle";
1351
+ var isBlankText = (n) => {
1352
+ if (n.kind === "heading" || n.kind === "body") return blockPlainText(n.html) === "";
1353
+ if (n.kind === "subtitle") return n.text.trim() === "";
1354
+ return false;
1355
+ };
1096
1356
  function carouselSlides(cells) {
1097
1357
  const out = [];
1098
1358
  for (const c of cells) {
@@ -1101,12 +1361,18 @@ function carouselSlides(cells) {
1101
1361
  out.push({ media: n.media });
1102
1362
  continue;
1103
1363
  }
1104
- if (n.kind === "stack" && n.children.length === 2) {
1105
- const [m, h] = n.children;
1106
- if (m?.kind === "media" && h?.kind === "heading") {
1364
+ if (n.kind === "stack" && n.children.length >= 2) {
1365
+ const [m, ...rest] = n.children;
1366
+ const h = rest.find((r) => r.kind === "heading");
1367
+ if (m?.kind === "media" && h?.kind === "heading" && rest.every(isTextNode)) {
1368
+ const sub = rest.find(
1369
+ (r) => r !== h && (r.kind === "body" || r.kind === "subtitle") && !isBlankText(r)
1370
+ );
1371
+ const subcaption = sub?.kind === "body" ? { html: sub.html, ...sub.role ? { role: sub.role } : {} } : sub?.kind === "subtitle" ? { html: sub.text, ...sub.role ? { role: sub.role } : {} } : void 0;
1107
1372
  out.push({
1108
1373
  media: m.media,
1109
- caption: { html: h.html, level: h.level, ...h.role ? { role: h.role } : {} }
1374
+ caption: { html: h.html, level: h.level, ...h.role ? { role: h.role } : {} },
1375
+ ...subcaption ? { subcaption } : {}
1110
1376
  });
1111
1377
  continue;
1112
1378
  }
@@ -1134,12 +1400,18 @@ function rewriteMapMounts(node, isMapMount) {
1134
1400
  token: c.token,
1135
1401
  node: rewriteMapMounts(c.node, isMapMount)
1136
1402
  })),
1137
- // Keep the slider marker dropping it here would silently demote a
1138
- // Carousel to Grid whenever a map config is present.
1139
- ...node.slider ? { slider: node.slider } : {}
1403
+ // Preserve the row's own markers when rebuilding: dropping the slider
1404
+ // would silently demote a Carousel to Grid, and dropping the style would
1405
+ // lose a card background for every band whenever a map config exists.
1406
+ ...node.slider ? { slider: node.slider } : {},
1407
+ ...node.style ? { style: node.style } : {}
1140
1408
  };
1141
1409
  case "stack":
1142
- return { kind: "stack", children: node.children.map((n) => rewriteMapMounts(n, isMapMount)) };
1410
+ return {
1411
+ kind: "stack",
1412
+ children: node.children.map((n) => rewriteMapMounts(n, isMapMount)),
1413
+ ...node.style ? { style: node.style } : {}
1414
+ };
1143
1415
  case "heading":
1144
1416
  case "body":
1145
1417
  case "subtitle":
@@ -1375,10 +1647,192 @@ function extractToggles(html) {
1375
1647
  return groups.length === labels.length && groups.every((g) => g !== void 0) ? groups : [];
1376
1648
  }
1377
1649
 
1650
+ // src/blux/grid/feed-grid.ts
1651
+ var termMatchesTag = (term, tag) => term === tag || term.endsWith("s") && term.slice(0, -1) === tag || tag.endsWith("s") && tag.slice(0, -1) === term;
1652
+ function tagFilter(expr) {
1653
+ const groups = (expr ?? "").split("||").map(
1654
+ (g) => g.split("&&").map((t) => t.trim().toLowerCase()).filter(Boolean)
1655
+ ).filter((g) => g.length > 0);
1656
+ if (!groups.length) return () => true;
1657
+ return (tags) => {
1658
+ const set = tags.map((t) => t.toLowerCase());
1659
+ return groups.some((g) => g.every((term) => set.some((tag) => termMatchesTag(term, tag))));
1660
+ };
1661
+ }
1662
+ function sortRecords(records, sort) {
1663
+ if (sort === "title") {
1664
+ return [...records].sort(
1665
+ (a, b) => String(a["title"] ?? "").localeCompare(String(b["title"] ?? ""))
1666
+ );
1667
+ }
1668
+ if (sort === "fdate" || sort === "date") {
1669
+ return [...records].sort(
1670
+ (a, b) => String(b["date"] ?? "").localeCompare(String(a["date"] ?? ""))
1671
+ );
1672
+ }
1673
+ return records;
1674
+ }
1675
+ function cropRatioOf(v) {
1676
+ if (typeof v !== "string") return void 0;
1677
+ const m = /^\s*(\d+(?:\.\d+)?)\s*:\s*(\d+(?:\.\d+)?)\s*$/.exec(v);
1678
+ if (!m) return void 0;
1679
+ const w = Number(m[1]);
1680
+ const h = Number(m[2]);
1681
+ return w > 0 && h > 0 ? `${w}:${h}` : void 0;
1682
+ }
1683
+ var isDisabled = (r) => r["disabled"] === true || r["disable"] === true;
1684
+ var styleDisabled = (cfg) => !!cfg && typeof cfg === "object" && cfg["class"] === "disable";
1685
+ function resolveFeedTiles(bandDef, resolvers) {
1686
+ const sources = Array.isArray(bandDef.sources) ? bandDef.sources.map(String) : [];
1687
+ const source = sources[0];
1688
+ if (!source) return null;
1689
+ const cfg = bandDef.sourceConfig ?? {};
1690
+ const filterExpr = cfg["filters"]?.tag;
1691
+ const match = tagFilter(filterExpr);
1692
+ const sort = cfg["sort"];
1693
+ const bodyOff = styleDisabled(cfg["_body"]);
1694
+ const titleOff = styleDisabled(cfg["_title"]);
1695
+ const ratio = cropRatioOf(cfg["mediaRatio"] ?? cfg["ratio"]);
1696
+ const framed = (m) => m && ratio ? { ...m, cropRatio: ratio, fit: "cover" } : m;
1697
+ if (source === "__media") {
1698
+ const matched = [];
1699
+ for (const [uuid, entry] of resolvers.media) {
1700
+ const type = String(entry["type"] ?? "");
1701
+ if (!type.startsWith("image/")) continue;
1702
+ if (!match(entry["tags"] ?? [])) continue;
1703
+ matched.push({ ...entry, __uuid: uuid });
1704
+ }
1705
+ const tiles2 = sortRecords(matched, sort).map((entry) => {
1706
+ const media = framed(
1707
+ resolvers.mediaFor(
1708
+ String(entry["__uuid"]),
1709
+ entry["type"],
1710
+ entry["name"]
1711
+ )
1712
+ );
1713
+ const tile = {};
1714
+ if (media) tile.media = media;
1715
+ if (!titleOff && entry["name"]) tile.title = escapeHtml(String(entry["name"]));
1716
+ if (!bodyOff && entry["description"]) tile.body = plainToHtml(String(entry["description"]));
1717
+ return tile;
1718
+ }).filter((t) => t.media || t.title || t.body);
1719
+ return tiles2.length ? tiles2 : null;
1720
+ }
1721
+ const records = resolvers.feeds.get(source);
1722
+ if (!records) return null;
1723
+ const enabled = records.filter((r) => !isDisabled(r));
1724
+ const filtered = filterExpr ? enabled.filter((r) => match(r["tags"] ?? [])) : enabled;
1725
+ const ordered = sortRecords(filtered, sort);
1726
+ const tiles = ordered.map((r) => {
1727
+ const tile = {};
1728
+ const m = r["media"];
1729
+ if (m?.media) {
1730
+ const media = framed(resolvers.mediaFor(m.media, m.type));
1731
+ if (media) tile.media = media;
1732
+ }
1733
+ if (!titleOff && r["title"]) tile.title = String(r["title"]);
1734
+ if (!bodyOff && r["body"]) tile.body = String(r["body"]);
1735
+ return tile;
1736
+ });
1737
+ return tiles.length ? tiles : null;
1738
+ }
1739
+ function materializeFeedGrid(opts) {
1740
+ const { heading, tiles, columns, spacing, overlay } = opts;
1741
+ const headingNode = heading ? {
1742
+ kind: "heading",
1743
+ level: heading.level,
1744
+ html: heading.html,
1745
+ ...heading.role ? { role: heading.role } : {}
1746
+ } : null;
1747
+ if (!tiles || !tiles.length) return headingNode;
1748
+ const cols = Math.max(1, Math.round(columns));
1749
+ const cells = tiles.map((t) => {
1750
+ const parts = [];
1751
+ if (t.media) parts.push({ kind: "media", media: t.media });
1752
+ if (t.title) parts.push({ kind: "heading", level: 6, html: t.title, role: "text6" });
1753
+ if (t.body) parts.push({ kind: "body", html: t.body });
1754
+ let node;
1755
+ if (overlay && t.media) {
1756
+ node = {
1757
+ kind: "stack",
1758
+ children: parts,
1759
+ style: {
1760
+ _overlay: overlay.ratio,
1761
+ ...overlay.color ? { _overlayColor: overlay.color } : {},
1762
+ ...overlay.valign ? { _overlayValign: overlay.valign } : {}
1763
+ }
1764
+ };
1765
+ } else {
1766
+ node = parts.length === 1 ? parts[0] : { kind: "stack", children: parts };
1767
+ }
1768
+ return {
1769
+ token: {
1770
+ cols,
1771
+ raw: `grid-${cols}${spacing ? `-s${spacing}` : ""}`,
1772
+ ...spacing ? { spacing } : {}
1773
+ },
1774
+ node
1775
+ };
1776
+ });
1777
+ const row = { kind: "row", cells };
1778
+ return headingNode ? { kind: "stack", children: [headingNode, row] } : row;
1779
+ }
1780
+ function escapeHtml(s) {
1781
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1782
+ }
1783
+ function plainToHtml(s) {
1784
+ const inner = escapeHtml(s.trim()).replace(/\r?\n/g, "<br>");
1785
+ return `<p>${inner}</p>`;
1786
+ }
1787
+ function extFor(mime, name) {
1788
+ const byMime = {
1789
+ "image/jpeg": "jpg",
1790
+ "image/jpg": "jpg",
1791
+ "image/png": "png",
1792
+ "image/svg+xml": "svg",
1793
+ "image/gif": "gif",
1794
+ "image/webp": "webp",
1795
+ "image/avif": "avif",
1796
+ "image/bmp": "bmp",
1797
+ "image/tiff": "tiff"
1798
+ };
1799
+ if (mime && byMime[mime]) return byMime[mime];
1800
+ const m = /\.([a-z0-9]{2,5})$/i.exec(name ?? "");
1801
+ const ext = m?.[1]?.toLowerCase();
1802
+ const IMG = /* @__PURE__ */ new Set(["jpg", "jpeg", "png", "svg", "gif", "webp", "avif", "bmp", "tif", "tiff"]);
1803
+ return ext && IMG.has(ext) ? ext === "jpeg" ? "jpg" : ext : null;
1804
+ }
1805
+ function isFeedBand(item) {
1806
+ return !!item && typeof item === "object" && Array.isArray(item.sources) && item.sources.length > 0;
1807
+ }
1808
+ function feedAssetBase(htmls, siteId) {
1809
+ for (const html of htmls) {
1810
+ const m = /data-base="(https?:\/\/[^"]+?\/)"/i.exec(html);
1811
+ if (m?.[1]) return m[1].replace(/^http:/, "https:");
1812
+ }
1813
+ return `https://${CDN_HOSTS[0]}/${siteId}/`;
1814
+ }
1815
+ function buildFeedResolvers(feeds, mediaLibrary, base2) {
1816
+ const feedMap = /* @__PURE__ */ new Map();
1817
+ for (const [id, f] of Object.entries(feeds)) {
1818
+ if (Array.isArray(f?.items)) feedMap.set(id, f.items);
1819
+ }
1820
+ const mediaMap = new Map(Object.entries(mediaLibrary));
1821
+ return {
1822
+ feeds: feedMap,
1823
+ media: mediaMap,
1824
+ mediaFor: (uuid, type, name) => {
1825
+ const ext = extFor(type, name);
1826
+ if (!ext) return null;
1827
+ return { kind: "image", assetId: uuid, base: base2, ext };
1828
+ }
1829
+ };
1830
+ }
1831
+
1378
1832
  // src/blux/emit/block-styles.ts
1379
- function blockStylesByIndex(siteJson) {
1833
+ function blockStylesByIndex(siteJson, pageIndex = 0) {
1380
1834
  const out = /* @__PURE__ */ new Map();
1381
- const blocks = siteJson?.content?.pages?.[0]?.items;
1835
+ const blocks = siteJson?.content?.pages?.[pageIndex]?.items;
1382
1836
  if (!Array.isArray(blocks)) return out;
1383
1837
  blocks.forEach((b, i) => {
1384
1838
  const styles = b?.styles ?? {};
@@ -1391,6 +1845,30 @@ function blockStylesByIndex(siteJson) {
1391
1845
  });
1392
1846
  return out;
1393
1847
  }
1848
+ var CONTAINER_RE = /^\.(blocks\d+)container$/;
1849
+ function blockClassDefaults(siteJson) {
1850
+ const out = /* @__PURE__ */ new Map();
1851
+ const blocks = siteJson?.styles?.blocks;
1852
+ if (!Array.isArray(blocks)) return out;
1853
+ for (const slot of blocks) {
1854
+ if (slot === null || typeof slot !== "object") continue;
1855
+ for (const [selector, css] of Object.entries(slot)) {
1856
+ const blockClass = CONTAINER_RE.exec(selector)?.[1];
1857
+ if (!blockClass || css === null || typeof css !== "object") continue;
1858
+ const rec = css;
1859
+ const padding = cleanCssValue(rec["padding"]);
1860
+ const mobilePadding = cleanCssValue(rec["__media_mobile_padding"]);
1861
+ const maxWidth = cleanCssValue(rec["max-width"]);
1862
+ const defaults = {
1863
+ ...padding ? { padding } : {},
1864
+ ...mobilePadding ? { mobilePadding } : {},
1865
+ ...maxWidth ? { maxWidth } : {}
1866
+ };
1867
+ if (Object.keys(defaults).length) out.set(blockClass, defaults);
1868
+ }
1869
+ }
1870
+ return out;
1871
+ }
1394
1872
 
1395
1873
  // src/blux/emit/grid-slice.ts
1396
1874
  function stripTags(html) {
@@ -1446,9 +1924,10 @@ function sliceSpecToPlanSlice(spec) {
1446
1924
  return {
1447
1925
  slice_type: "carousel",
1448
1926
  variation: "default",
1449
- items: spec.slides.map(
1450
- (s) => s.caption ? { caption: blockPlainText(s.caption.html) } : {}
1451
- ),
1927
+ items: spec.slides.map((s) => ({
1928
+ ...s.caption ? { caption: blockPlainText(s.caption.html) } : {},
1929
+ ...s.subcaption ? { subcaption: blockPlainText(s.subcaption.html) } : {}
1930
+ })),
1452
1931
  primary: { band: spec.index }
1453
1932
  };
1454
1933
  case "MediaFull":
@@ -1526,13 +2005,17 @@ function collectPlanAssets(specs, resolve, diagnostics) {
1526
2005
  }
1527
2006
  function buildGridPlan(specs, ir) {
1528
2007
  const page = ir.pages[0];
1529
- const uid = page?.uid ?? "home";
1530
- const title = page?.title ?? uid;
1531
- const doc = {
2008
+ return buildGridSitePlan(
2009
+ [{ uid: page?.uid ?? "home", title: page?.title ?? page?.uid ?? "home", specs }],
2010
+ ir
2011
+ );
2012
+ }
2013
+ function buildGridSitePlan(pages, ir) {
2014
+ const documents = pages.map((p) => ({
1532
2015
  type: "page",
1533
- uid,
1534
- data: { title: richText(`<h1>${title}</h1>`), slices: specs.map(sliceSpecToPlanSlice) }
1535
- };
2016
+ uid: p.uid,
2017
+ data: { title: richText(`<h1>${p.title}</h1>`), slices: p.specs.map(sliceSpecToPlanSlice) }
2018
+ }));
1536
2019
  const assetById = new Map(ir.assets.map((a) => [a.id, a]));
1537
2020
  const sourceUrlById = new Map(ir.assets.map((a) => [a.id, a.sourceUrl]));
1538
2021
  const resolve = (m) => {
@@ -1541,9 +2024,13 @@ function buildGridPlan(specs, ir) {
1541
2024
  return url ? { id: m.assetId, url, alt: asset?.alt ?? "" } : null;
1542
2025
  };
1543
2026
  const diagnostics = [...ir.diagnostics ?? []];
1544
- const assets = collectPlanAssets(specs, resolve, diagnostics);
2027
+ const assets = collectPlanAssets(
2028
+ pages.flatMap((p) => p.specs),
2029
+ resolve,
2030
+ diagnostics
2031
+ );
1545
2032
  const customTypes = ir.collections.map(buildCustomType);
1546
- return { customTypes, documents: [doc], assets, stylesManifest: [], diagnostics };
2033
+ return { customTypes, documents, assets, stylesManifest: [], diagnostics };
1547
2034
  }
1548
2035
 
1549
2036
  // src/blux/emit/presentation.ts
@@ -1562,7 +2049,7 @@ function renderNode(node, resolve) {
1562
2049
  const rn = renderNode(c.node, resolve);
1563
2050
  if (rn) cells.push({ token: renderToken(c.token), node: rn });
1564
2051
  }
1565
- return { kind: "row", cells };
2052
+ return { kind: "row", cells, ...node.style ? { style: node.style } : {} };
1566
2053
  }
1567
2054
  case "stack": {
1568
2055
  const children = [];
@@ -1570,19 +2057,30 @@ function renderNode(node, resolve) {
1570
2057
  const rn = renderNode(c, resolve);
1571
2058
  if (rn) children.push(rn);
1572
2059
  }
1573
- return { kind: "stack", children };
2060
+ return { kind: "stack", children, ...node.style ? { style: node.style } : {} };
1574
2061
  }
1575
2062
  case "heading":
1576
2063
  return {
1577
2064
  kind: "heading",
1578
2065
  level: node.level,
1579
2066
  html: node.html,
1580
- ...node.role ? { role: node.role } : {}
2067
+ ...node.role ? { role: node.role } : {},
2068
+ ...node.style ? { style: node.style } : {}
1581
2069
  };
1582
2070
  case "body":
1583
- return { kind: "body", html: node.html, ...node.role ? { role: node.role } : {} };
2071
+ return {
2072
+ kind: "body",
2073
+ html: node.html,
2074
+ ...node.role ? { role: node.role } : {},
2075
+ ...node.style ? { style: node.style } : {}
2076
+ };
1584
2077
  case "subtitle":
1585
- return { kind: "subtitle", text: node.text, ...node.role ? { role: node.role } : {} };
2078
+ return {
2079
+ kind: "subtitle",
2080
+ text: node.text,
2081
+ ...node.role ? { role: node.role } : {},
2082
+ ...node.style ? { style: node.style } : {}
2083
+ };
1586
2084
  case "media": {
1587
2085
  const m = resolve(node.media);
1588
2086
  return m ? { kind: "media", media: m } : null;
@@ -1599,12 +2097,45 @@ function hasMapWidget(node) {
1599
2097
  if (node.kind === "stack") return node.children.some(hasMapWidget);
1600
2098
  return false;
1601
2099
  }
2100
+ function markPanelRows(node, toggleCount) {
2101
+ if (node.kind === "row") {
2102
+ return {
2103
+ ...node,
2104
+ cells: node.cells.map((c) => ({ ...c, node: markPanelRows(c.node, toggleCount) }))
2105
+ };
2106
+ }
2107
+ if (node.kind === "stack") {
2108
+ const children = node.children.map((c) => markPanelRows(c, toggleCount));
2109
+ for (let i = 0; i + 1 < children.length; i++) {
2110
+ const cur = children[i];
2111
+ const next = children[i + 1];
2112
+ if (cur?.kind === "widget" && cur.widget.type === "map" && next?.kind === "row" && next.cells.length === toggleCount) {
2113
+ children[i + 1] = { ...next, panels: true };
2114
+ }
2115
+ }
2116
+ return { ...node, children };
2117
+ }
2118
+ return node;
2119
+ }
1602
2120
  function buildPresentation(specs, deps) {
1603
2121
  const bands = {};
1604
2122
  for (const spec of specs) {
1605
2123
  const bp = {};
1606
2124
  const style = deps.styleFor(spec.index);
1607
2125
  if (style) bp.style = style;
2126
+ const defaults = spec.blockClass ? deps.defaultsFor(spec.blockClass) : void 0;
2127
+ if (defaults) {
2128
+ const own = bp.style ?? {};
2129
+ const fill = {};
2130
+ if (own["_contentPadding"] === void 0 && defaults.padding) {
2131
+ fill["_contentPadding"] = defaults.padding;
2132
+ if (defaults.mobilePadding) fill["_contentPaddingMobile"] = defaults.mobilePadding;
2133
+ }
2134
+ if (own["_max-content-width"] === void 0 && defaults.maxWidth) {
2135
+ fill["_max-content-width"] = defaults.maxWidth;
2136
+ }
2137
+ if (Object.keys(fill).length) bp.style = { ...own, ...fill };
2138
+ }
1608
2139
  if (spec.background) {
1609
2140
  const bg = deps.resolveMedia(spec.background);
1610
2141
  if (bg) bp.background = bg;
@@ -1633,14 +2164,15 @@ function buildPresentation(specs, deps) {
1633
2164
  for (const s of spec.slides) {
1634
2165
  const media = deps.resolveMedia(s.media);
1635
2166
  if (!media) break;
1636
- const slide = {
1637
- media
1638
- };
2167
+ const slide = { media };
1639
2168
  if (s.caption) {
1640
2169
  const caption = { level: s.caption.level };
1641
2170
  if (s.caption.role !== void 0) caption.role = s.caption.role;
1642
2171
  slide.caption = caption;
1643
2172
  }
2173
+ if (s.subcaption) {
2174
+ slide.subcaption = s.subcaption.role !== void 0 ? { role: s.subcaption.role } : {};
2175
+ }
1644
2176
  slides.push(slide);
1645
2177
  }
1646
2178
  if (slides.length > 0) {
@@ -1665,8 +2197,9 @@ function buildPresentation(specs, deps) {
1665
2197
  break;
1666
2198
  case "Grid": {
1667
2199
  const tree = renderNode(spec.root, deps.resolveMedia);
1668
- if (tree) bp.tree = tree;
1669
- if (deps.map && hasMapWidget(spec.root)) bp.map = deps.map;
2200
+ const co = deps.map && hasMapWidget(spec.root) ? deps.map : null;
2201
+ if (co) bp.map = co;
2202
+ if (tree) bp.tree = co ? markPanelRows(tree, co.toggles.length) : tree;
1670
2203
  break;
1671
2204
  }
1672
2205
  }
@@ -1699,6 +2232,7 @@ function convertExport({
1699
2232
  const assetsById = new Map(ir.assets.map((a) => [a.id, a]));
1700
2233
  const sourceUrlById = new Map(ir.assets.map((a) => [a.id, a.sourceUrl]));
1701
2234
  const styles = blockStylesByIndex(siteJson);
2235
+ const defaults = blockClassDefaults(siteJson);
1702
2236
  const deps = {
1703
2237
  resolveMedia: (m) => {
1704
2238
  const url = mediaUrl(m, sourceUrlById);
@@ -1713,17 +2247,284 @@ function convertExport({
1713
2247
  ...m.fit ? { fit: m.fit } : {},
1714
2248
  ...m.position ? { position: m.position } : {},
1715
2249
  ...m.minHeight ? { minHeight: m.minHeight } : {},
2250
+ ...m.cropRatio ? { cropRatio: m.cropRatio } : {},
1716
2251
  ...m.playback ? { playback: m.playback } : {}
1717
2252
  };
1718
2253
  return rm;
1719
2254
  },
1720
2255
  styleFor: (i) => styles.get(i),
2256
+ defaultsFor: (blockClass) => defaults.get(blockClass),
1721
2257
  map: mapConfig ? mapRenderFromConfig(mapConfig) : null
1722
2258
  };
1723
2259
  const plan = buildGridPlan(specs, ir);
1724
2260
  const presentation = buildPresentation(specs, deps);
1725
2261
  return { bands, specs, ir, mapConfig, plan, presentation };
1726
2262
  }
2263
+ function leadingHeadings(root) {
2264
+ if (root.kind === "heading" || root.kind === "subtitle") return [root];
2265
+ if (root.kind === "stack") {
2266
+ const out = [];
2267
+ for (const c of root.children) {
2268
+ if (c.kind === "heading" || c.kind === "subtitle") out.push(c);
2269
+ else break;
2270
+ }
2271
+ return out;
2272
+ }
2273
+ return [];
2274
+ }
2275
+ function feedColumns(item) {
2276
+ const n = Number(item.columns);
2277
+ return Number.isFinite(n) && n > 0 ? n : 3;
2278
+ }
2279
+ function tileOverlay(item) {
2280
+ const cfg = item?.sourceConfig;
2281
+ if (!cfg || cfg["overlay"] !== true) return null;
2282
+ const ratio = cropRatioOf(cfg["mediaRatio"] ?? cfg["ratio"]);
2283
+ if (!ratio) return null;
2284
+ const color = typeof cfg["overlayColor"] === "string" ? cfg["overlayColor"] : void 0;
2285
+ const valign = typeof cfg["contentvalign"] === "string" ? cfg["contentvalign"] : void 0;
2286
+ return { ratio, ...color ? { color } : {}, ...valign ? { valign } : {} };
2287
+ }
2288
+ function isEmptyish(root) {
2289
+ switch (root.kind) {
2290
+ case "heading":
2291
+ case "subtitle":
2292
+ return true;
2293
+ case "raw":
2294
+ return root.html.trim() === "";
2295
+ case "media":
2296
+ case "widget":
2297
+ return false;
2298
+ case "row":
2299
+ return root.cells.length === 0;
2300
+ case "stack":
2301
+ return root.children.every(isEmptyish);
2302
+ default:
2303
+ return false;
2304
+ }
2305
+ }
2306
+ function materializeFeedBands(bands, pageItems, resolvers, diagnostics) {
2307
+ if (!Array.isArray(pageItems)) return;
2308
+ for (const band of bands) {
2309
+ const item = pageItems[band.index];
2310
+ if (!isFeedBand(item)) continue;
2311
+ if (!isEmptyish(band.root)) {
2312
+ diagnostics.push({
2313
+ kind: "empty-feed-grid",
2314
+ where: String(band.index),
2315
+ message: `band ${band.index} has parsed content but site.json item is a feed source \u2014 positional join misaligned, left as parsed`
2316
+ });
2317
+ continue;
2318
+ }
2319
+ const tiles = resolveFeedTiles(item, resolvers);
2320
+ if (!tiles) {
2321
+ diagnostics.push({
2322
+ kind: "empty-feed-grid",
2323
+ where: String(band.index),
2324
+ message: `feed band ${band.index} (source ${String(item.sources[0])}) resolved to no tiles`
2325
+ });
2326
+ continue;
2327
+ }
2328
+ const spacing = parseInt(String(item.spacing ?? ""), 10);
2329
+ const row = materializeFeedGrid({
2330
+ tiles,
2331
+ columns: feedColumns(item),
2332
+ ...Number.isFinite(spacing) && spacing > 0 ? { spacing } : {},
2333
+ ...tileOverlay(item) ? { overlay: tileOverlay(item) } : {}
2334
+ });
2335
+ if (!row) continue;
2336
+ const headings = leadingHeadings(band.root);
2337
+ band.root = headings.length ? { kind: "stack", children: [...headings, row] } : row;
2338
+ }
2339
+ }
2340
+ function sitePages(siteJson) {
2341
+ const { pages } = normalizePages(parseBluxSite(siteJson));
2342
+ return pages.map((p) => ({ uid: p.uid, path: p.path, title: p.title }));
2343
+ }
2344
+ function convertSite({
2345
+ siteJson,
2346
+ htmlByUid
2347
+ }) {
2348
+ const ir = assembleIR({ siteJson, htmls: [...htmlByUid.values()] });
2349
+ const assetsById = new Map(ir.assets.map((a) => [a.id, a]));
2350
+ const sourceUrlById = new Map(ir.assets.map((a) => [a.id, a.sourceUrl]));
2351
+ const defaults = blockClassDefaults(siteJson);
2352
+ const raw = parseBluxSite(siteJson);
2353
+ const assetBase = feedAssetBase([...htmlByUid.values()], ir.meta.bluxSiteId);
2354
+ const feedResolvers = buildFeedResolvers(raw.feeds, raw.media, assetBase);
2355
+ const pageItemsByIndex = siteJson?.content?.pages;
2356
+ const pages = [];
2357
+ const presentation = { pages: {} };
2358
+ ir.pages.forEach((page, pageIndex) => {
2359
+ const html = htmlByUid.get(page.uid);
2360
+ if (html === void 0) {
2361
+ ir.diagnostics.push({
2362
+ kind: "missing-page-html",
2363
+ where: page.uid,
2364
+ message: `no rendered html for page "${page.uid}" (${page.path || "/"}) \u2014 page skipped`
2365
+ });
2366
+ return;
2367
+ }
2368
+ const bands = parseGridBands(html);
2369
+ materializeFeedBands(
2370
+ bands,
2371
+ pageItemsByIndex?.[pageIndex]?.items,
2372
+ feedResolvers,
2373
+ ir.diagnostics
2374
+ );
2375
+ const mapConfig = extractMapConfig(html);
2376
+ const specs = classifyBands(bands, mapConfig ? { isMapMount: makeIsMapMount(mapConfig) } : {});
2377
+ const styles = blockStylesByIndex(siteJson, pageIndex);
2378
+ const deps = {
2379
+ resolveMedia: (m) => {
2380
+ const url = mediaUrl(m, sourceUrlById);
2381
+ if (!url) return null;
2382
+ const alt = assetsById.get(m.assetId)?.alt;
2383
+ const rm = {
2384
+ kind: m.kind,
2385
+ url,
2386
+ ...alt ? { alt } : {},
2387
+ ...m.width !== void 0 ? { width: m.width } : {},
2388
+ ...m.aspect !== void 0 ? { aspect: m.aspect } : {},
2389
+ ...m.fit ? { fit: m.fit } : {},
2390
+ ...m.position ? { position: m.position } : {},
2391
+ ...m.minHeight ? { minHeight: m.minHeight } : {},
2392
+ ...m.cropRatio ? { cropRatio: m.cropRatio } : {},
2393
+ ...m.playback ? { playback: m.playback } : {}
2394
+ };
2395
+ return rm;
2396
+ },
2397
+ styleFor: (i) => styles.get(i),
2398
+ defaultsFor: (blockClass) => defaults.get(blockClass),
2399
+ map: mapConfig ? mapRenderFromConfig(mapConfig) : null
2400
+ };
2401
+ presentation.pages[page.uid] = buildPresentation(specs, deps);
2402
+ pages.push({ uid: page.uid, title: page.title, path: page.path, bands, specs, mapConfig });
2403
+ });
2404
+ const plan = buildGridSitePlan(
2405
+ pages.map((p) => ({ uid: p.uid, title: p.title, specs: p.specs })),
2406
+ ir
2407
+ );
2408
+ return { pages, ir, plan, presentation };
2409
+ }
2410
+
2411
+ // src/blux/emit/site-config.ts
2412
+ function parseNavItems(items) {
2413
+ if (!Array.isArray(items)) return [];
2414
+ const out = [];
2415
+ for (const raw of items) {
2416
+ const label = typeof raw?.title === "string" ? raw.title.trim() : "";
2417
+ if (!label) continue;
2418
+ const href = typeof raw?.link === "string" ? raw.link : "";
2419
+ const children = parseNavItems(raw?.items);
2420
+ out.push({ label, href, ...children.length ? { children } : {} });
2421
+ }
2422
+ return out;
2423
+ }
2424
+ var SOCIAL_DOMAINS = {
2425
+ facebook: ["facebook.com", "fb.com"],
2426
+ twitter: ["twitter.com", "x.com"],
2427
+ instagram: ["instagram.com"],
2428
+ pinterest: ["pinterest.com"],
2429
+ linkedin: ["linkedin.com"],
2430
+ "linkedin-company": ["linkedin.com"],
2431
+ youtube: ["youtube.com", "youtu.be"],
2432
+ reddit: ["reddit.com"]
2433
+ };
2434
+ function hostMatches(host, domain) {
2435
+ return host === domain || host.endsWith("." + domain);
2436
+ }
2437
+ function hostOf(url) {
2438
+ try {
2439
+ return new URL(url).hostname.toLowerCase();
2440
+ } catch {
2441
+ return "";
2442
+ }
2443
+ }
2444
+ function footerRegion(html) {
2445
+ const lower = html.toLowerCase();
2446
+ const start = lower.lastIndexOf("<footer");
2447
+ if (start === -1) return html;
2448
+ const end = lower.indexOf("</footer>", start);
2449
+ return end === -1 ? html.slice(start) : html.slice(start, end + "</footer>".length);
2450
+ }
2451
+ function socialHrefResolverFromHtml(htmls) {
2452
+ const hrefs = [];
2453
+ const re = /href\s*=\s*["']([^"']+)["']/gi;
2454
+ for (const html of htmls) {
2455
+ if (typeof html !== "string") continue;
2456
+ const region = footerRegion(html);
2457
+ let m;
2458
+ while (m = re.exec(region)) {
2459
+ const url = m[1];
2460
+ if (url && /^https?:\/\//i.test(url)) hrefs.push(url);
2461
+ }
2462
+ }
2463
+ return (network) => {
2464
+ const domains = SOCIAL_DOMAINS[network];
2465
+ if (!domains) return void 0;
2466
+ return hrefs.find((u) => {
2467
+ const host = hostOf(u);
2468
+ return host && domains.some((d) => hostMatches(host, d));
2469
+ });
2470
+ };
2471
+ }
2472
+ function parseSocials(raw, resolveSocialHref) {
2473
+ const out = [];
2474
+ const items = Array.isArray(raw) ? raw : [];
2475
+ for (const it of items) {
2476
+ const media = it?.media;
2477
+ if (!media || media.type !== "social") continue;
2478
+ const networks = media.networks;
2479
+ if (!networks || typeof networks !== "object") continue;
2480
+ const urls = media.urls ?? {};
2481
+ for (const [network, on] of Object.entries(networks)) {
2482
+ if (on !== true) continue;
2483
+ const fromExport = typeof urls[network] === "string" ? urls[network] : void 0;
2484
+ const url = fromExport ?? resolveSocialHref?.(network);
2485
+ out.push({ network, ...url ? { href: url } : {} });
2486
+ }
2487
+ }
2488
+ return out;
2489
+ }
2490
+ function collectTitles(node, acc) {
2491
+ if (Array.isArray(node)) {
2492
+ for (const n of node) collectTitles(n, acc);
2493
+ return acc;
2494
+ }
2495
+ if (node && typeof node === "object") {
2496
+ const rec = node;
2497
+ if (typeof rec.title === "string" && rec.title.trim()) acc.push(rec.title.trim());
2498
+ for (const k of Object.keys(rec)) if (k !== "title") collectTitles(rec[k], acc);
2499
+ }
2500
+ return acc;
2501
+ }
2502
+ var COPYRIGHT_RE = /©|\(c\)|\bcopyright\b|rights reserved/i;
2503
+ function parseFooterText(raw) {
2504
+ return collectTitles(raw, []).find((t) => COPYRIGHT_RE.test(t));
2505
+ }
2506
+ function buildSiteConfig(siteJson, resolveLogo, resolveSocialHref) {
2507
+ const j = siteJson;
2508
+ const navRoot = Array.isArray(j.navigation) ? j.navigation[0] : j.navigation;
2509
+ const footRoot = Array.isArray(j.footer) ? j.footer[0] : j.footer;
2510
+ const items = parseNavItems(navRoot?.items);
2511
+ const logoMedia = navRoot?.logo?.media;
2512
+ const logoUuid = typeof logoMedia?.media === "string" ? logoMedia.media : void 0;
2513
+ const logoUrl = logoUuid ? resolveLogo(logoUuid) : null;
2514
+ const maxWidth = typeof logoMedia?.["max-width"] === "string" ? logoMedia["max-width"] : void 0;
2515
+ const socials = parseSocials(footRoot?.items, resolveSocialHref);
2516
+ const text = parseFooterText(footRoot?.items);
2517
+ return {
2518
+ nav: {
2519
+ ...logoUrl ? { logo: { url: logoUrl, ...maxWidth ? { maxWidth } : {} } } : {},
2520
+ items
2521
+ },
2522
+ footer: {
2523
+ socials,
2524
+ ...text ? { text } : {}
2525
+ }
2526
+ };
2527
+ }
1727
2528
 
1728
2529
  // src/blux/emit/validate-layout.ts
1729
2530
  function tokKey(t) {
@@ -1963,11 +2764,19 @@ function walkBand(bp, map) {
1963
2764
  };
1964
2765
  return out;
1965
2766
  }
1966
- function rewriteManifestUrls(manifest, urlMap) {
2767
+ function rewritePage(p, map) {
1967
2768
  const bands = {};
1968
- for (const [k, bp] of Object.entries(manifest.bands)) bands[k] = walkBand(bp, urlMap);
2769
+ for (const [k, bp] of Object.entries(p.bands)) bands[k] = walkBand(bp, map);
1969
2770
  return { bands };
1970
2771
  }
2772
+ function rewriteManifestUrls(manifest, urlMap) {
2773
+ if ("pages" in manifest && manifest.pages) {
2774
+ const pages = {};
2775
+ for (const [uid, p] of Object.entries(manifest.pages)) pages[uid] = rewritePage(p, urlMap);
2776
+ return { pages };
2777
+ }
2778
+ return rewritePage(manifest, urlMap);
2779
+ }
1971
2780
 
1972
2781
  // src/cli/commands/blux.ts
1973
2782
  async function runBluxCommand(action, dir, opts) {
@@ -2021,9 +2830,10 @@ async function runBluxCommand(action, dir, opts) {
2021
2830
  await writeFile(join(out, "ir.json"), JSON.stringify(ir, null, 2));
2022
2831
  await writeFile(join(out, "migration-plan.json"), JSON.stringify(plan, null, 2));
2023
2832
  const rolesCss = emitRolesCss(ir.theme);
2833
+ const buttonsCss = emitButtonsCss(ir.theme);
2024
2834
  await writeFile(
2025
2835
  join(out, "theme.css"),
2026
- emitThemeCss(ir.theme) + (rolesCss ? "\n" + rolesCss : "")
2836
+ emitThemeCss(ir.theme) + (rolesCss ? "\n" + rolesCss : "") + (buttonsCss ? "\n" + buttonsCss : "")
2027
2837
  );
2028
2838
  await writeFile(join(out, "review-manifest.json"), JSON.stringify(manifest, null, 2));
2029
2839
  await writeFile(
@@ -2068,12 +2878,16 @@ async function runBluxCommand(action, dir, opts) {
2068
2878
  WARNING missing assets: ${r.missingAssets.join(", ")}` : "";
2069
2879
  let manifestNote = "";
2070
2880
  const manifestPath = join(dirname(planPath), "blux-presentation.json");
2881
+ let manifestRaw = null;
2071
2882
  try {
2072
- const manifest = JSON.parse(await readFile(manifestPath, "utf-8"));
2883
+ manifestRaw = await readFile(manifestPath, "utf-8");
2884
+ } catch {
2885
+ }
2886
+ if (manifestRaw !== null) {
2887
+ const manifest = JSON.parse(manifestRaw);
2073
2888
  const rewritten = rewriteManifestUrls(manifest, r.assetUrlByCdn);
2074
2889
  await writeFile(manifestPath, JSON.stringify(rewritten, null, 2) + "\n");
2075
2890
  manifestNote = "\nmanifest media rewritten to Prismic urls";
2076
- } catch {
2077
2891
  }
2078
2892
  return {
2079
2893
  output: `custom types pushed: ${pushed.join(", ") || "none"}
@@ -2163,15 +2977,24 @@ assets: ${r.assetsUploaded} uploaded, ${r.assetsReused} reused | documents: ${r.
2163
2977
  }
2164
2978
  if (action === "convert") {
2165
2979
  if (!dir) return { output: "blux convert needs a Blux export directory.", code: 1 };
2166
- let html;
2167
2980
  let siteJson;
2168
2981
  try {
2169
- html = await readFile(join(dir, "index.html"), "utf-8");
2170
2982
  siteJson = JSON.parse(await readFile(join(dir, "site.json"), "utf-8"));
2171
2983
  } catch (err) {
2172
2984
  return { output: `could not read export in ${dir}: ${err.message}`, code: 1 };
2173
2985
  }
2174
- const { bands, mapConfig, plan, presentation, ir, specs } = convertExport({ html, siteJson });
2986
+ const htmlByUid = /* @__PURE__ */ new Map();
2987
+ for (const p of sitePages(siteJson)) {
2988
+ const file = p.path ? join(dir, p.path, "index.html") : join(dir, "index.html");
2989
+ try {
2990
+ htmlByUid.set(p.uid, await readFile(file, "utf-8"));
2991
+ } catch {
2992
+ }
2993
+ }
2994
+ if (!htmlByUid.size) {
2995
+ return { output: `could not read any page html in ${dir}`, code: 1 };
2996
+ }
2997
+ const { pages, ir, plan, presentation } = convertSite({ siteJson, htmlByUid });
2175
2998
  const outDir = opts.out ?? join(dir, "blux-out");
2176
2999
  await mkdir(outDir, { recursive: true });
2177
3000
  await writeFile(join(outDir, "migration-plan.json"), JSON.stringify(plan, null, 2));
@@ -2179,18 +3002,76 @@ assets: ${r.assetsUploaded} uploaded, ${r.assetsReused} reused | documents: ${r.
2179
3002
  join(outDir, "blux-presentation.json"),
2180
3003
  JSON.stringify(presentation, null, 2) + "\n"
2181
3004
  );
2182
- await writeFile(
2183
- join(outDir, "theme.css"),
2184
- emitThemeCss(ir.theme) + "\n" + emitRolesCss(ir.theme)
3005
+ {
3006
+ const buttonsCss = emitButtonsCss(ir.theme);
3007
+ await writeFile(
3008
+ join(outDir, "theme.css"),
3009
+ emitThemeCss(ir.theme) + "\n" + emitRolesCss(ir.theme) + (buttonsCss ? "\n" + buttonsCss : "")
3010
+ );
3011
+ }
3012
+ const mapConfigs = Object.fromEntries(
3013
+ pages.filter((p) => p.mapConfig).map((p) => [p.uid, p.mapConfig])
2185
3014
  );
2186
- if (mapConfig) {
2187
- await writeFile(join(outDir, "map-config.json"), JSON.stringify(mapConfig, null, 2) + "\n");
3015
+ if (Object.keys(mapConfigs).length) {
3016
+ await writeFile(join(outDir, "map-config.json"), JSON.stringify(mapConfigs, null, 2) + "\n");
2188
3017
  }
2189
- const layout = validateLayout(specs, presentation);
2190
- await writeFile(join(outDir, "layout-report.json"), JSON.stringify(layout, null, 2) + "\n");
2191
- const sliceCount = plan.documents[0]?.data.slices?.length ?? 0;
3018
+ {
3019
+ const sourceUrlById = new Map(ir.assets.map((a) => [a.id, a.sourceUrl]));
3020
+ const base2 = feedAssetBase([...htmlByUid.values()], ir.meta.bluxSiteId);
3021
+ const mediaDict = siteJson.media ?? {};
3022
+ const extByMime = {
3023
+ "image/png": "png",
3024
+ "image/jpeg": "jpg",
3025
+ "image/svg+xml": "svg",
3026
+ "image/gif": "gif",
3027
+ "image/webp": "webp"
3028
+ };
3029
+ const resolveLogo = (uuid) => {
3030
+ const scraped = sourceUrlById.get(uuid);
3031
+ if (scraped) return scraped;
3032
+ const ext = extByMime[String(mediaDict[uuid]?.type ?? "")];
3033
+ return ext ? `${base2}${uuid}.${ext}` : null;
3034
+ };
3035
+ const resolveSocialHref = socialHrefResolverFromHtml([...htmlByUid.values()]);
3036
+ const siteConfig = buildSiteConfig(siteJson, resolveLogo, resolveSocialHref);
3037
+ await writeFile(join(outDir, "site-config.json"), JSON.stringify(siteConfig, null, 2) + "\n");
3038
+ }
3039
+ let faviconLine = "";
3040
+ if (ir.meta.favicon?.sourceUrl) {
3041
+ const { assetId, sourceUrl } = ir.meta.favicon;
3042
+ try {
3043
+ const res = await (opts.fetchImpl ?? fetch)(sourceUrl);
3044
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
3045
+ await writeFile(join(outDir, "favicon.png"), new Uint8Array(await res.arrayBuffer()));
3046
+ faviconLine = `
3047
+ favicon \u2192 ${join(outDir, "favicon.png")}`;
3048
+ } catch (err) {
3049
+ await writeFile(
3050
+ join(outDir, "favicon.json"),
3051
+ JSON.stringify({ assetId, url: sourceUrl }, null, 2) + "\n"
3052
+ );
3053
+ faviconLine = `
3054
+ favicon fetch failed (${err.message}) \u2014 url preserved in ${join(outDir, "favicon.json")}`;
3055
+ }
3056
+ }
3057
+ const layoutByUid = {};
3058
+ const reportLines = [];
3059
+ for (const p of pages) {
3060
+ const pagePresentation = presentation.pages[p.uid];
3061
+ if (!pagePresentation) continue;
3062
+ const layout = validateLayout(p.specs, pagePresentation);
3063
+ layoutByUid[p.uid] = layout;
3064
+ reportLines.push(`[${p.uid}] ${formatLayoutReport(layout)}`);
3065
+ }
3066
+ await writeFile(
3067
+ join(outDir, "layout-report.json"),
3068
+ JSON.stringify({ pages: layoutByUid }, null, 2) + "\n"
3069
+ );
3070
+ const totalBands = pages.reduce((n, p) => n + p.bands.length, 0);
3071
+ const missing = ir.diagnostics.filter((d) => d.kind === "missing-page-html");
2192
3072
  return {
2193
- output: `Converted ${bands.length} bands \u2192 ${outDir} (${Object.keys(presentation.bands).length} manifest bands, ${sliceCount} slices` + (mapConfig ? ", map config extracted" : "") + ")\n" + formatLayoutReport(layout),
3073
+ output: `Converted ${pages.length} pages / ${totalBands} bands \u2192 ${outDir} (${plan.documents.length} page documents` + (Object.keys(mapConfigs).length ? `, map config on ${Object.keys(mapConfigs).join(", ")}` : "") + ")" + (missing.length ? `
3074
+ skipped (no html): ${missing.map((d) => d.where).join(", ")}` : "") + faviconLine + "\n" + reportLines.join("\n"),
2194
3075
  code: 0
2195
3076
  };
2196
3077
  }
@@ -2202,4 +3083,4 @@ assets: ${r.assetsUploaded} uploaded, ${r.assetsReused} reused | documents: ${r.
2202
3083
  export {
2203
3084
  runBluxCommand
2204
3085
  };
2205
- //# sourceMappingURL=blux-FKZYMKLF.js.map
3086
+ //# sourceMappingURL=blux-OL5HKP26.js.map