@ohhwells/bridge 0.1.79 → 0.1.80-next.244

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -69,6 +69,7 @@ function isRenderableTree(value) {
69
69
 
70
70
  // src/lib/ai-sections-store.ts
71
71
  var AI_SECTIONS_KEY = "__ohw_ai_sections";
72
+ var AI_SLOT_KEY_PREFIX = "ai.";
72
73
  var EMPTY_AI_SECTIONS = { v: 1, sections: [] };
73
74
  function parseAiSectionsState(raw) {
74
75
  if (!raw) return EMPTY_AI_SECTIONS;
@@ -112,6 +113,63 @@ function applyTreeToState(state, payload) {
112
113
  const others = state.sections.filter((existing) => existing.id !== entry.id);
113
114
  return { ...state, v: 1, sections: [...others, entry] };
114
115
  }
116
+ function foldAlignIntoTrees(state, store) {
117
+ const byId = new Map(state.sections.map((entry) => [entry.id, entry]));
118
+ const nextTrees = /* @__PURE__ */ new Map();
119
+ const treeFor = (id) => {
120
+ const cloned = nextTrees.get(id);
121
+ if (cloned) return cloned;
122
+ const entry = byId.get(id);
123
+ if (!entry) return void 0;
124
+ const fresh = {
125
+ ...entry.tree,
126
+ rows: entry.tree.rows.map((row) => ({ ...row, blocks: row.blocks.map((block) => ({ ...block })) }))
127
+ };
128
+ nextTrees.set(id, fresh);
129
+ return fresh;
130
+ };
131
+ const nodes = {};
132
+ for (const [key, override] of Object.entries(store.nodes)) {
133
+ const match = override.align !== void 0 && key.startsWith(AI_SLOT_KEY_PREFIX) ? key.match(/^ai\.(.+?)\.r(\d+)\.b(\d+)(?:\.|$)/) : null;
134
+ const tree = match ? treeFor(match[1]) : void 0;
135
+ const block = match && tree ? tree.rows[Number(match[2])]?.blocks[Number(match[3])] : void 0;
136
+ if (!block) {
137
+ nodes[key] = override;
138
+ continue;
139
+ }
140
+ block.align = override.align;
141
+ const rest = { ...override };
142
+ delete rest.align;
143
+ if (Object.keys(rest).length > 0) nodes[key] = rest;
144
+ }
145
+ const sections = {};
146
+ for (const [sectionId, override] of Object.entries(store.sections)) {
147
+ const tree = override.align !== void 0 ? treeFor(sectionId) : void 0;
148
+ if (!tree) {
149
+ sections[sectionId] = override;
150
+ continue;
151
+ }
152
+ for (const row of tree.rows) {
153
+ for (const block of row.blocks) block.align = override.align;
154
+ }
155
+ const rest = { ...override };
156
+ delete rest.align;
157
+ if (Object.keys(rest).length > 0) sections[sectionId] = rest;
158
+ }
159
+ if (nextTrees.size === 0) return { state, store, changed: false };
160
+ return {
161
+ state: {
162
+ ...state,
163
+ v: 1,
164
+ sections: state.sections.map((entry) => {
165
+ const tree = nextTrees.get(entry.id);
166
+ return tree ? { ...entry, tree } : entry;
167
+ })
168
+ },
169
+ store: { v: 1, sections, nodes },
170
+ changed: true
171
+ };
172
+ }
115
173
  function removeFromState(state, id) {
116
174
  return { ...state, v: 1, sections: state.sections.filter((entry) => entry.id !== id) };
117
175
  }
@@ -304,6 +362,9 @@ function styleSheetCss() {
304
362
  `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px}px !important; padding-bottom: ${px}px !important; }`
305
363
  );
306
364
  }
365
+ for (const align of ["left", "center", "right"]) {
366
+ rules.push(`[data-ohw-style-align="${align}"] { text-align: ${align} !important; }`);
367
+ }
307
368
  return rules.join("\n");
308
369
  }
309
370
  var STYLE_FONT_LINK_ID = "ohw-style-fonts";
@@ -330,13 +391,29 @@ var SECTION_ATTRS = {
330
391
  textDistribution: "data-ohw-style-distribution",
331
392
  headlineScale: "data-ohw-style-headline",
332
393
  imageAspect: "data-ohw-style-aspect",
333
- spacing: "data-ohw-style-spacing"
394
+ spacing: "data-ohw-style-spacing",
395
+ align: "data-ohw-style-align"
334
396
  };
335
397
  var NODE_WROTE_ATTR = "data-ohw-style-node";
336
- var NODE_PROPS = ["color", "font-family", "font-size", "background"];
398
+ var NODE_PROPS = [
399
+ "color",
400
+ "font-family",
401
+ "font-size",
402
+ "background",
403
+ "text-align",
404
+ "justify-content",
405
+ "align-items"
406
+ ];
407
+ var ALIGN_JUSTIFY = {
408
+ left: "flex-start",
409
+ center: "center",
410
+ right: "flex-end"
411
+ };
337
412
  function saveInline(el, prop) {
338
413
  const attr = `data-ohw-style-prev-${prop}`;
339
- if (!el.hasAttribute(attr)) el.setAttribute(attr, el.style.getPropertyValue(prop));
414
+ if (el.hasAttribute(attr)) return;
415
+ const value = el.style.getPropertyValue(prop) || (prop === "background" ? el.style.getPropertyValue("background-color") : "");
416
+ el.setAttribute(attr, value);
340
417
  }
341
418
  function restoreInline(el, prop) {
342
419
  const attr = `data-ohw-style-prev-${prop}`;
@@ -375,6 +452,13 @@ function clearNodeProps(root) {
375
452
  function buttonSurfaceOf(el) {
376
453
  return el.closest("a, button") ?? el;
377
454
  }
455
+ function alignSubjectOf(el) {
456
+ const button = el.closest('[data-ohw-role="button"]');
457
+ return button?.parentElement ?? el;
458
+ }
459
+ function horizontalFlexProp(el) {
460
+ return getComputedStyle(el).flexDirection.startsWith("column") ? "align-items" : "justify-content";
461
+ }
378
462
  function applyStylesToDom(store) {
379
463
  ensureStyleSheet();
380
464
  clearSectionAttrs(document);
@@ -387,7 +471,8 @@ function applyStylesToDom(store) {
387
471
  const sections = document.querySelectorAll(
388
472
  `[data-ohw-section="${CSS.escape(sectionId)}"]`
389
473
  );
390
- for (const section of Array.from(sections)) {
474
+ for (const marker of Array.from(sections)) {
475
+ const section = marker.querySelector(":scope > [data-ai-section]") ?? marker;
391
476
  for (const [prop, attr] of Object.entries(SECTION_ATTRS)) {
392
477
  const value = override[prop];
393
478
  if (value === void 0) continue;
@@ -419,6 +504,15 @@ function applyStylesToDom(store) {
419
504
  el.style.setProperty("font-size", `${override.fontSize}px`, "important");
420
505
  el.setAttribute(NODE_WROTE_ATTR, "");
421
506
  }
507
+ if (override.align !== void 0) {
508
+ const subject = alignSubjectOf(el);
509
+ const flexProp = horizontalFlexProp(subject);
510
+ saveInline(subject, "text-align");
511
+ saveInline(subject, flexProp);
512
+ subject.style.setProperty("text-align", override.align, "important");
513
+ subject.style.setProperty(flexProp, ALIGN_JUSTIFY[override.align] ?? "flex-start", "important");
514
+ subject.setAttribute(NODE_WROTE_ATTR, "");
515
+ }
422
516
  if (override.buttonBackground !== void 0 || override.buttonText !== void 0) {
423
517
  const surface = buttonSurfaceOf(el);
424
518
  if (override.buttonBackground !== void 0) {
@@ -469,8 +563,6 @@ var AI_MOBILE_CSS = [
469
563
  // Group containers flatten to a column on phones; span placements come along for free.
470
564
  "[data-ai-group]{display:flex !important;flex-direction:column !important}",
471
565
  "[data-ai-group] > *{grid-column:auto !important}",
472
- // The 50:50 form collapses to a single stacked column on phones.
473
- "[data-ai-form]{grid-template-columns:1fr !important}",
474
566
  "[data-ai-section] img{max-width:100%}",
475
567
  "}",
476
568
  "@media (min-width: 769px) and (max-width: 1024px){",
@@ -516,6 +608,22 @@ function accentBandContext(brand) {
516
608
  function textAttrs(ctx, path) {
517
609
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
518
610
  }
611
+ var AI_RESPONSIVE_CSS = [
612
+ "@media (max-width: 960px) {",
613
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
614
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
615
+ "}",
616
+ "@media (max-width: 640px) {",
617
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
618
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
619
+ // Group containers flatten to a column on phones; span placements come along for free.
620
+ " [data-ai-responsive] [data-ai-group] { display: flex !important; flex-direction: column !important; }",
621
+ " [data-ai-responsive] [data-ai-group] > * { grid-column: auto !important; }",
622
+ " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
623
+ " [data-ai-responsive] { overflow-x: hidden; }",
624
+ " [data-ai-responsive] img { max-width: 100%; }",
625
+ "}"
626
+ ].join("\n");
519
627
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
520
628
  function MediaBox({
521
629
  refValue,
@@ -528,13 +636,17 @@ function MediaBox({
528
636
  const url = refValue ? ctx.resolveMedia(refValue) : null;
529
637
  const isIcon = /^(lucide|simple):/.test(refValue);
530
638
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
531
- const editAttrs = ctx.keyFor && editPath && !isIcon ? { "data-ohw-key": ctx.keyFor(editPath), "data-ohw-editable": "image" } : {};
639
+ const editAttrs = ctx.keyFor && editPath ? {
640
+ "data-ohw-key": ctx.keyFor(editPath),
641
+ "data-ohw-editable": isIcon ? "icon" : "image"
642
+ } : {};
532
643
  if (isIcon) {
533
644
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
534
645
  return /* @__PURE__ */ jsx(
535
646
  "span",
536
647
  {
537
648
  "data-ai-icon": refValue,
649
+ ...editAttrs,
538
650
  style: {
539
651
  display: "inline-flex",
540
652
  width: 48,
@@ -629,7 +741,7 @@ function TextBlock({ slots, ctx, path }) {
629
741
  }
630
742
  function SectionHeaderBlock({ node, ctx, path }) {
631
743
  const slots = node.slots ?? {};
632
- const align = slots.alignment === "center" ? "center" : "left";
744
+ const align = node.align ?? (slots.alignment === "center" ? "center" : "left");
633
745
  const children = node.children ?? [];
634
746
  const buttonRowIdx = children.findIndex((c) => c.type === "button-row");
635
747
  const buttonRow = buttonRowIdx >= 0 ? children[buttonRowIdx] : void 0;
@@ -673,7 +785,7 @@ function SectionHeaderBlock({ node, ctx, path }) {
673
785
  display: "flex",
674
786
  gap: AI_TREE_TOKENS.spacing6,
675
787
  marginTop: AI_TREE_TOKENS.spacing8,
676
- justifyContent: align === "center" ? "center" : "flex-start"
788
+ justifyContent: align === "center" ? "center" : align === "right" ? "flex-end" : "flex-start"
677
789
  },
678
790
  children: (buttonRow.children ?? []).map((button, i) => /* @__PURE__ */ jsx(
679
791
  ButtonEl,
@@ -1006,7 +1118,7 @@ function CardBlock({ node, ctx, path }) {
1006
1118
  editPath: `${path}.media`
1007
1119
  }
1008
1120
  ) : null;
1009
- const centered = slots.alignment === "center";
1121
+ const centered = (node.align ?? slots.alignment) === "center";
1010
1122
  const content = /* @__PURE__ */ jsxs(
1011
1123
  "div",
1012
1124
  {
@@ -1420,7 +1532,7 @@ function CollectionBlock({ node, ctx, path }) {
1420
1532
  return /* @__PURE__ */ jsx(
1421
1533
  "div",
1422
1534
  {
1423
- "data-ai-grid": "",
1535
+ "data-ai-grid": String(itemsPerRow),
1424
1536
  style: {
1425
1537
  display: "grid",
1426
1538
  gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
@@ -1567,9 +1679,9 @@ function renderNode(node, ctx, path) {
1567
1679
  const fieldStyle = {
1568
1680
  width: "100%",
1569
1681
  boxSizing: "border-box",
1570
- border: `1px solid ${ctx.brand.palette.accent}`,
1571
- borderRadius: AI_TREE_TOKENS.radiusButton,
1572
- padding: "12px 14px",
1682
+ border: `1px solid color-mix(in srgb, ${ctx.brand.palette.dark} 45%, #ffffff)`,
1683
+ borderRadius: 0,
1684
+ padding: 12,
1573
1685
  background: "#fff",
1574
1686
  color: ctx.brand.palette.dark,
1575
1687
  outline: "none",
@@ -1577,81 +1689,90 @@ function renderNode(node, ctx, path) {
1577
1689
  };
1578
1690
  const labelStyle = {
1579
1691
  ...typeStyle(AI_TREE_TOKENS.type.bodyMBold, ctx.brand.fonts.body),
1580
- color: ctx.brand.palette.dark
1692
+ color: ctx.brand.palette.dark,
1693
+ textAlign: "left",
1694
+ width: "100%"
1581
1695
  };
1582
- return /* @__PURE__ */ jsx(
1583
- "form",
1584
- {
1585
- ...formAttrs,
1586
- "data-ai-form": "",
1587
- style: {
1588
- display: "grid",
1589
- gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
1590
- columnGap: 64,
1591
- rowGap: AI_TREE_TOKENS.spacing4
1592
- },
1593
- children: (node.children ?? []).map((child, i) => {
1594
- if (child.type === "input") {
1595
- const cs2 = child.slots ?? {};
1596
- const kind = str(cs2.kind);
1597
- const label = str(cs2.label);
1598
- const placeholder = str(cs2.placeholder);
1599
- const name = label.toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "") || `field-${i}`;
1600
- const isTextarea = kind === "textarea";
1601
- return /* @__PURE__ */ jsxs(
1602
- "div",
1603
- {
1604
- style: {
1605
- display: "flex",
1606
- flexDirection: "column",
1607
- gap: 8,
1608
- ...isTextarea ? { gridColumn: "1 / -1", maxWidth: 780 } : {}
1696
+ const centered = ctx.sectionAlignment === "center";
1697
+ const submitAlign = centered ? "center" : "flex-start";
1698
+ const children = node.children ?? [];
1699
+ return (
1700
+ // 32px between the field group and the submit. In a stacked (centered) section the form is
1701
+ // capped at 780px and centered — the section's 12-col grid would otherwise leave it hugging
1702
+ // the left edge; a split section lets it fill its own column.
1703
+ /* @__PURE__ */ jsxs(
1704
+ "form",
1705
+ {
1706
+ ...formAttrs,
1707
+ "data-ai-form": "",
1708
+ style: {
1709
+ display: "flex",
1710
+ flexDirection: "column",
1711
+ gap: 32,
1712
+ width: "100%",
1713
+ ...centered ? { maxWidth: 780, marginLeft: "auto", marginRight: "auto" } : {}
1714
+ },
1715
+ children: [
1716
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 24, width: "100%", alignItems: "flex-start" }, children: children.map((child, i) => {
1717
+ if (child.type !== "input") return null;
1718
+ const cs = child.slots ?? {};
1719
+ const kind = str(cs.kind);
1720
+ const label = str(cs.label);
1721
+ const placeholder = str(cs.placeholder);
1722
+ const required = cs.required === true;
1723
+ const name = label.toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "") || `field-${i}`;
1724
+ const isTextarea = kind === "textarea";
1725
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8, width: "100%" }, children: [
1726
+ /* @__PURE__ */ jsx("label", { ...textAttrs(ctx, `${path}.c${i}.label`), style: labelStyle, children: label }),
1727
+ isTextarea ? /* @__PURE__ */ jsx(
1728
+ "textarea",
1729
+ {
1730
+ name,
1731
+ placeholder,
1732
+ required,
1733
+ style: { ...fieldStyle, height: 180, resize: "vertical" }
1734
+ }
1735
+ ) : /* @__PURE__ */ jsx(
1736
+ "input",
1737
+ {
1738
+ name,
1739
+ type: kind === "email" ? "email" : "text",
1740
+ placeholder,
1741
+ required,
1742
+ style: { ...fieldStyle, height: 48 }
1743
+ }
1744
+ )
1745
+ ] }, i);
1746
+ }) }),
1747
+ children.map((child, i) => {
1748
+ if (child.type === "input") return null;
1749
+ const cs = child.slots ?? {};
1750
+ return /* @__PURE__ */ jsx(
1751
+ "button",
1752
+ {
1753
+ type: "submit",
1754
+ style: {
1755
+ alignSelf: submitAlign,
1756
+ border: "none",
1757
+ cursor: "pointer",
1758
+ padding: "12px 24px",
1759
+ // Corner radius follows the host template's own buttons (measured from a template
1760
+ // CTA); 8px only when the page has no template button to match.
1761
+ borderRadius: ctx.buttonRadius ?? 8,
1762
+ // Brand-styled: primary fill, brand-derived label colour (not a fixed token) so it
1763
+ // reads correctly on custom palettes.
1764
+ background: ctx.brand.palette.primary,
1765
+ color: ctx.buttonLabel ?? ctx.brand.palette.light,
1766
+ ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
1767
+ },
1768
+ children: /* @__PURE__ */ jsx("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
1609
1769
  },
1610
- children: [
1611
- /* @__PURE__ */ jsx("label", { ...textAttrs(ctx, `${path}.c${i}.label`), style: labelStyle, children: label }),
1612
- isTextarea ? /* @__PURE__ */ jsx(
1613
- "textarea",
1614
- {
1615
- name,
1616
- placeholder,
1617
- style: { ...fieldStyle, height: 140, resize: "vertical" }
1618
- }
1619
- ) : /* @__PURE__ */ jsx(
1620
- "input",
1621
- {
1622
- name,
1623
- type: kind === "email" ? "email" : "text",
1624
- placeholder,
1625
- style: { ...fieldStyle, height: 48 }
1626
- }
1627
- )
1628
- ]
1629
- },
1630
- i
1631
- );
1632
- }
1633
- const cs = child.slots ?? {};
1634
- return /* @__PURE__ */ jsx(
1635
- "button",
1636
- {
1637
- type: "submit",
1638
- style: {
1639
- gridColumn: "1 / -1",
1640
- justifySelf: "start",
1641
- border: "none",
1642
- cursor: "pointer",
1643
- padding: `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`,
1644
- borderRadius: AI_TREE_TOKENS.radiusButton,
1645
- background: ctx.brand.palette.primary,
1646
- color: AI_TREE_TOKENS.textPrimaryForeground,
1647
- ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
1648
- },
1649
- children: /* @__PURE__ */ jsx("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
1650
- },
1651
- i
1652
- );
1653
- })
1654
- }
1770
+ i
1771
+ );
1772
+ })
1773
+ ]
1774
+ }
1775
+ )
1655
1776
  );
1656
1777
  }
1657
1778
  case "schedule-widget":
@@ -1674,7 +1795,13 @@ function renderNode(node, ctx, path) {
1674
1795
  return null;
1675
1796
  }
1676
1797
  }
1677
- function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1798
+ function AiTreeRenderer({
1799
+ tree,
1800
+ brand,
1801
+ buttonRadius,
1802
+ resolveMedia,
1803
+ editKeyPrefix
1804
+ }) {
1678
1805
  if (!isRenderableTree(tree)) {
1679
1806
  return null;
1680
1807
  }
@@ -1686,6 +1813,8 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1686
1813
  resolveMedia: resolveMedia ?? (() => null),
1687
1814
  cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
1688
1815
  keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
1816
+ sectionAlignment: (tree.settings ?? {}).alignment === "center" ? "center" : "left",
1817
+ buttonRadius,
1689
1818
  ...band ? { buttonLabel: band.buttonLabel } : {}
1690
1819
  };
1691
1820
  const settings = tree.settings ?? {};
@@ -1708,11 +1837,25 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1708
1837
  }
1709
1838
  })();
1710
1839
  const distributed = !isOverlay && settings.textDistribution;
1840
+ const rowAlignItems = (rowAlign) => {
1841
+ if (rowAlign === "top") return "start";
1842
+ if (rowAlign === "bottom") return "end";
1843
+ if (rowAlign === "center" || rowAlign === "stretch") return rowAlign;
1844
+ if (distributed === "space-between") return "stretch";
1845
+ return (distributed ?? settings.verticalPosition) === "top" ? "start" : "center";
1846
+ };
1847
+ const cellAlignStyle = (blockAlign) => blockAlign ? {
1848
+ display: "flex",
1849
+ flexDirection: "column",
1850
+ alignItems: blockAlign === "left" ? "flex-start" : blockAlign === "right" ? "flex-end" : "center",
1851
+ textAlign: blockAlign
1852
+ } : {};
1711
1853
  return /* @__PURE__ */ jsxs(
1712
1854
  "section",
1713
1855
  {
1714
1856
  "data-ai-section": tree.tag ?? "",
1715
1857
  ...bgAttrs,
1858
+ "data-ai-responsive": "",
1716
1859
  style: {
1717
1860
  position: "relative",
1718
1861
  padding: `${pad}px 0`,
@@ -1723,12 +1866,13 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1723
1866
  color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
1724
1867
  },
1725
1868
  children: [
1726
- isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
1869
+ /* @__PURE__ */ jsx("style", { children: AI_RESPONSIVE_CSS }),
1727
1870
  /* @__PURE__ */ jsx("style", { children: AI_MOBILE_CSS }),
1871
+ isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
1728
1872
  /* @__PURE__ */ jsx(
1729
1873
  "div",
1730
1874
  {
1731
- "data-ai-container": "",
1875
+ "data-ai-section-inner": "",
1732
1876
  style: {
1733
1877
  position: "relative",
1734
1878
  maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
@@ -1739,12 +1883,12 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1739
1883
  children: tree.rows.map((row, r2) => /* @__PURE__ */ jsx(
1740
1884
  "div",
1741
1885
  {
1742
- "data-ai-row": "",
1886
+ "data-ai-columns": "",
1743
1887
  style: {
1744
1888
  display: "grid",
1745
1889
  gridTemplateColumns: "repeat(12, 1fr)",
1746
1890
  gap: AI_TREE_TOKENS.spacing6,
1747
- alignItems: distributed === "space-between" ? "stretch" : (distributed ?? settings.verticalPosition) === "top" ? "start" : "center",
1891
+ alignItems: rowAlignItems(row.align),
1748
1892
  marginTop: r2 > 0 ? AI_TREE_TOKENS.spacing8 : 0
1749
1893
  },
1750
1894
  children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ jsx(
@@ -1754,6 +1898,8 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1754
1898
  style: {
1755
1899
  gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`,
1756
1900
  minWidth: 0,
1901
+ // Horizontal placement of the block's content within its column.
1902
+ ...cellAlignStyle(block.align),
1757
1903
  // space-between: each column becomes a flex column whose content spreads over
1758
1904
  // the full row height instead of clumping at the top.
1759
1905
  ...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
@@ -1815,6 +1961,13 @@ function deriveTemplateBrand() {
1815
1961
  }
1816
1962
  };
1817
1963
  }
1964
+ function deriveTemplateButtonRadius() {
1965
+ if (typeof document === "undefined") return null;
1966
+ const btn = document.querySelector('[data-ohw-role="button"]');
1967
+ if (!btn) return null;
1968
+ const radius = getComputedStyle(btn).borderTopLeftRadius;
1969
+ return radius || null;
1970
+ }
1818
1971
  var mounted = /* @__PURE__ */ new Map();
1819
1972
  function findTemplateSection(id) {
1820
1973
  for (const el of document.querySelectorAll(`[data-ohw-section="${CSS.escape(id)}"]`)) {
@@ -1966,6 +2119,7 @@ function applyAiSectionsToDom(state, options) {
1966
2119
  if (typeof document === "undefined") return;
1967
2120
  const brandOverride = deriveBrandOverride();
1968
2121
  const templateBrand = deriveTemplateBrand();
2122
+ const templateButtonRadius = deriveTemplateButtonRadius();
1969
2123
  const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
1970
2124
  const pagePath = window.location.pathname;
1971
2125
  const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
@@ -2005,6 +2159,7 @@ function applyAiSectionsToDom(state, options) {
2005
2159
  {
2006
2160
  tree: entry.tree,
2007
2161
  brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
2162
+ buttonRadius: templateButtonRadius,
2008
2163
  resolveMedia,
2009
2164
  editKeyPrefix: `ai.${entry.id}`
2010
2165
  }
@@ -2153,6 +2308,7 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
2153
2308
  /* @__PURE__ */ jsx3(
2154
2309
  Dialog.Overlay,
2155
2310
  {
2311
+ "data-ohw-scheduling-modal": "",
2156
2312
  className: "fixed inset-0 z-50",
2157
2313
  style: { background: "rgba(0,0,0,0.45)" }
2158
2314
  }
@@ -2160,6 +2316,7 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
2160
2316
  /* @__PURE__ */ jsxs2(
2161
2317
  Dialog.Content,
2162
2318
  {
2319
+ "data-ohw-scheduling-modal": "",
2163
2320
  className: "fixed left-1/2 top-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 bg-white rounded-xl shadow-xl outline-none font-body box-border overflow-hidden",
2164
2321
  style: { maxWidth: 400 },
2165
2322
  children: [
@@ -2633,7 +2790,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
2633
2790
  const autoId = useId();
2634
2791
  const insertAfter = insertAfterProp ?? autoId;
2635
2792
  const [schedule, setSchedule] = useState2(null);
2636
- const [loading, setLoading] = useState2(true);
2793
+ const [loading, setLoading] = useState2(initialScheduleId !== null);
2637
2794
  const [inEditor, setInEditor] = useState2(false);
2638
2795
  const [isHovered, setIsHovered] = useState2(false);
2639
2796
  const [modalState, setModalState] = useState2(null);
@@ -2807,8 +2964,10 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
2807
2964
  "*"
2808
2965
  );
2809
2966
  };
2810
- if (!inEditor && !loading && !schedule) return null;
2811
2967
  const sectionId = `scheduling-${insertAfter}`;
2968
+ if (!inEditor && !loading && !schedule) {
2969
+ return /* @__PURE__ */ jsx4("section", { "data-ohw-section": sectionId, "data-ohw-scheduling-anchor": insertAfter, style: { display: "none" } });
2970
+ }
2812
2971
  return /* @__PURE__ */ jsxs3(
2813
2972
  "section",
2814
2973
  {
@@ -7749,6 +7908,7 @@ function MediaOverlay({
7749
7908
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
7750
7909
  );
7751
7910
  }, [isVideo]);
7911
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
7752
7912
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
7753
7913
  const box = {
7754
7914
  position: "fixed",
@@ -7878,17 +8038,17 @@ function MediaOverlay({
7878
8038
  },
7879
8039
  children: [
7880
8040
  isVideo ? /* @__PURE__ */ jsx15(Film, { size: 14 }) : /* @__PURE__ */ jsx15(ImageIcon, { size: 14 }),
7881
- isVideo ? "Replace video" : "Replace image"
8041
+ replaceLabel
7882
8042
  ]
7883
8043
  }
7884
8044
  ),
7885
- replaceMode === "none" ? null : /* @__PURE__ */ jsxs7(
8045
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ jsxs7(
7886
8046
  Button,
7887
8047
  {
7888
8048
  "data-ohw-media-overlay": "",
7889
8049
  variant: "outline",
7890
8050
  size: "sm",
7891
- "aria-label": isVideo ? "Replace video" : "Replace image",
8051
+ "aria-label": replaceLabel,
7892
8052
  className: "gap-1.5 cursor-pointer hover:bg-background",
7893
8053
  style: {
7894
8054
  ...OVERLAY_BUTTON_STYLE,
@@ -7911,7 +8071,7 @@ function MediaOverlay({
7911
8071
  },
7912
8072
  children: [
7913
8073
  isVideo ? /* @__PURE__ */ jsx15(Film, { size: 14 }) : /* @__PURE__ */ jsx15(ImageIcon, { size: 14 }),
7914
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
8074
+ replaceMode === "full" ? replaceLabel : null
7915
8075
  ]
7916
8076
  }
7917
8077
  )
@@ -12855,6 +13015,7 @@ function readLogoSizeState(content, placement) {
12855
13015
  function getLogoElement(el) {
12856
13016
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
12857
13017
  if (marked) return marked;
13018
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
12858
13019
  const root = el.closest("nav, [data-ohw-nav-root], footer");
12859
13020
  if (!root) return null;
12860
13021
  const anchor = el.closest("a");
@@ -14789,21 +14950,10 @@ function parseSchedulingInsertAfter(insertAfter) {
14789
14950
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
14790
14951
  };
14791
14952
  }
14792
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
14793
- const parsed = parseSchedulingInsertAfter(insertAfter);
14794
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
14795
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
14796
- return { effectiveInsertAfter, insertBefore };
14797
- }
14798
- function getSchedulingMountPoint(insertAfter) {
14799
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
14800
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
14801
- if (!anchorEl && anchor === "scheduling") {
14802
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
14803
- anchorEl = widgets.at(-1) ?? null;
14804
- }
14805
- if (!anchorEl) return null;
14806
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
14953
+ function resolveEntryAnchor(entry) {
14954
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
14955
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
14956
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
14807
14957
  }
14808
14958
  function schedulingMountDepth(insertAfter) {
14809
14959
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -14820,8 +14970,7 @@ function getPageSchedulingEntries(raw) {
14820
14970
  }
14821
14971
  }
14822
14972
  function isSchedulingWidgetMissing(entry) {
14823
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
14824
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
14973
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
14825
14974
  }
14826
14975
  function hasMissingSchedulingWidgets(entries) {
14827
14976
  return entries.some(isSchedulingWidgetMissing);
@@ -14851,16 +15000,17 @@ function initSectionsFromContent(content, removeExisting = false) {
14851
15000
  } catch {
14852
15001
  }
14853
15002
  }
14854
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
14855
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
14856
- const sectionId = schedulingSectionId(effectiveInsertAfter);
15003
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
15004
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
15005
+ const sectionId = schedulingSectionId(widgetId);
14857
15006
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
14858
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
14859
- if (!mountPoint) return false;
15007
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
15008
+ if (!anchorEl) return false;
15009
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
14860
15010
  const container = document.createElement("div");
14861
15011
  container.dataset.ohwSectionContainer = "scheduling";
14862
- if (insertBefore) {
14863
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
15012
+ if (beforeId) {
15013
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
14864
15014
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
14865
15015
  if (!beforePoint) return false;
14866
15016
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -14871,19 +15021,25 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
14871
15021
  }
14872
15022
  tail.insertAdjacentElement("afterend", container);
14873
15023
  }
14874
- const root = createRoot2(container);
14875
- flushSync2(() => {
14876
- root.render(
14877
- /* @__PURE__ */ jsx33(
14878
- SchedulingWidget,
14879
- {
14880
- notifyOnConnect,
14881
- initialScheduleId: scheduleId,
14882
- insertAfter: effectiveInsertAfter
14883
- }
14884
- )
14885
- );
14886
- });
15024
+ try {
15025
+ const root = createRoot2(container);
15026
+ flushSync2(() => {
15027
+ root.render(
15028
+ /* @__PURE__ */ jsx33(
15029
+ SchedulingWidget,
15030
+ {
15031
+ notifyOnConnect,
15032
+ initialScheduleId: scheduleId,
15033
+ insertAfter: widgetId
15034
+ }
15035
+ )
15036
+ );
15037
+ });
15038
+ } catch (err) {
15039
+ console.error("[ow:scheduling] render threw", err);
15040
+ container.remove();
15041
+ return false;
15042
+ }
14887
15043
  const tracker = getSectionsTracker();
14888
15044
  let sections = [];
14889
15045
  try {
@@ -14891,10 +15047,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
14891
15047
  } catch {
14892
15048
  }
14893
15049
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
14894
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
15050
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
14895
15051
  sections.push({
14896
15052
  type: "scheduling",
14897
- insertAfter: effectiveInsertAfter,
15053
+ insertAfter: widgetId,
15054
+ anchorId,
15055
+ beforeId: beforeId ?? null,
14898
15056
  pagePath: window.location.pathname,
14899
15057
  ...scheduleId ? { scheduleId } : {}
14900
15058
  });
@@ -14908,7 +15066,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
14908
15066
  for (let i = pending.length - 1; i >= 0; i--) {
14909
15067
  const entry = pending[i];
14910
15068
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
14911
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId)) {
15069
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
15070
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
14912
15071
  pending.splice(i, 1);
14913
15072
  }
14914
15073
  }
@@ -15066,6 +15225,11 @@ function applyLinkByKey(key, val) {
15066
15225
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
15067
15226
  }
15068
15227
  }
15228
+ function isInsideLinkEditor(target) {
15229
+ return Boolean(
15230
+ target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest("[data-ohw-link-page-dropdown]") || target.closest("[data-ohw-section-picker]") || target.closest("[data-ohw-navbar-container-chrome]") || target.closest("[data-ohw-navbar-add-button]") || target.closest("[data-ohw-footer-container-chrome]") || target.closest("[data-ohw-footer-add-button]") || target.closest("[data-ohw-more-menu]") || target.closest('[data-slot="dropdown-menu-content"]') || target.closest('[data-slot="popover-content"]') || target.closest('[data-slot="dialog-content"]') || target.closest('[data-slot="dialog-overlay"]')
15231
+ );
15232
+ }
15069
15233
  function isInsideFloatingPanel(target) {
15070
15234
  return Boolean(target.closest("[data-ohw-floating-panel]"));
15071
15235
  }
@@ -15073,11 +15237,6 @@ function isPointOverFloatingPanel(clientX, clientY) {
15073
15237
  const el = document.elementFromPoint(clientX, clientY);
15074
15238
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
15075
15239
  }
15076
- function isInsideLinkEditor(target) {
15077
- return Boolean(
15078
- target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest("[data-ohw-link-page-dropdown]") || target.closest("[data-ohw-section-picker]") || target.closest("[data-ohw-navbar-container-chrome]") || target.closest("[data-ohw-navbar-add-button]") || target.closest("[data-ohw-footer-container-chrome]") || target.closest("[data-ohw-footer-add-button]") || target.closest("[data-ohw-more-menu]") || target.closest('[data-slot="dropdown-menu-content"]') || target.closest('[data-slot="popover-content"]') || target.closest('[data-slot="dialog-content"]') || target.closest('[data-slot="dialog-overlay"]')
15079
- );
15080
- }
15081
15240
  function getHrefKeyFromElement(el) {
15082
15241
  if (!el) return null;
15083
15242
  const anchor = el.closest("[data-ohw-href-key]");
@@ -15336,7 +15495,7 @@ function getNavigationSelectionParent(el) {
15336
15495
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
15337
15496
  return getFooterLinksContainer();
15338
15497
  }
15339
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
15498
+ if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isFooterLinksContainer(el) || isInferredFooterGroup2(el)) {
15340
15499
  return getNavigationRoot(el);
15341
15500
  }
15342
15501
  return null;
@@ -15551,7 +15710,6 @@ var ICONS = {
15551
15710
  insertUnorderedList: '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>',
15552
15711
  insertOrderedList: '<line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/>'
15553
15712
  };
15554
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
15555
15713
  var SELECTION_CHROME_GAP2 = 4;
15556
15714
  var TOOLBAR_STROKE_GAP2 = 4;
15557
15715
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -15931,6 +16089,8 @@ function StateToggle({
15931
16089
  );
15932
16090
  }
15933
16091
  var contentCache = /* @__PURE__ */ new Map();
16092
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
16093
+ var brandingCache = /* @__PURE__ */ new Map();
15934
16094
  var OHW_LOADER_STYLE = {
15935
16095
  position: "fixed",
15936
16096
  inset: 0,
@@ -15968,6 +16128,89 @@ function OhwLoaderSpinner() {
15968
16128
  )
15969
16129
  ] });
15970
16130
  }
16131
+ function OhwBrandMark() {
16132
+ return /* @__PURE__ */ jsxs20(
16133
+ "svg",
16134
+ {
16135
+ width: "16",
16136
+ height: "16",
16137
+ viewBox: "0 0 48 48",
16138
+ fill: "none",
16139
+ "aria-hidden": true,
16140
+ style: { display: "block", flexShrink: 0 },
16141
+ xmlns: "http://www.w3.org/2000/svg",
16142
+ children: [
16143
+ /* @__PURE__ */ jsx33(
16144
+ "mask",
16145
+ {
16146
+ id: "ohw-badge-mark",
16147
+ style: { maskType: "luminance" },
16148
+ maskUnits: "userSpaceOnUse",
16149
+ x: "0",
16150
+ y: "0",
16151
+ width: "48",
16152
+ height: "48",
16153
+ children: /* @__PURE__ */ jsx33("path", { d: "M23.8741 48C37.0594 48 47.7481 37.2548 47.7481 24C47.7481 10.7452 37.0594 0 23.8741 0C10.6888 0 0 10.7452 0 24C0 37.2548 10.6888 48 23.8741 48Z", fill: "white" })
16154
+ }
16155
+ ),
16156
+ /* @__PURE__ */ jsxs20("g", { mask: "url(#ohw-badge-mark)", children: [
16157
+ /* @__PURE__ */ jsx33("path", { d: "M23.8731 48.0497C37.0584 48.0497 47.7472 37.3046 47.7472 24.0497C47.7472 10.7949 37.0584 0.0497208 23.8731 0.0497208C10.6878 0.0497208 -0.000976562 10.7949 -0.000976562 24.0497C-0.000976562 37.3046 10.6878 48.0497 23.8731 48.0497Z", fill: "#0078E5" }),
16158
+ /* @__PURE__ */ jsx33("path", { d: "M17.1307 14.7172C13.1687 14.7172 9.38102 18.1154 8.65114 22.34C8.38885 23.8488 8.5598 25.2581 9.06929 26.4451C6.20005 29.1677 1.77216 27.8721 -1.40212 26.1536C-2.73618 25.4317 -3.92695 27.4745 -2.59037 28.1981C1.33389 30.3226 6.86037 31.6621 10.4402 28.4188C11.4718 29.3859 12.867 29.9621 14.4894 29.9621C18.4161 29.9621 22.2038 26.5318 22.9337 22.34C23.6636 18.1162 21.0566 14.7172 17.1298 14.7172H17.1307ZM19.9798 22.34C19.5281 25.0399 17.2689 27.231 14.9754 27.231C12.6466 27.231 11.1877 25.0399 11.6394 22.34C12.1262 19.6401 14.3151 17.4482 16.6438 17.4482C18.9374 17.4482 20.4667 19.6401 19.9798 22.34Z", fill: "white" }),
16159
+ /* @__PURE__ */ jsx33("path", { d: "M40.0017 27.0262C39.1797 27.081 38.2721 26.995 37.4668 26.7415C37.3344 26.6993 37.28 26.5401 37.3529 26.4205C37.5959 26.0212 37.8255 25.6152 38.009 25.1889C38.1071 24.9918 38.2018 24.793 38.2897 24.5908C38.3274 24.5041 38.4163 24.451 38.5101 24.4619C38.63 24.4754 38.7054 24.4821 38.8881 24.4821L39.1529 24.4796L39.8283 24.4543C45.9229 24.0492 50.4765 20.4319 54.8466 16.9014C56.0172 15.9554 57.6932 17.6208 56.5116 18.5752C51.7687 22.4065 47.1966 26.5081 40.9319 26.9689", fill: "white" }),
16160
+ /* @__PURE__ */ jsx33("path", { d: "M37.9687 24.27C38.4472 23.1319 38.7656 21.9045 38.9609 20.6991C39.5927 16.76 38.5058 14.2193 36.1553 14.2193C34.1835 14.2193 33.1469 17.2427 32.9199 18.8694C32.743 19.9872 32.5914 22.1219 33.6028 23.9524C33.7553 24.259 34.15 24.7712 34.471 25.1259C34.5447 25.2067 34.6746 25.2 34.7349 25.1082C34.9528 24.7788 35.1615 24.4039 35.3584 24.0257C35.5444 23.6677 35.5888 23.6138 35.8587 23.0207C35.8838 22.966 35.8813 22.9002 35.8478 22.8505C35.5888 22.4597 35.2168 21.9787 35.1204 21.4614C34.9184 20.4455 34.9436 19.2257 35.2218 18.1078C35.4413 17.3118 35.7195 16.7844 35.9039 16.5106C35.9466 16.4466 36.0279 16.4129 36.0975 16.4449C36.369 16.5671 36.5827 16.8838 36.7385 17.396C37.0167 18.2089 37.0167 19.3782 36.814 20.6999C36.6991 21.5271 36.4771 22.3729 36.1746 23.1673C36.1293 23.308 36.0757 23.4461 36.0187 23.5826C36.0187 23.5868 36.0187 23.591 36.0187 23.5961C35.9911 23.6946 35.9207 23.8067 35.8846 23.901C35.5536 24.5497 35.2344 25.1697 34.8439 25.7838C34.8388 25.7863 34.8346 25.7914 34.8296 25.7931C34.6528 26.0525 34.4718 26.2901 34.2866 26.4965C34.2774 26.5099 34.2682 26.5234 34.2589 26.5369C34.2405 26.5638 34.2179 26.5815 34.1944 26.595C33.5064 27.3212 32.7665 27.6893 32.0123 27.6893C31.8606 27.6893 31.6838 27.664 31.507 27.4349C31.3042 27.1299 30.85 26.0879 31.2539 22.9112C31.4785 21.3949 31.8011 20.0268 31.931 19.5408C31.9579 19.4413 31.8908 19.3419 31.7886 19.3293L30.0062 19.1162C29.9241 19.1061 29.8478 19.1566 29.8252 19.2366C29.2704 21.1615 27.0305 27.6885 24.9599 27.6885C24.328 27.6885 24.1512 26.6211 24.1001 26.2909C23.775 23.6264 25.528 18.5492 29.5302 16.2267C29.6048 16.1838 29.635 16.0928 29.5998 16.0136L28.9042 14.467C28.8632 14.3752 28.7501 14.3389 28.6638 14.3886C25.8079 16.0414 24.1847 18.5231 23.2915 20.3436C22.2046 22.5793 21.6993 25.0189 21.9515 26.8738C22.1795 28.7794 23.1398 29.872 24.6054 29.872C26.0543 29.872 27.4369 28.9066 28.7098 27.0187C28.7953 26.8915 28.9889 26.9311 29.0149 27.0819C29.2646 28.5283 29.9811 29.872 31.6579 29.872C33.5282 29.872 35.3232 28.7288 36.7134 26.6447C36.7712 26.5874 36.7972 26.5411 36.8181 26.4906C36.8232 26.4931 36.8282 26.4948 36.8332 26.4973C37.01 26.2025 37.181 25.9051 37.3444 25.6027C37.4508 25.4005 37.5572 25.1992 37.6586 24.9945C37.7181 24.874 37.7743 24.7527 37.8262 24.6289C37.838 24.6002 37.8547 24.5665 37.8706 24.5337", fill: "white" }),
16161
+ /* @__PURE__ */ jsx33("path", { d: "M30.5839 31.6397C25.7546 34.8577 19.4773 34.7853 14.6907 31.5243C13.5368 30.7384 12.5044 32.6498 13.6474 33.4281C19.034 37.0985 26.3077 37.096 31.7218 33.488C32.8791 32.7172 31.7478 30.8639 30.5839 31.6397Z", fill: "white" })
16162
+ ] })
16163
+ ]
16164
+ }
16165
+ );
16166
+ }
16167
+ var OHW_BADGE_STYLE = {
16168
+ position: "fixed",
16169
+ left: 20,
16170
+ bottom: 20,
16171
+ zIndex: 2147483e3,
16172
+ boxSizing: "border-box",
16173
+ display: "inline-flex",
16174
+ alignItems: "center",
16175
+ gap: 0,
16176
+ padding: "6px 8px",
16177
+ margin: 0,
16178
+ background: "#ffffff",
16179
+ border: "1px solid #e7e5e4",
16180
+ borderRadius: 9999,
16181
+ boxShadow: "0 1px 3px rgba(0, 0, 0, 0.1)",
16182
+ color: "#0c0a09",
16183
+ textDecoration: "none",
16184
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
16185
+ };
16186
+ var OHW_BADGE_LABEL_STYLE = {
16187
+ padding: "0 4px",
16188
+ fontSize: 14,
16189
+ lineHeight: "24px",
16190
+ fontWeight: 500,
16191
+ fontStyle: "normal",
16192
+ letterSpacing: "normal",
16193
+ textTransform: "none",
16194
+ color: "#0c0a09",
16195
+ whiteSpace: "nowrap"
16196
+ };
16197
+ function MadeWithOhhWells() {
16198
+ return /* @__PURE__ */ jsxs20(
16199
+ "a",
16200
+ {
16201
+ href: "https://ohhwells.com",
16202
+ target: "_blank",
16203
+ rel: "noopener noreferrer",
16204
+ "aria-label": "Made with OhhWells",
16205
+ "data-ohw-badge": "",
16206
+ style: OHW_BADGE_STYLE,
16207
+ children: [
16208
+ /* @__PURE__ */ jsx33(OhwBrandMark, {}),
16209
+ /* @__PURE__ */ jsx33("span", { style: OHW_BADGE_LABEL_STYLE, children: "Made with OhhWells" })
16210
+ ]
16211
+ }
16212
+ );
16213
+ }
15971
16214
  var OHW_LOADER_PREHYDRATE_SCRIPT = `(function(){try{var p=location.hostname.split(".");var fromHost=p.length>=3&&p[0]!=="www"?p[0]:"";var fromQuery=new URLSearchParams(location.search).get("subdomain")||"";if(!fromHost&&!fromQuery)return;var e=document.getElementById("ohw-loader");if(e)e.style.display="flex"}catch(e){}})();`;
15972
16215
  function resolveSubdomain(subdomainFromQuery) {
15973
16216
  if (subdomainFromQuery) return subdomainFromQuery;
@@ -16027,6 +16270,7 @@ function OhhwellsBridge() {
16027
16270
  }
16028
16271
  }, []);
16029
16272
  const [fetchState, setFetchState] = useState13("idle");
16273
+ const [showBranding, setShowBranding] = useState13(false);
16030
16274
  const autoSaveTimers = useRef10(/* @__PURE__ */ new Map());
16031
16275
  const activeElRef = useRef10(null);
16032
16276
  const pointerHeldRef = useRef10(false);
@@ -16375,13 +16619,6 @@ function OhhwellsBridge() {
16375
16619
  const [isItemDragging, setIsItemDragging] = useState13(false);
16376
16620
  const [isFooterFrameSelection, setIsFooterFrameSelection] = useState13(false);
16377
16621
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
16378
- const [floatingPanel, setFloatingPanel] = useState13(null);
16379
- const floatingPanelOpenRef = useRef10(false);
16380
- floatingPanelOpenRef.current = floatingPanel !== null;
16381
- const [floatingPanelPos, setFloatingPanelPos] = useState13(null);
16382
- const [logoSizeDraft, setLogoSizeDraft] = useState13(null);
16383
- const [editorViewport, setEditorViewport] = useState13("desktop");
16384
- const [parentScrollSnap, setParentScrollSnap] = useState13(null);
16385
16622
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = useState13(null);
16386
16623
  const [footerHeadingVisible, setFooterHeadingVisible] = useState13(null);
16387
16624
  const footerDragRef = useRef10(null);
@@ -16399,6 +16636,13 @@ function OhhwellsBridge() {
16399
16636
  const brandKitRef = useRef10("");
16400
16637
  const stylesRef = useRef10("");
16401
16638
  const pendingDeleteUndoRef = useRef10(null);
16639
+ const [floatingPanel, setFloatingPanel] = useState13(null);
16640
+ const floatingPanelOpenRef = useRef10(false);
16641
+ const setFloatingPanelRef = useRef10(setFloatingPanel);
16642
+ const [floatingPanelPos, setFloatingPanelPos] = useState13(null);
16643
+ const [logoSizeDraft, setLogoSizeDraft] = useState13(null);
16644
+ const [editorViewport, setEditorViewport] = useState13("desktop");
16645
+ const [parentScrollSnap, setParentScrollSnap] = useState13(null);
16402
16646
  const [sitePages, setSitePages] = useState13([]);
16403
16647
  const [sectionsByPath, setSectionsByPath] = useState13({});
16404
16648
  const sectionsPrefetchGenRef = useRef10(0);
@@ -16407,7 +16651,18 @@ function OhhwellsBridge() {
16407
16651
  const linkPopoverOpenRef = useRef10(false);
16408
16652
  const linkPopoverGraceUntilRef = useRef10(0);
16409
16653
  setLinkPopoverRef.current = setLinkPopover;
16654
+ setFloatingPanelRef.current = setFloatingPanel;
16410
16655
  linkPopoverSessionRef.current = linkPopover;
16656
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
16657
+ useEffect13(() => {
16658
+ const syncViewport = () => {
16659
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
16660
+ setEditorViewport((prev) => prev === next ? prev : next);
16661
+ };
16662
+ syncViewport();
16663
+ window.addEventListener("resize", syncViewport);
16664
+ return () => window.removeEventListener("resize", syncViewport);
16665
+ }, []);
16411
16666
  const {
16412
16667
  navDragRef,
16413
16668
  navDropSlots,
@@ -17738,11 +17993,11 @@ function OhhwellsBridge() {
17738
17993
  for (const [key, val] of Object.entries(content)) {
17739
17994
  if (key === "__ohw_sections") continue;
17740
17995
  if (key === AI_SECTIONS_KEY) continue;
17996
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
17997
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
17741
17998
  if (key === BRAND_KIT_KEY) continue;
17742
17999
  if (key === STYLE_STORE_KEY) continue;
17743
18000
  if (BRAND_CHROME_KEYS.has(key)) continue;
17744
- if (key === LOGO_PLACEHOLDER_KEY) continue;
17745
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
17746
18001
  if (applyVideoSettingNode(key, val)) continue;
17747
18002
  if (applyCarouselNode(key, val)) continue;
17748
18003
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -17802,16 +18057,22 @@ function OhhwellsBridge() {
17802
18057
  };
17803
18058
  const cached = contentCache.get(subdomain);
17804
18059
  if (cached) {
18060
+ setShowBranding(brandingCache.get(subdomain) ?? false);
17805
18061
  applyContent(cached).finally(() => setFetchState("done"));
17806
18062
  return;
17807
18063
  }
17808
18064
  let cancelled = false;
17809
18065
  setFetchState("loading");
17810
18066
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
17811
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18067
+ const initialPath = pathname;
18068
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
18069
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
17812
18070
  if (cancelled) return;
17813
18071
  const content = data?.content ?? {};
18072
+ const branding = Boolean(data?.showBranding);
17814
18073
  contentCache.set(subdomain, content);
18074
+ brandingCache.set(subdomain, branding);
18075
+ setShowBranding(branding);
17815
18076
  return applyContent(content);
17816
18077
  }).catch(() => {
17817
18078
  }).finally(() => {
@@ -17944,11 +18205,12 @@ function OhhwellsBridge() {
17944
18205
  for (const [key, val] of Object.entries(content)) {
17945
18206
  if (key === "__ohw_sections") continue;
17946
18207
  if (key === AI_SECTIONS_KEY) continue;
18208
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
18209
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
17947
18210
  if (key === BRAND_KIT_KEY) continue;
17948
18211
  if (key === STYLE_STORE_KEY) continue;
18212
+ if (key === STYLE_STORE_KEY) continue;
17949
18213
  if (BRAND_CHROME_KEYS.has(key)) continue;
17950
- if (key === LOGO_PLACEHOLDER_KEY) continue;
17951
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
17952
18214
  if (applyVideoSettingNode(key, val)) continue;
17953
18215
  if (applyCarouselNode(key, val)) continue;
17954
18216
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -17994,6 +18256,17 @@ function OhhwellsBridge() {
17994
18256
  debounceTimer = setTimeout(applyFromCache, 150);
17995
18257
  };
17996
18258
  applyFromCache();
18259
+ const pathCacheKey = `${subdomain}::${pathname}`;
18260
+ if (!fetchedContentPaths.has(pathCacheKey)) {
18261
+ fetchedContentPaths.add(pathCacheKey);
18262
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
18263
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18264
+ if (!data?.content) return;
18265
+ contentCache.set(subdomain, data.content);
18266
+ applyFromCache();
18267
+ }).catch(() => {
18268
+ });
18269
+ }
17997
18270
  observer = new MutationObserver(scheduleApply);
17998
18271
  observer.observe(document.body, { childList: true, subtree: true });
17999
18272
  return () => {
@@ -18107,25 +18380,13 @@ function OhhwellsBridge() {
18107
18380
  };
18108
18381
  const t1 = setTimeout(measure, 50);
18109
18382
  const t2 = setTimeout(measure, 500);
18110
- let lastWidth = window.innerWidth;
18111
- let resizeTimers = [];
18112
- const clearResizeTimers = () => {
18113
- resizeTimers.forEach(clearTimeout);
18114
- resizeTimers = [];
18115
- };
18116
- const handleResize = () => {
18117
- if (window.innerWidth === lastWidth) return;
18118
- lastWidth = window.innerWidth;
18119
- clearResizeTimers();
18120
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
18121
- };
18122
- window.addEventListener("resize", handleResize);
18383
+ const ro = new ResizeObserver(schedule);
18384
+ ro.observe(document.body);
18123
18385
  return () => {
18124
18386
  clearTimeout(t1);
18125
18387
  clearTimeout(t2);
18126
18388
  if (raf != null) cancelAnimationFrame(raf);
18127
- clearResizeTimers();
18128
- window.removeEventListener("resize", handleResize);
18389
+ ro.disconnect();
18129
18390
  };
18130
18391
  }, [pathname, isEditMode, postToParent2]);
18131
18392
  useEffect13(() => {
@@ -18371,9 +18632,6 @@ function OhhwellsBridge() {
18371
18632
  if (target.closest("[data-ohw-state-toggle]")) return;
18372
18633
  if (target.closest("[data-ohw-max-badge]")) return;
18373
18634
  if (isInsideLinkEditor(target)) return;
18374
- if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
18375
- clearMediaSelectionRef.current();
18376
- }
18377
18635
  if (isInsideFloatingPanel(target)) return;
18378
18636
  if (target.closest("[data-ohw-form-toolbar]")) return;
18379
18637
  if (target.closest(
@@ -18381,6 +18639,9 @@ function OhhwellsBridge() {
18381
18639
  )) {
18382
18640
  return;
18383
18641
  }
18642
+ if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
18643
+ clearMediaSelectionRef.current();
18644
+ }
18384
18645
  {
18385
18646
  const formEl = getFormElement(target);
18386
18647
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -18532,14 +18793,6 @@ function OhhwellsBridge() {
18532
18793
  }
18533
18794
  const clickedButton = findClosestButtonLike(target);
18534
18795
  const buttonOnMedia = Boolean(clickedButton && isMediaEditable(editable) && editable.contains(clickedButton));
18535
- console.log("[click-debug]", {
18536
- editableType: editable.dataset.ohwEditable,
18537
- editableTag: editable.tagName,
18538
- targetTag: target.tagName,
18539
- clickedButtonTag: clickedButton?.tagName ?? null,
18540
- buttonOnMedia,
18541
- isMediaEditableEditable: isMediaEditable(editable)
18542
- });
18543
18796
  if (isMediaEditable(editable) && !buttonOnMedia) {
18544
18797
  e.preventDefault();
18545
18798
  e.stopPropagation();
@@ -18566,11 +18819,6 @@ function OhhwellsBridge() {
18566
18819
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
18567
18820
  const hrefCtx = getHrefKeyFromElement(hrefLookupTarget);
18568
18821
  const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
18569
- console.log("[click-debug 2]", {
18570
- hrefLookupTargetTag: hrefLookupTarget.tagName,
18571
- hrefCtx: hrefCtx ? { key: hrefCtx.key } : null,
18572
- navAnchorTag: navAnchor?.tagName ?? null
18573
- });
18574
18822
  if (navAnchor) {
18575
18823
  e.preventDefault();
18576
18824
  e.stopPropagation();
@@ -18740,6 +18988,9 @@ function OhhwellsBridge() {
18740
18988
  setHoveredItemRect(null);
18741
18989
  hoveredNavContainerRef.current = null;
18742
18990
  setHoveredNavContainerRect(null);
18991
+ siblingHintElRef.current = null;
18992
+ setSiblingHintRect(null);
18993
+ setSiblingHintRects([]);
18743
18994
  return;
18744
18995
  }
18745
18996
  {
@@ -18858,7 +19109,6 @@ function OhhwellsBridge() {
18858
19109
  hoveredNavContainerRef.current = null;
18859
19110
  setHoveredNavContainerRect(null);
18860
19111
  hoveredItemElRef.current = editable;
18861
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
18862
19112
  }
18863
19113
  }
18864
19114
  }
@@ -19155,7 +19405,7 @@ function OhhwellsBridge() {
19155
19405
  }
19156
19406
  };
19157
19407
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
19158
- if (linkPopoverOpenRef.current) {
19408
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
19159
19409
  if (hoveredImageRef.current) {
19160
19410
  hoveredImageRef.current = null;
19161
19411
  hoveredImageHasTextOverlapRef.current = false;
@@ -19520,8 +19770,7 @@ function OhhwellsBridge() {
19520
19770
  };
19521
19771
  const handleMouseMove = (e) => {
19522
19772
  const { clientX, clientY } = e;
19523
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
19524
- if (isOverEditorChrome(clientX, clientY)) {
19773
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
19525
19774
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
19526
19775
  formHoverElRef.current = null;
19527
19776
  setFormHoverRect(null);
@@ -19529,6 +19778,12 @@ function OhhwellsBridge() {
19529
19778
  setHoveredItemRect(null);
19530
19779
  hoveredNavContainerRef.current = null;
19531
19780
  setHoveredNavContainerRect(null);
19781
+ siblingHintElRef.current = null;
19782
+ setSiblingHintRect(null);
19783
+ setSiblingHintRects([]);
19784
+ dismissImageHover();
19785
+ clearImageHover();
19786
+ setSectionGap(null);
19532
19787
  return;
19533
19788
  }
19534
19789
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -19540,7 +19795,11 @@ function OhhwellsBridge() {
19540
19795
  if (e.data?.type !== "ow:pointer-sync") return;
19541
19796
  const { clientX, clientY } = e.data;
19542
19797
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
19543
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
19798
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
19799
+ dismissImageHover();
19800
+ clearImageHover();
19801
+ return;
19802
+ }
19544
19803
  if (probeSocialsRowAt(clientX, clientY)) return;
19545
19804
  probeSectionGapAt(clientX, clientY);
19546
19805
  probeImageAt(clientX, clientY);
@@ -19847,11 +20106,11 @@ function OhhwellsBridge() {
19847
20106
  continue;
19848
20107
  }
19849
20108
  if (key === AI_SECTIONS_KEY) continue;
20109
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
20110
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
19850
20111
  if (key === BRAND_KIT_KEY) continue;
19851
20112
  if (key === STYLE_STORE_KEY) continue;
19852
20113
  if (BRAND_CHROME_KEYS.has(key)) continue;
19853
- if (key === LOGO_PLACEHOLDER_KEY) continue;
19854
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
19855
20114
  if (applyVideoSettingNode(key, val)) continue;
19856
20115
  if (applyCarouselNode(key, val)) continue;
19857
20116
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -20012,6 +20271,20 @@ function OhhwellsBridge() {
20012
20271
  postAiSectionsChanged();
20013
20272
  };
20014
20273
  window.addEventListener("message", handleAiSetSections);
20274
+ const handleMoveSection = (e) => {
20275
+ if (e.data?.type !== "ow:move-section") return;
20276
+ const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
20277
+ const direction = e.data.direction === "up" || e.data.direction === "down" ? e.data.direction : null;
20278
+ if (!instanceId || !direction) return;
20279
+ const entries = moveSectionInstance(instanceId, direction, window.location.pathname);
20280
+ if (!entries) return;
20281
+ const orderJson = JSON.stringify(entries);
20282
+ editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
20283
+ setAiSectionOrder(orderJson, window.location.pathname);
20284
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20285
+ window.dispatchEvent(new Event("resize"));
20286
+ };
20287
+ window.addEventListener("message", handleMoveSection);
20015
20288
  const handleAiSetBrand = (e) => {
20016
20289
  if (e.data?.type !== "ow:ai-set-brand") return;
20017
20290
  const value = typeof e.data.value === "string" ? e.data.value : "";
@@ -20026,12 +20299,35 @@ function OhhwellsBridge() {
20026
20299
  window.addEventListener("message", handleAiSetBrand);
20027
20300
  const handleAiSetStyles = (e) => {
20028
20301
  if (e.data?.type !== "ow:ai-set-styles") return;
20029
- const value = typeof e.data.value === "string" ? e.data.value : "";
20302
+ let value = typeof e.data.value === "string" ? e.data.value : "";
20030
20303
  const previous = stylesRef.current;
20304
+ let previousSections;
20305
+ const store = parseStyleStore(value);
20306
+ if (store) {
20307
+ const folded = foldAlignIntoTrees(parseAiSectionsState(aiSectionsRef.current), store);
20308
+ if (folded.changed) {
20309
+ const nextSections = serializeAiSectionsState(folded.state);
20310
+ if (nextSections !== aiSectionsRef.current) {
20311
+ previousSections = aiSectionsRef.current;
20312
+ aiSectionsRef.current = nextSections;
20313
+ applyAiSectionsToDom(folded.state);
20314
+ postToParentRef.current({
20315
+ type: "ow:change",
20316
+ nodes: [{ key: AI_SECTIONS_KEY, text: nextSections }]
20317
+ });
20318
+ }
20319
+ value = JSON.stringify(folded.store);
20320
+ }
20321
+ }
20031
20322
  stylesRef.current = value;
20032
20323
  applyStylesToDom(parseStyleStore(value));
20033
20324
  postToParentRef.current({ type: "ow:change", nodes: [{ key: STYLE_STORE_KEY, text: value }] });
20034
- postToParentRef.current({ type: "ow:ai-styles-applied", previous, value });
20325
+ postToParentRef.current({
20326
+ type: "ow:ai-styles-applied",
20327
+ previous,
20328
+ value,
20329
+ ...previousSections !== void 0 ? { previousSections } : {}
20330
+ });
20035
20331
  };
20036
20332
  window.addEventListener("message", handleAiSetStyles);
20037
20333
  const handleGetBrand = (e) => {
@@ -20041,20 +20337,6 @@ function OhhwellsBridge() {
20041
20337
  postToParentRef.current({ type: "ow:brand-value", value });
20042
20338
  };
20043
20339
  window.addEventListener("message", handleGetBrand);
20044
- const handleMoveSection = (e) => {
20045
- if (e.data?.type !== "ow:move-section") return;
20046
- const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
20047
- const direction = e.data.direction === "up" || e.data.direction === "down" ? e.data.direction : null;
20048
- if (!instanceId || !direction) return;
20049
- const entries = moveSectionInstance(instanceId, direction, window.location.pathname);
20050
- if (!entries) return;
20051
- const orderJson = JSON.stringify(entries);
20052
- editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
20053
- setAiSectionOrder(orderJson, window.location.pathname);
20054
- postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20055
- window.dispatchEvent(new Event("resize"));
20056
- };
20057
- window.addEventListener("message", handleMoveSection);
20058
20340
  const handlePanelDragging = (e) => {
20059
20341
  if (e.data?.type !== "ow:panel-dragging") return;
20060
20342
  if (e.data.dragging) document.documentElement.setAttribute("data-ohw-panel-dragging", "");
@@ -20112,6 +20394,12 @@ function OhhwellsBridge() {
20112
20394
  closeLinkPopoverRef.current();
20113
20395
  return;
20114
20396
  }
20397
+ if (floatingPanelOpenRef.current) {
20398
+ setFloatingPanelRef.current(null);
20399
+ deselectRef.current();
20400
+ deactivateRef.current();
20401
+ return;
20402
+ }
20115
20403
  deselectRef.current();
20116
20404
  deactivateRef.current();
20117
20405
  clearMediaSelectionRef.current();
@@ -20386,8 +20674,12 @@ function OhhwellsBridge() {
20386
20674
  if (inserted) {
20387
20675
  const tracker = getSectionsTracker();
20388
20676
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
20389
- const h = document.body.scrollHeight;
20390
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
20677
+ const reportHeight = () => {
20678
+ const h = document.body.scrollHeight;
20679
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
20680
+ };
20681
+ reportHeight();
20682
+ setTimeout(reportHeight, 500);
20391
20683
  }
20392
20684
  };
20393
20685
  const handleSwitchSchedule = (e) => {
@@ -20783,17 +21075,17 @@ function OhhwellsBridge() {
20783
21075
  window.removeEventListener("message", handleAiApplyTree);
20784
21076
  window.removeEventListener("message", handleAiDeleteSection);
20785
21077
  window.removeEventListener("message", handleAiSetSections);
21078
+ window.removeEventListener("message", handleMoveSection);
20786
21079
  window.removeEventListener("message", handleAiSetBrand);
20787
21080
  window.removeEventListener("message", handleAiSetStyles);
20788
21081
  window.removeEventListener("message", handleGetBrand);
20789
- window.removeEventListener("message", handleMoveSection);
20790
21082
  window.removeEventListener("message", handlePanelDragging);
20791
21083
  window.removeEventListener("message", handleDeleteSection);
20792
21084
  window.removeEventListener("message", handleDeactivate);
20793
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
20794
21085
  window.removeEventListener("message", handleToastAction);
20795
21086
  window.removeEventListener("message", handleFormCount);
20796
21087
  window.removeEventListener("message", handleUiEscape);
21088
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
20797
21089
  autoSaveTimers.current.forEach(clearTimeout);
20798
21090
  autoSaveTimers.current.clear();
20799
21091
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -20996,7 +21288,7 @@ function OhhwellsBridge() {
20996
21288
  postToParent2({
20997
21289
  type: "ow:ready",
20998
21290
  version: "1",
20999
- bridgeVersion: "0.1.78",
21291
+ bridgeVersion: "0.1.80",
21000
21292
  path: pathname,
21001
21293
  nodes: collectEditableNodes(editContentRef.current),
21002
21294
  sections
@@ -21459,6 +21751,7 @@ function OhhwellsBridge() {
21459
21751
  return /* @__PURE__ */ jsxs20(Fragment8, { children: [
21460
21752
  /* @__PURE__ */ jsx33("div", { id: "ohw-loader", suppressHydrationWarning: true, style: { ...OHW_LOADER_STYLE, display: "none" }, children: /* @__PURE__ */ jsx33(OhwLoaderSpinner, {}) }),
21461
21753
  /* @__PURE__ */ jsx33("script", { suppressHydrationWarning: true, dangerouslySetInnerHTML: { __html: OHW_LOADER_PREHYDRATE_SCRIPT } }),
21754
+ subdomain && !isEditMode && showBranding && /* @__PURE__ */ jsx33(MadeWithOhhWells, {}),
21462
21755
  bridgeRoot ? createPortal2(
21463
21756
  /* @__PURE__ */ jsxs20(Fragment8, { children: [
21464
21757
  /* @__PURE__ */ jsx33("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
@@ -21914,6 +22207,59 @@ function OhhwellsBridge() {
21914
22207
  ) : null
21915
22208
  ] });
21916
22209
  }
22210
+
22211
+ // src/ui/EmptySection.tsx
22212
+ import Link3 from "next/link";
22213
+ import { Fragment as Fragment9, jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
22214
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
22215
+ return /* @__PURE__ */ jsxs21(Fragment9, { children: [
22216
+ /* @__PURE__ */ jsx34(
22217
+ "p",
22218
+ {
22219
+ style: {
22220
+ fontFamily: "var(--brand-font-body)",
22221
+ fontSize: "0.75rem",
22222
+ fontWeight: 500,
22223
+ letterSpacing: "0.15em",
22224
+ textTransform: "uppercase",
22225
+ color: "var(--brand-accent)",
22226
+ marginBottom: "1.5rem"
22227
+ },
22228
+ children: /* @__PURE__ */ jsx34(Link3, { href: homeHref, style: { color: "inherit" }, children: /* @__PURE__ */ jsx34("span", { ...eyebrowKey ? { "data-ohw-editable": "text", "data-ohw-key": eyebrowKey } : {}, children: "Home" }) })
22229
+ }
22230
+ ),
22231
+ /* @__PURE__ */ jsx34(
22232
+ "h1",
22233
+ {
22234
+ style: {
22235
+ fontFamily: "var(--brand-font-heading)",
22236
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
22237
+ lineHeight: 1.1,
22238
+ letterSpacing: "-0.025em",
22239
+ color: "var(--brand-text)",
22240
+ marginBottom: "1rem"
22241
+ },
22242
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
22243
+ children: title
22244
+ }
22245
+ ),
22246
+ /* @__PURE__ */ jsx34(
22247
+ "p",
22248
+ {
22249
+ style: {
22250
+ fontFamily: "var(--brand-font-body)",
22251
+ fontSize: "1rem",
22252
+ lineHeight: 1.7,
22253
+ fontWeight: 300,
22254
+ color: "var(--brand-text-muted)",
22255
+ maxWidth: "340px"
22256
+ },
22257
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
22258
+ children: "This page doesn't have any content yet."
22259
+ }
22260
+ )
22261
+ ] });
22262
+ }
21917
22263
  export {
21918
22264
  AI_DEFAULT_BRAND,
21919
22265
  AI_TREE_SCHEMA_VERSIONS,
@@ -21930,6 +22276,7 @@ export {
21930
22276
  DropdownMenuItem,
21931
22277
  DropdownMenuSeparator,
21932
22278
  DropdownMenuTrigger,
22279
+ EmptySection,
21933
22280
  ItemActionToolbar,
21934
22281
  ItemInteractionLayer,
21935
22282
  LinkEditorPanel,