@ohhwells/bridge 0.1.82 → 0.1.84-next.246

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
  }
@@ -183,6 +241,17 @@ var BRAND_VAR_PREFIX = "--ohw-brand-";
183
241
  var BRAND_VAR_NAMES = ["primary", "accent", "light", "dark", "surface", "border", "muted"].map(
184
242
  (role) => `${BRAND_VAR_PREFIX}${role}`
185
243
  );
244
+ var LEGACY_BRAND_VAR_NAMES = [
245
+ "primary",
246
+ "accent",
247
+ "background",
248
+ "text",
249
+ "text-muted",
250
+ "surface",
251
+ "border",
252
+ "on-primary",
253
+ "navbar-background"
254
+ ].map((role) => `--brand-${role}`);
186
255
  var FONT_VARS = {
187
256
  heading: ["--font-heading", "--font-display", "--brand-font-heading"],
188
257
  body: ["--font-body", "--brand-font-body"]
@@ -191,14 +260,29 @@ var BRAND_FONT_LINK_ID = "ohw-brand-fonts";
191
260
  function brandColorVars(kit) {
192
261
  const { dark, primary, accent, light } = kit.palette;
193
262
  const mix = (a, aPct, b) => `color-mix(in srgb, ${a} ${aPct}%, ${b})`;
263
+ const surface = mix(light, 95, dark);
264
+ const border = mix(light, 85, dark);
265
+ const muted = mix(dark, 62, light);
194
266
  return {
195
267
  [`${BRAND_VAR_PREFIX}primary`]: primary,
196
268
  [`${BRAND_VAR_PREFIX}accent`]: accent,
197
269
  [`${BRAND_VAR_PREFIX}light`]: light,
198
270
  [`${BRAND_VAR_PREFIX}dark`]: dark,
199
- [`${BRAND_VAR_PREFIX}surface`]: mix(light, 95, dark),
200
- [`${BRAND_VAR_PREFIX}border`]: mix(light, 85, dark),
201
- [`${BRAND_VAR_PREFIX}muted`]: mix(dark, 62, light)
271
+ [`${BRAND_VAR_PREFIX}surface`]: surface,
272
+ [`${BRAND_VAR_PREFIX}border`]: border,
273
+ [`${BRAND_VAR_PREFIX}muted`]: muted,
274
+ // The BrandProvider contract every template actually renders from (see LEGACY_BRAND_VAR_NAMES).
275
+ "--brand-primary": primary,
276
+ "--brand-accent": accent,
277
+ "--brand-background": light,
278
+ "--brand-text": dark,
279
+ "--brand-text-muted": muted,
280
+ "--brand-surface": surface,
281
+ "--brand-border": border,
282
+ // Buttons/bands painted in the primary colour assume it's dark/saturated enough to need
283
+ // light text on top — the same assumption LOGO_IMAGE's light-on-dark navbar mark makes.
284
+ "--brand-on-primary": light,
285
+ "--brand-navbar-background": dark
202
286
  };
203
287
  }
204
288
  function parseBrandKit(raw) {
@@ -239,7 +323,7 @@ function loadBrandFonts(families) {
239
323
  function applyBrandToDom(kit) {
240
324
  const root = document.documentElement;
241
325
  if (!kit) {
242
- for (const name of BRAND_VAR_NAMES) root.style.removeProperty(name);
326
+ for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES]) root.style.removeProperty(name);
243
327
  for (const name of [...FONT_VARS.heading, ...FONT_VARS.body]) root.style.removeProperty(name);
244
328
  document.getElementById(BRAND_FONT_LINK_ID)?.remove();
245
329
  return;
@@ -304,6 +388,9 @@ function styleSheetCss() {
304
388
  `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px}px !important; padding-bottom: ${px}px !important; }`
305
389
  );
306
390
  }
391
+ for (const align of ["left", "center", "right"]) {
392
+ rules.push(`[data-ohw-style-align="${align}"] { text-align: ${align} !important; }`);
393
+ }
307
394
  return rules.join("\n");
308
395
  }
309
396
  var STYLE_FONT_LINK_ID = "ohw-style-fonts";
@@ -330,13 +417,29 @@ var SECTION_ATTRS = {
330
417
  textDistribution: "data-ohw-style-distribution",
331
418
  headlineScale: "data-ohw-style-headline",
332
419
  imageAspect: "data-ohw-style-aspect",
333
- spacing: "data-ohw-style-spacing"
420
+ spacing: "data-ohw-style-spacing",
421
+ align: "data-ohw-style-align"
334
422
  };
335
423
  var NODE_WROTE_ATTR = "data-ohw-style-node";
336
- var NODE_PROPS = ["color", "font-family", "font-size", "background"];
424
+ var NODE_PROPS = [
425
+ "color",
426
+ "font-family",
427
+ "font-size",
428
+ "background",
429
+ "text-align",
430
+ "justify-content",
431
+ "align-items"
432
+ ];
433
+ var ALIGN_JUSTIFY = {
434
+ left: "flex-start",
435
+ center: "center",
436
+ right: "flex-end"
437
+ };
337
438
  function saveInline(el, prop) {
338
439
  const attr = `data-ohw-style-prev-${prop}`;
339
- if (!el.hasAttribute(attr)) el.setAttribute(attr, el.style.getPropertyValue(prop));
440
+ if (el.hasAttribute(attr)) return;
441
+ const value = el.style.getPropertyValue(prop) || (prop === "background" ? el.style.getPropertyValue("background-color") : "");
442
+ el.setAttribute(attr, value);
340
443
  }
341
444
  function restoreInline(el, prop) {
342
445
  const attr = `data-ohw-style-prev-${prop}`;
@@ -375,6 +478,13 @@ function clearNodeProps(root) {
375
478
  function buttonSurfaceOf(el) {
376
479
  return el.closest("a, button") ?? el;
377
480
  }
481
+ function alignSubjectOf(el) {
482
+ const button = el.closest('[data-ohw-role="button"]');
483
+ return button?.parentElement ?? el;
484
+ }
485
+ function horizontalFlexProp(el) {
486
+ return getComputedStyle(el).flexDirection.startsWith("column") ? "align-items" : "justify-content";
487
+ }
378
488
  function applyStylesToDom(store) {
379
489
  ensureStyleSheet();
380
490
  clearSectionAttrs(document);
@@ -387,7 +497,8 @@ function applyStylesToDom(store) {
387
497
  const sections = document.querySelectorAll(
388
498
  `[data-ohw-section="${CSS.escape(sectionId)}"]`
389
499
  );
390
- for (const section of Array.from(sections)) {
500
+ for (const marker of Array.from(sections)) {
501
+ const section = marker.querySelector(":scope > [data-ai-section]") ?? marker;
391
502
  for (const [prop, attr] of Object.entries(SECTION_ATTRS)) {
392
503
  const value = override[prop];
393
504
  if (value === void 0) continue;
@@ -419,6 +530,15 @@ function applyStylesToDom(store) {
419
530
  el.style.setProperty("font-size", `${override.fontSize}px`, "important");
420
531
  el.setAttribute(NODE_WROTE_ATTR, "");
421
532
  }
533
+ if (override.align !== void 0) {
534
+ const subject = alignSubjectOf(el);
535
+ const flexProp = horizontalFlexProp(subject);
536
+ saveInline(subject, "text-align");
537
+ saveInline(subject, flexProp);
538
+ subject.style.setProperty("text-align", override.align, "important");
539
+ subject.style.setProperty(flexProp, ALIGN_JUSTIFY[override.align] ?? "flex-start", "important");
540
+ subject.setAttribute(NODE_WROTE_ATTR, "");
541
+ }
422
542
  if (override.buttonBackground !== void 0 || override.buttonText !== void 0) {
423
543
  const surface = buttonSurfaceOf(el);
424
544
  if (override.buttonBackground !== void 0) {
@@ -514,6 +634,22 @@ function accentBandContext(brand) {
514
634
  function textAttrs(ctx, path) {
515
635
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
516
636
  }
637
+ var AI_RESPONSIVE_CSS = [
638
+ "@media (max-width: 960px) {",
639
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
640
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
641
+ "}",
642
+ "@media (max-width: 640px) {",
643
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
644
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
645
+ // Group containers flatten to a column on phones; span placements come along for free.
646
+ " [data-ai-responsive] [data-ai-group] { display: flex !important; flex-direction: column !important; }",
647
+ " [data-ai-responsive] [data-ai-group] > * { grid-column: auto !important; }",
648
+ " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
649
+ " [data-ai-responsive] { overflow-x: hidden; }",
650
+ " [data-ai-responsive] img { max-width: 100%; }",
651
+ "}"
652
+ ].join("\n");
517
653
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
518
654
  function MediaBox({
519
655
  refValue,
@@ -526,13 +662,17 @@ function MediaBox({
526
662
  const url = refValue ? ctx.resolveMedia(refValue) : null;
527
663
  const isIcon = /^(lucide|simple):/.test(refValue);
528
664
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
529
- const editAttrs = ctx.keyFor && editPath && !isIcon ? { "data-ohw-key": ctx.keyFor(editPath), "data-ohw-editable": "image" } : {};
665
+ const editAttrs = ctx.keyFor && editPath ? {
666
+ "data-ohw-key": ctx.keyFor(editPath),
667
+ "data-ohw-editable": isIcon ? "icon" : "image"
668
+ } : {};
530
669
  if (isIcon) {
531
670
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
532
671
  return /* @__PURE__ */ jsx(
533
672
  "span",
534
673
  {
535
674
  "data-ai-icon": refValue,
675
+ ...editAttrs,
536
676
  style: {
537
677
  display: "inline-flex",
538
678
  width: 48,
@@ -627,7 +767,7 @@ function TextBlock({ slots, ctx, path }) {
627
767
  }
628
768
  function SectionHeaderBlock({ node, ctx, path }) {
629
769
  const slots = node.slots ?? {};
630
- const align = slots.alignment === "center" ? "center" : "left";
770
+ const align = node.align ?? (slots.alignment === "center" ? "center" : "left");
631
771
  const children = node.children ?? [];
632
772
  const buttonRowIdx = children.findIndex((c) => c.type === "button-row");
633
773
  const buttonRow = buttonRowIdx >= 0 ? children[buttonRowIdx] : void 0;
@@ -671,7 +811,7 @@ function SectionHeaderBlock({ node, ctx, path }) {
671
811
  display: "flex",
672
812
  gap: AI_TREE_TOKENS.spacing6,
673
813
  marginTop: AI_TREE_TOKENS.spacing8,
674
- justifyContent: align === "center" ? "center" : "flex-start"
814
+ justifyContent: align === "center" ? "center" : align === "right" ? "flex-end" : "flex-start"
675
815
  },
676
816
  children: (buttonRow.children ?? []).map((button, i) => /* @__PURE__ */ jsx(
677
817
  ButtonEl,
@@ -1004,7 +1144,7 @@ function CardBlock({ node, ctx, path }) {
1004
1144
  editPath: `${path}.media`
1005
1145
  }
1006
1146
  ) : null;
1007
- const centered = slots.alignment === "center";
1147
+ const centered = (node.align ?? slots.alignment) === "center";
1008
1148
  const content = /* @__PURE__ */ jsxs(
1009
1149
  "div",
1010
1150
  {
@@ -1418,7 +1558,7 @@ function CollectionBlock({ node, ctx, path }) {
1418
1558
  return /* @__PURE__ */ jsx(
1419
1559
  "div",
1420
1560
  {
1421
- "data-ai-grid": "",
1561
+ "data-ai-grid": String(itemsPerRow),
1422
1562
  style: {
1423
1563
  display: "grid",
1424
1564
  gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
@@ -1723,11 +1863,25 @@ function AiTreeRenderer({
1723
1863
  }
1724
1864
  })();
1725
1865
  const distributed = !isOverlay && settings.textDistribution;
1866
+ const rowAlignItems = (rowAlign) => {
1867
+ if (rowAlign === "top") return "start";
1868
+ if (rowAlign === "bottom") return "end";
1869
+ if (rowAlign === "center" || rowAlign === "stretch") return rowAlign;
1870
+ if (distributed === "space-between") return "stretch";
1871
+ return (distributed ?? settings.verticalPosition) === "top" ? "start" : "center";
1872
+ };
1873
+ const cellAlignStyle = (blockAlign) => blockAlign ? {
1874
+ display: "flex",
1875
+ flexDirection: "column",
1876
+ alignItems: blockAlign === "left" ? "flex-start" : blockAlign === "right" ? "flex-end" : "center",
1877
+ textAlign: blockAlign
1878
+ } : {};
1726
1879
  return /* @__PURE__ */ jsxs(
1727
1880
  "section",
1728
1881
  {
1729
1882
  "data-ai-section": tree.tag ?? "",
1730
1883
  ...bgAttrs,
1884
+ "data-ai-responsive": "",
1731
1885
  style: {
1732
1886
  position: "relative",
1733
1887
  padding: `${pad}px 0`,
@@ -1738,12 +1892,13 @@ function AiTreeRenderer({
1738
1892
  color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
1739
1893
  },
1740
1894
  children: [
1741
- isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
1895
+ /* @__PURE__ */ jsx("style", { children: AI_RESPONSIVE_CSS }),
1742
1896
  /* @__PURE__ */ jsx("style", { children: AI_MOBILE_CSS }),
1897
+ isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
1743
1898
  /* @__PURE__ */ jsx(
1744
1899
  "div",
1745
1900
  {
1746
- "data-ai-container": "",
1901
+ "data-ai-section-inner": "",
1747
1902
  style: {
1748
1903
  position: "relative",
1749
1904
  maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
@@ -1754,12 +1909,12 @@ function AiTreeRenderer({
1754
1909
  children: tree.rows.map((row, r2) => /* @__PURE__ */ jsx(
1755
1910
  "div",
1756
1911
  {
1757
- "data-ai-row": "",
1912
+ "data-ai-columns": "",
1758
1913
  style: {
1759
1914
  display: "grid",
1760
1915
  gridTemplateColumns: "repeat(12, 1fr)",
1761
1916
  gap: AI_TREE_TOKENS.spacing6,
1762
- alignItems: distributed === "space-between" ? "stretch" : (distributed ?? settings.verticalPosition) === "top" ? "start" : "center",
1917
+ alignItems: rowAlignItems(row.align),
1763
1918
  marginTop: r2 > 0 ? AI_TREE_TOKENS.spacing8 : 0
1764
1919
  },
1765
1920
  children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ jsx(
@@ -1769,6 +1924,8 @@ function AiTreeRenderer({
1769
1924
  style: {
1770
1925
  gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`,
1771
1926
  minWidth: 0,
1927
+ // Horizontal placement of the block's content within its column.
1928
+ ...cellAlignStyle(block.align),
1772
1929
  // space-between: each column becomes a flex column whose content spreads over
1773
1930
  // the full row height instead of clumping at the top.
1774
1931
  ...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
@@ -7356,6 +7513,9 @@ function applyFieldType(wrapper, type) {
7356
7513
  el.style.removeProperty("min-height");
7357
7514
  el.style.removeProperty("resize");
7358
7515
  el.removeAttribute("rows");
7516
+ if (el.className) {
7517
+ el.className = el.className.split(/\s+/).filter((token) => !/textarea/i.test(token)).join(" ");
7518
+ }
7359
7519
  };
7360
7520
  const applyDefaults = (el) => {
7361
7521
  el.setAttribute("placeholder", defaults.placeholder);
@@ -7777,6 +7937,7 @@ function MediaOverlay({
7777
7937
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
7778
7938
  );
7779
7939
  }, [isVideo]);
7940
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
7780
7941
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
7781
7942
  const box = {
7782
7943
  position: "fixed",
@@ -7906,17 +8067,17 @@ function MediaOverlay({
7906
8067
  },
7907
8068
  children: [
7908
8069
  isVideo ? /* @__PURE__ */ jsx15(Film, { size: 14 }) : /* @__PURE__ */ jsx15(ImageIcon, { size: 14 }),
7909
- isVideo ? "Replace video" : "Replace image"
8070
+ replaceLabel
7910
8071
  ]
7911
8072
  }
7912
8073
  ),
7913
- replaceMode === "none" ? null : /* @__PURE__ */ jsxs7(
8074
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ jsxs7(
7914
8075
  Button,
7915
8076
  {
7916
8077
  "data-ohw-media-overlay": "",
7917
8078
  variant: "outline",
7918
8079
  size: "sm",
7919
- "aria-label": isVideo ? "Replace video" : "Replace image",
8080
+ "aria-label": replaceLabel,
7920
8081
  className: "gap-1.5 cursor-pointer hover:bg-background",
7921
8082
  style: {
7922
8083
  ...OVERLAY_BUTTON_STYLE,
@@ -7939,7 +8100,7 @@ function MediaOverlay({
7939
8100
  },
7940
8101
  children: [
7941
8102
  isVideo ? /* @__PURE__ */ jsx15(Film, { size: 14 }) : /* @__PURE__ */ jsx15(ImageIcon, { size: 14 }),
7942
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
8103
+ replaceMode === "full" ? replaceLabel : null
7943
8104
  ]
7944
8105
  }
7945
8106
  )
@@ -8145,6 +8306,27 @@ function deleteSectionInstance(instanceId, currentPath, existingEntries) {
8145
8306
  function restoreSectionInstance(instanceId, currentPath, existingEntries) {
8146
8307
  return setSectionRemoved(instanceId, currentPath, existingEntries, false);
8147
8308
  }
8309
+ function duplicateSectionInstance(instanceId, newId, currentPath, existingEntries) {
8310
+ const original = findByInstanceId(instanceId);
8311
+ if (!original) return null;
8312
+ const clone = original.cloneNode(true);
8313
+ clone.setAttribute("data-ohw-instance", newId);
8314
+ const keyRekeys = rekeySectionSubtree(clone, newId);
8315
+ original.insertAdjacentElement("afterend", clone);
8316
+ const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
8317
+ const entries = topLevelSections().map((el, order) => {
8318
+ const id = instanceIdOf(el);
8319
+ return {
8320
+ instanceId: id,
8321
+ type: el.getAttribute("data-ohw-section") ?? "",
8322
+ order,
8323
+ pagePath: currentPath,
8324
+ ...byId.get(id)?.removed ? { removed: true } : {}
8325
+ };
8326
+ });
8327
+ applyPersistedOrder(entries);
8328
+ return { entries, keyRekeys };
8329
+ }
8148
8330
  function newInstanceId() {
8149
8331
  return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `instance-${Date.now()}-${Math.random().toString(36).slice(2)}`;
8150
8332
  }
@@ -8159,14 +8341,20 @@ function getPageSectionOrderEntries(raw, currentPath) {
8159
8341
  }
8160
8342
  function rekeySectionSubtree(root, instanceId) {
8161
8343
  const suffix = `::${instanceId}`;
8344
+ const pairs = [];
8162
8345
  const rekey = (el, attr) => {
8163
8346
  const current = el.getAttribute(attr);
8164
- if (current) el.setAttribute(attr, `${current}${suffix}`);
8347
+ if (!current) return;
8348
+ const base = current.includes("::") ? current.slice(0, current.indexOf("::")) : current;
8349
+ const next = `${base}${suffix}`;
8350
+ el.setAttribute(attr, next);
8351
+ pairs.push({ from: current, to: next });
8165
8352
  };
8166
8353
  if (root.hasAttribute("data-ohw-key")) rekey(root, "data-ohw-key");
8167
8354
  if (root.hasAttribute("data-ohw-href-key")) rekey(root, "data-ohw-href-key");
8168
8355
  root.querySelectorAll("[data-ohw-key]").forEach((el) => rekey(el, "data-ohw-key"));
8169
8356
  root.querySelectorAll("[data-ohw-href-key]").forEach((el) => rekey(el, "data-ohw-href-key"));
8357
+ return pairs;
8170
8358
  }
8171
8359
  function initSectionInstancesFromContent(content, currentPath) {
8172
8360
  document.querySelectorAll("[data-ohw-section]:not([data-ohw-instance])").forEach((el) => {
@@ -11606,7 +11794,7 @@ function applySocialsDisplayToRow(row, display) {
11606
11794
  const icon = item.querySelector(ICON_SELECTOR);
11607
11795
  if (label) label.style.display = display.text ? "" : "none";
11608
11796
  if (icon) icon.style.display = display.icon ? "" : "none";
11609
- layOutIconAndLabel(item, Boolean(display.text && display.icon));
11797
+ layOutIconAndLabel(item, Boolean(display.text));
11610
11798
  });
11611
11799
  allowRowToWrap(row);
11612
11800
  }
@@ -11618,6 +11806,9 @@ function layOutIconAndLabel(item, on) {
11618
11806
  item.style.gap = "";
11619
11807
  item.style.whiteSpace = "";
11620
11808
  item.style.flex = "";
11809
+ item.style.width = "";
11810
+ item.style.height = "";
11811
+ item.style.padding = "";
11621
11812
  return;
11622
11813
  }
11623
11814
  item.style.display = on ? "inline-flex" : "";
@@ -11625,6 +11816,9 @@ function layOutIconAndLabel(item, on) {
11625
11816
  item.style.gap = on ? "8px" : "";
11626
11817
  item.style.whiteSpace = on ? "nowrap" : "";
11627
11818
  item.style.flex = on ? "0 0 auto" : "";
11819
+ item.style.width = on ? "auto" : "";
11820
+ item.style.height = on ? "auto" : "";
11821
+ item.style.padding = on ? "0 12px" : "";
11628
11822
  }
11629
11823
  function allowRowToWrap(row) {
11630
11824
  const display = row.ownerDocument.defaultView?.getComputedStyle(row).display ?? "";
@@ -12580,6 +12774,15 @@ function resolveLogoDisplayText(text) {
12580
12774
  function isFooterLogoRoot(root) {
12581
12775
  return Boolean(root.closest("footer") || root.closest('[data-ohw-section="footer"]'));
12582
12776
  }
12777
+ var NAV_IMAGE_KEYS = ["nav-logo-image", "logo-image"];
12778
+ var FOOTER_IMAGE_KEYS = ["footer-logo", "footer-logo-image", "footer-logo-img"];
12779
+ function firstNonEmptyContentValue(content, keys) {
12780
+ for (const key of keys) {
12781
+ const value = content[key];
12782
+ if (typeof value === "string" && value.trim()) return value.trim();
12783
+ }
12784
+ return null;
12785
+ }
12583
12786
  function imageKeyForRoot(root) {
12584
12787
  return isFooterLogoRoot(root) ? "footer-logo" : "nav-logo-image";
12585
12788
  }
@@ -12629,9 +12832,10 @@ function applyLogoIdentity(text, isPlaceholder) {
12629
12832
  });
12630
12833
  return display;
12631
12834
  }
12632
- function applyLogoImage(url, alt) {
12835
+ function applyLogoImage(url, alt, placement) {
12633
12836
  const displayAlt = resolveLogoDisplayText(alt);
12634
12837
  document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
12838
+ if (placement && (isFooterLogoRoot(root) ? "footer" : "navbar") !== placement) return;
12635
12839
  ensureLogoHrefKey(root);
12636
12840
  const imageKey = imageKeyForRoot(root);
12637
12841
  const textKey = textKeyForRoot(root);
@@ -12752,7 +12956,12 @@ function applyLogoFromContent(content) {
12752
12956
  const logoImageUrl = typeof rawLogoImage === "string" && rawLogoImage.trim() ? rawLogoImage.trim() : null;
12753
12957
  const imageExplicitlyCleared = LOGO_IMAGE_KEYS.some((key) => key in content) && !logoImageUrl;
12754
12958
  const logoIsPlaceholder = LOGO_PLACEHOLDER_KEY in content ? content[LOGO_PLACEHOLDER_KEY] !== "false" : !logoImageUrl && (!logoText.trim() || logoText === PLACEHOLDER_BUSINESS_NAME);
12755
- if (logoImageUrl) {
12959
+ const navImageUrl = firstNonEmptyContentValue(content, NAV_IMAGE_KEYS);
12960
+ const footerImageUrl = firstNonEmptyContentValue(content, FOOTER_IMAGE_KEYS);
12961
+ if (navImageUrl && footerImageUrl && navImageUrl !== footerImageUrl) {
12962
+ applyLogoImage(navImageUrl, logoAlt, "navbar");
12963
+ applyLogoImage(footerImageUrl, logoAlt, "footer");
12964
+ } else if (logoImageUrl) {
12756
12965
  applyLogoImage(logoImageUrl, logoAlt);
12757
12966
  } else {
12758
12967
  if (imageExplicitlyCleared) applyLogoImage(null, logoAlt);
@@ -12883,6 +13092,7 @@ function readLogoSizeState(content, placement) {
12883
13092
  function getLogoElement(el) {
12884
13093
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
12885
13094
  if (marked) return marked;
13095
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
12886
13096
  const root = el.closest("nav, [data-ohw-nav-root], footer");
12887
13097
  if (!root) return null;
12888
13098
  const anchor = el.closest("a");
@@ -14817,21 +15027,10 @@ function parseSchedulingInsertAfter(insertAfter) {
14817
15027
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
14818
15028
  };
14819
15029
  }
14820
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
14821
- const parsed = parseSchedulingInsertAfter(insertAfter);
14822
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
14823
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
14824
- return { effectiveInsertAfter, insertBefore };
14825
- }
14826
- function getSchedulingMountPoint(insertAfter) {
14827
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
14828
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
14829
- if (!anchorEl && anchor === "scheduling") {
14830
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
14831
- anchorEl = widgets.at(-1) ?? null;
14832
- }
14833
- if (!anchorEl) return null;
14834
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
15030
+ function resolveEntryAnchor(entry) {
15031
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
15032
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
15033
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
14835
15034
  }
14836
15035
  function schedulingMountDepth(insertAfter) {
14837
15036
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -14848,8 +15047,7 @@ function getPageSchedulingEntries(raw) {
14848
15047
  }
14849
15048
  }
14850
15049
  function isSchedulingWidgetMissing(entry) {
14851
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
14852
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
15050
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
14853
15051
  }
14854
15052
  function hasMissingSchedulingWidgets(entries) {
14855
15053
  return entries.some(isSchedulingWidgetMissing);
@@ -14879,16 +15077,17 @@ function initSectionsFromContent(content, removeExisting = false) {
14879
15077
  } catch {
14880
15078
  }
14881
15079
  }
14882
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
14883
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
14884
- const sectionId = schedulingSectionId(effectiveInsertAfter);
15080
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
15081
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
15082
+ const sectionId = schedulingSectionId(widgetId);
14885
15083
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
14886
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
14887
- if (!mountPoint) return false;
15084
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
15085
+ if (!anchorEl) return false;
15086
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
14888
15087
  const container = document.createElement("div");
14889
15088
  container.dataset.ohwSectionContainer = "scheduling";
14890
- if (insertBefore) {
14891
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
15089
+ if (beforeId) {
15090
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
14892
15091
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
14893
15092
  if (!beforePoint) return false;
14894
15093
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -14899,19 +15098,25 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
14899
15098
  }
14900
15099
  tail.insertAdjacentElement("afterend", container);
14901
15100
  }
14902
- const root = createRoot2(container);
14903
- flushSync2(() => {
14904
- root.render(
14905
- /* @__PURE__ */ jsx33(
14906
- SchedulingWidget,
14907
- {
14908
- notifyOnConnect,
14909
- initialScheduleId: scheduleId,
14910
- insertAfter: effectiveInsertAfter
14911
- }
14912
- )
14913
- );
14914
- });
15101
+ try {
15102
+ const root = createRoot2(container);
15103
+ flushSync2(() => {
15104
+ root.render(
15105
+ /* @__PURE__ */ jsx33(
15106
+ SchedulingWidget,
15107
+ {
15108
+ notifyOnConnect,
15109
+ initialScheduleId: scheduleId,
15110
+ insertAfter: widgetId
15111
+ }
15112
+ )
15113
+ );
15114
+ });
15115
+ } catch (err) {
15116
+ console.error("[ow:scheduling] render threw", err);
15117
+ container.remove();
15118
+ return false;
15119
+ }
14915
15120
  const tracker = getSectionsTracker();
14916
15121
  let sections = [];
14917
15122
  try {
@@ -14919,10 +15124,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
14919
15124
  } catch {
14920
15125
  }
14921
15126
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
14922
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
15127
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
14923
15128
  sections.push({
14924
15129
  type: "scheduling",
14925
- insertAfter: effectiveInsertAfter,
15130
+ insertAfter: widgetId,
15131
+ anchorId,
15132
+ beforeId: beforeId ?? null,
14926
15133
  pagePath: window.location.pathname,
14927
15134
  ...scheduleId ? { scheduleId } : {}
14928
15135
  });
@@ -14936,7 +15143,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
14936
15143
  for (let i = pending.length - 1; i >= 0; i--) {
14937
15144
  const entry = pending[i];
14938
15145
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
14939
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId ?? null)) {
15146
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
15147
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
14940
15148
  pending.splice(i, 1);
14941
15149
  }
14942
15150
  }
@@ -15094,6 +15302,11 @@ function applyLinkByKey(key, val) {
15094
15302
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
15095
15303
  }
15096
15304
  }
15305
+ function isInsideLinkEditor(target) {
15306
+ return Boolean(
15307
+ 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"]')
15308
+ );
15309
+ }
15097
15310
  function isInsideFloatingPanel(target) {
15098
15311
  return Boolean(target.closest("[data-ohw-floating-panel]"));
15099
15312
  }
@@ -15101,11 +15314,6 @@ function isPointOverFloatingPanel(clientX, clientY) {
15101
15314
  const el = document.elementFromPoint(clientX, clientY);
15102
15315
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
15103
15316
  }
15104
- function isInsideLinkEditor(target) {
15105
- return Boolean(
15106
- 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"]')
15107
- );
15108
- }
15109
15317
  function getHrefKeyFromElement(el) {
15110
15318
  if (!el) return null;
15111
15319
  const anchor = el.closest("[data-ohw-href-key]");
@@ -15364,7 +15572,7 @@ function getNavigationSelectionParent(el) {
15364
15572
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
15365
15573
  return getFooterLinksContainer();
15366
15574
  }
15367
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
15575
+ 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)) {
15368
15576
  return getNavigationRoot(el);
15369
15577
  }
15370
15578
  return null;
@@ -15579,7 +15787,6 @@ var ICONS = {
15579
15787
  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"/>',
15580
15788
  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"/>'
15581
15789
  };
15582
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
15583
15790
  var SELECTION_CHROME_GAP2 = 4;
15584
15791
  var TOOLBAR_STROKE_GAP2 = 4;
15585
15792
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -15959,6 +16166,8 @@ function StateToggle({
15959
16166
  );
15960
16167
  }
15961
16168
  var contentCache = /* @__PURE__ */ new Map();
16169
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
16170
+ var brandingCache = /* @__PURE__ */ new Map();
15962
16171
  var OHW_LOADER_STYLE = {
15963
16172
  position: "fixed",
15964
16173
  inset: 0,
@@ -15996,6 +16205,89 @@ function OhwLoaderSpinner() {
15996
16205
  )
15997
16206
  ] });
15998
16207
  }
16208
+ function OhwBrandMark() {
16209
+ return /* @__PURE__ */ jsxs20(
16210
+ "svg",
16211
+ {
16212
+ width: "16",
16213
+ height: "16",
16214
+ viewBox: "0 0 48 48",
16215
+ fill: "none",
16216
+ "aria-hidden": true,
16217
+ style: { display: "block", flexShrink: 0 },
16218
+ xmlns: "http://www.w3.org/2000/svg",
16219
+ children: [
16220
+ /* @__PURE__ */ jsx33(
16221
+ "mask",
16222
+ {
16223
+ id: "ohw-badge-mark",
16224
+ style: { maskType: "luminance" },
16225
+ maskUnits: "userSpaceOnUse",
16226
+ x: "0",
16227
+ y: "0",
16228
+ width: "48",
16229
+ height: "48",
16230
+ 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" })
16231
+ }
16232
+ ),
16233
+ /* @__PURE__ */ jsxs20("g", { mask: "url(#ohw-badge-mark)", children: [
16234
+ /* @__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" }),
16235
+ /* @__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" }),
16236
+ /* @__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" }),
16237
+ /* @__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" }),
16238
+ /* @__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" })
16239
+ ] })
16240
+ ]
16241
+ }
16242
+ );
16243
+ }
16244
+ var OHW_BADGE_STYLE = {
16245
+ position: "fixed",
16246
+ left: 20,
16247
+ bottom: 20,
16248
+ zIndex: 2147483e3,
16249
+ boxSizing: "border-box",
16250
+ display: "inline-flex",
16251
+ alignItems: "center",
16252
+ gap: 0,
16253
+ padding: "6px 8px",
16254
+ margin: 0,
16255
+ background: "#ffffff",
16256
+ border: "1px solid #e7e5e4",
16257
+ borderRadius: 9999,
16258
+ boxShadow: "0 1px 3px rgba(0, 0, 0, 0.1)",
16259
+ color: "#0c0a09",
16260
+ textDecoration: "none",
16261
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
16262
+ };
16263
+ var OHW_BADGE_LABEL_STYLE = {
16264
+ padding: "0 4px",
16265
+ fontSize: 14,
16266
+ lineHeight: "24px",
16267
+ fontWeight: 500,
16268
+ fontStyle: "normal",
16269
+ letterSpacing: "normal",
16270
+ textTransform: "none",
16271
+ color: "#0c0a09",
16272
+ whiteSpace: "nowrap"
16273
+ };
16274
+ function MadeWithOhhWells() {
16275
+ return /* @__PURE__ */ jsxs20(
16276
+ "a",
16277
+ {
16278
+ href: "https://ohhwells.com",
16279
+ target: "_blank",
16280
+ rel: "noopener noreferrer",
16281
+ "aria-label": "Made with OhhWells",
16282
+ "data-ohw-badge": "",
16283
+ style: OHW_BADGE_STYLE,
16284
+ children: [
16285
+ /* @__PURE__ */ jsx33(OhwBrandMark, {}),
16286
+ /* @__PURE__ */ jsx33("span", { style: OHW_BADGE_LABEL_STYLE, children: "Made with OhhWells" })
16287
+ ]
16288
+ }
16289
+ );
16290
+ }
15999
16291
  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){}})();`;
16000
16292
  function resolveSubdomain(subdomainFromQuery) {
16001
16293
  if (subdomainFromQuery) return subdomainFromQuery;
@@ -16055,6 +16347,7 @@ function OhhwellsBridge() {
16055
16347
  }
16056
16348
  }, []);
16057
16349
  const [fetchState, setFetchState] = useState13("idle");
16350
+ const [showBranding, setShowBranding] = useState13(false);
16058
16351
  const autoSaveTimers = useRef10(/* @__PURE__ */ new Map());
16059
16352
  const activeElRef = useRef10(null);
16060
16353
  const pointerHeldRef = useRef10(false);
@@ -16403,13 +16696,6 @@ function OhhwellsBridge() {
16403
16696
  const [isItemDragging, setIsItemDragging] = useState13(false);
16404
16697
  const [isFooterFrameSelection, setIsFooterFrameSelection] = useState13(false);
16405
16698
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
16406
- const [floatingPanel, setFloatingPanel] = useState13(null);
16407
- const floatingPanelOpenRef = useRef10(false);
16408
- floatingPanelOpenRef.current = floatingPanel !== null;
16409
- const [floatingPanelPos, setFloatingPanelPos] = useState13(null);
16410
- const [logoSizeDraft, setLogoSizeDraft] = useState13(null);
16411
- const [editorViewport, setEditorViewport] = useState13("desktop");
16412
- const [parentScrollSnap, setParentScrollSnap] = useState13(null);
16413
16699
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = useState13(null);
16414
16700
  const [footerHeadingVisible, setFooterHeadingVisible] = useState13(null);
16415
16701
  const footerDragRef = useRef10(null);
@@ -16427,6 +16713,13 @@ function OhhwellsBridge() {
16427
16713
  const brandKitRef = useRef10("");
16428
16714
  const stylesRef = useRef10("");
16429
16715
  const pendingDeleteUndoRef = useRef10(null);
16716
+ const [floatingPanel, setFloatingPanel] = useState13(null);
16717
+ const floatingPanelOpenRef = useRef10(false);
16718
+ const setFloatingPanelRef = useRef10(setFloatingPanel);
16719
+ const [floatingPanelPos, setFloatingPanelPos] = useState13(null);
16720
+ const [logoSizeDraft, setLogoSizeDraft] = useState13(null);
16721
+ const [editorViewport, setEditorViewport] = useState13("desktop");
16722
+ const [parentScrollSnap, setParentScrollSnap] = useState13(null);
16430
16723
  const [sitePages, setSitePages] = useState13([]);
16431
16724
  const [sectionsByPath, setSectionsByPath] = useState13({});
16432
16725
  const sectionsPrefetchGenRef = useRef10(0);
@@ -16435,7 +16728,18 @@ function OhhwellsBridge() {
16435
16728
  const linkPopoverOpenRef = useRef10(false);
16436
16729
  const linkPopoverGraceUntilRef = useRef10(0);
16437
16730
  setLinkPopoverRef.current = setLinkPopover;
16731
+ setFloatingPanelRef.current = setFloatingPanel;
16438
16732
  linkPopoverSessionRef.current = linkPopover;
16733
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
16734
+ useEffect13(() => {
16735
+ const syncViewport = () => {
16736
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
16737
+ setEditorViewport((prev) => prev === next ? prev : next);
16738
+ };
16739
+ syncViewport();
16740
+ window.addEventListener("resize", syncViewport);
16741
+ return () => window.removeEventListener("resize", syncViewport);
16742
+ }, []);
16439
16743
  const {
16440
16744
  navDragRef,
16441
16745
  navDropSlots,
@@ -17763,14 +18067,15 @@ function OhhwellsBridge() {
17763
18067
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
17764
18068
  }
17765
18069
  applyBrandChrome(content);
18070
+ initSectionInstancesFromContent(content, window.location.pathname);
17766
18071
  for (const [key, val] of Object.entries(content)) {
17767
18072
  if (key === "__ohw_sections") continue;
17768
18073
  if (key === AI_SECTIONS_KEY) continue;
18074
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
18075
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
17769
18076
  if (key === BRAND_KIT_KEY) continue;
17770
18077
  if (key === STYLE_STORE_KEY) continue;
17771
18078
  if (BRAND_CHROME_KEYS.has(key)) continue;
17772
- if (key === LOGO_PLACEHOLDER_KEY) continue;
17773
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
17774
18079
  if (applyVideoSettingNode(key, val)) continue;
17775
18080
  if (applyCarouselNode(key, val)) continue;
17776
18081
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -17818,7 +18123,6 @@ function OhhwellsBridge() {
17818
18123
  if (isEditModeRef.current) requestMissingSocialIconsRef.current();
17819
18124
  enforceLinkHrefs();
17820
18125
  initSectionsFromContent(content, true);
17821
- initSectionInstancesFromContent(content, window.location.pathname);
17822
18126
  sectionsLoadedRef.current = true;
17823
18127
  pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
17824
18128
  if (imageLoads.length === 0) return Promise.resolve();
@@ -17830,16 +18134,22 @@ function OhhwellsBridge() {
17830
18134
  };
17831
18135
  const cached = contentCache.get(subdomain);
17832
18136
  if (cached) {
18137
+ setShowBranding(brandingCache.get(subdomain) ?? false);
17833
18138
  applyContent(cached).finally(() => setFetchState("done"));
17834
18139
  return;
17835
18140
  }
17836
18141
  let cancelled = false;
17837
18142
  setFetchState("loading");
17838
18143
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
17839
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18144
+ const initialPath = pathname;
18145
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
18146
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
17840
18147
  if (cancelled) return;
17841
18148
  const content = data?.content ?? {};
18149
+ const branding = Boolean(data?.showBranding);
17842
18150
  contentCache.set(subdomain, content);
18151
+ brandingCache.set(subdomain, branding);
18152
+ setShowBranding(branding);
17843
18153
  return applyContent(content);
17844
18154
  }).catch(() => {
17845
18155
  }).finally(() => {
@@ -17972,11 +18282,11 @@ function OhhwellsBridge() {
17972
18282
  for (const [key, val] of Object.entries(content)) {
17973
18283
  if (key === "__ohw_sections") continue;
17974
18284
  if (key === AI_SECTIONS_KEY) continue;
18285
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
18286
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
17975
18287
  if (key === BRAND_KIT_KEY) continue;
17976
18288
  if (key === STYLE_STORE_KEY) continue;
17977
18289
  if (BRAND_CHROME_KEYS.has(key)) continue;
17978
- if (key === LOGO_PLACEHOLDER_KEY) continue;
17979
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
17980
18290
  if (applyVideoSettingNode(key, val)) continue;
17981
18291
  if (applyCarouselNode(key, val)) continue;
17982
18292
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -18022,6 +18332,17 @@ function OhhwellsBridge() {
18022
18332
  debounceTimer = setTimeout(applyFromCache, 150);
18023
18333
  };
18024
18334
  applyFromCache();
18335
+ const pathCacheKey = `${subdomain}::${pathname}`;
18336
+ if (!fetchedContentPaths.has(pathCacheKey)) {
18337
+ fetchedContentPaths.add(pathCacheKey);
18338
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
18339
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18340
+ if (!data?.content) return;
18341
+ contentCache.set(subdomain, data.content);
18342
+ applyFromCache();
18343
+ }).catch(() => {
18344
+ });
18345
+ }
18025
18346
  observer = new MutationObserver(scheduleApply);
18026
18347
  observer.observe(document.body, { childList: true, subtree: true });
18027
18348
  return () => {
@@ -18135,25 +18456,13 @@ function OhhwellsBridge() {
18135
18456
  };
18136
18457
  const t1 = setTimeout(measure, 50);
18137
18458
  const t2 = setTimeout(measure, 500);
18138
- let lastWidth = window.innerWidth;
18139
- let resizeTimers = [];
18140
- const clearResizeTimers = () => {
18141
- resizeTimers.forEach(clearTimeout);
18142
- resizeTimers = [];
18143
- };
18144
- const handleResize = () => {
18145
- if (window.innerWidth === lastWidth) return;
18146
- lastWidth = window.innerWidth;
18147
- clearResizeTimers();
18148
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
18149
- };
18150
- window.addEventListener("resize", handleResize);
18459
+ const ro = new ResizeObserver(schedule);
18460
+ ro.observe(document.body);
18151
18461
  return () => {
18152
18462
  clearTimeout(t1);
18153
18463
  clearTimeout(t2);
18154
18464
  if (raf != null) cancelAnimationFrame(raf);
18155
- clearResizeTimers();
18156
- window.removeEventListener("resize", handleResize);
18465
+ ro.disconnect();
18157
18466
  };
18158
18467
  }, [pathname, isEditMode, postToParent2]);
18159
18468
  useEffect13(() => {
@@ -18399,9 +18708,6 @@ function OhhwellsBridge() {
18399
18708
  if (target.closest("[data-ohw-state-toggle]")) return;
18400
18709
  if (target.closest("[data-ohw-max-badge]")) return;
18401
18710
  if (isInsideLinkEditor(target)) return;
18402
- if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
18403
- clearMediaSelectionRef.current();
18404
- }
18405
18711
  if (isInsideFloatingPanel(target)) return;
18406
18712
  if (target.closest("[data-ohw-form-toolbar]")) return;
18407
18713
  if (target.closest(
@@ -18409,6 +18715,9 @@ function OhhwellsBridge() {
18409
18715
  )) {
18410
18716
  return;
18411
18717
  }
18718
+ if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
18719
+ clearMediaSelectionRef.current();
18720
+ }
18412
18721
  {
18413
18722
  const formEl = getFormElement(target);
18414
18723
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -18560,14 +18869,6 @@ function OhhwellsBridge() {
18560
18869
  }
18561
18870
  const clickedButton = findClosestButtonLike(target);
18562
18871
  const buttonOnMedia = Boolean(clickedButton && isMediaEditable(editable) && editable.contains(clickedButton));
18563
- console.log("[click-debug]", {
18564
- editableType: editable.dataset.ohwEditable,
18565
- editableTag: editable.tagName,
18566
- targetTag: target.tagName,
18567
- clickedButtonTag: clickedButton?.tagName ?? null,
18568
- buttonOnMedia,
18569
- isMediaEditableEditable: isMediaEditable(editable)
18570
- });
18571
18872
  if (isMediaEditable(editable) && !buttonOnMedia) {
18572
18873
  e.preventDefault();
18573
18874
  e.stopPropagation();
@@ -18594,11 +18895,6 @@ function OhhwellsBridge() {
18594
18895
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
18595
18896
  const hrefCtx = getHrefKeyFromElement(hrefLookupTarget);
18596
18897
  const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
18597
- console.log("[click-debug 2]", {
18598
- hrefLookupTargetTag: hrefLookupTarget.tagName,
18599
- hrefCtx: hrefCtx ? { key: hrefCtx.key } : null,
18600
- navAnchorTag: navAnchor?.tagName ?? null
18601
- });
18602
18898
  if (navAnchor) {
18603
18899
  e.preventDefault();
18604
18900
  e.stopPropagation();
@@ -18768,6 +19064,9 @@ function OhhwellsBridge() {
18768
19064
  setHoveredItemRect(null);
18769
19065
  hoveredNavContainerRef.current = null;
18770
19066
  setHoveredNavContainerRect(null);
19067
+ siblingHintElRef.current = null;
19068
+ setSiblingHintRect(null);
19069
+ setSiblingHintRects([]);
18771
19070
  return;
18772
19071
  }
18773
19072
  {
@@ -18886,7 +19185,6 @@ function OhhwellsBridge() {
18886
19185
  hoveredNavContainerRef.current = null;
18887
19186
  setHoveredNavContainerRect(null);
18888
19187
  hoveredItemElRef.current = editable;
18889
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
18890
19188
  }
18891
19189
  }
18892
19190
  }
@@ -19183,7 +19481,7 @@ function OhhwellsBridge() {
19183
19481
  }
19184
19482
  };
19185
19483
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
19186
- if (linkPopoverOpenRef.current) {
19484
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
19187
19485
  if (hoveredImageRef.current) {
19188
19486
  hoveredImageRef.current = null;
19189
19487
  hoveredImageHasTextOverlapRef.current = false;
@@ -19548,8 +19846,7 @@ function OhhwellsBridge() {
19548
19846
  };
19549
19847
  const handleMouseMove = (e) => {
19550
19848
  const { clientX, clientY } = e;
19551
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
19552
- if (isOverEditorChrome(clientX, clientY)) {
19849
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
19553
19850
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
19554
19851
  formHoverElRef.current = null;
19555
19852
  setFormHoverRect(null);
@@ -19557,6 +19854,12 @@ function OhhwellsBridge() {
19557
19854
  setHoveredItemRect(null);
19558
19855
  hoveredNavContainerRef.current = null;
19559
19856
  setHoveredNavContainerRect(null);
19857
+ siblingHintElRef.current = null;
19858
+ setSiblingHintRect(null);
19859
+ setSiblingHintRects([]);
19860
+ dismissImageHover();
19861
+ clearImageHover();
19862
+ setSectionGap(null);
19560
19863
  return;
19561
19864
  }
19562
19865
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -19568,7 +19871,11 @@ function OhhwellsBridge() {
19568
19871
  if (e.data?.type !== "ow:pointer-sync") return;
19569
19872
  const { clientX, clientY } = e.data;
19570
19873
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
19571
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
19874
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
19875
+ dismissImageHover();
19876
+ clearImageHover();
19877
+ return;
19878
+ }
19572
19879
  if (probeSocialsRowAt(clientX, clientY)) return;
19573
19880
  probeSectionGapAt(clientX, clientY);
19574
19881
  probeImageAt(clientX, clientY);
@@ -19868,6 +20175,7 @@ function OhhwellsBridge() {
19868
20175
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
19869
20176
  }
19870
20177
  applyBrandChrome(content);
20178
+ initSectionInstancesFromContent(content, window.location.pathname);
19871
20179
  let sectionsJson = null;
19872
20180
  for (const [key, val] of Object.entries(content)) {
19873
20181
  if (key === "__ohw_sections") {
@@ -19875,11 +20183,11 @@ function OhhwellsBridge() {
19875
20183
  continue;
19876
20184
  }
19877
20185
  if (key === AI_SECTIONS_KEY) continue;
20186
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
20187
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
19878
20188
  if (key === BRAND_KIT_KEY) continue;
19879
20189
  if (key === STYLE_STORE_KEY) continue;
19880
20190
  if (BRAND_CHROME_KEYS.has(key)) continue;
19881
- if (key === LOGO_PLACEHOLDER_KEY) continue;
19882
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
19883
20191
  if (applyVideoSettingNode(key, val)) continue;
19884
20192
  if (applyCarouselNode(key, val)) continue;
19885
20193
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -19909,7 +20217,6 @@ function OhhwellsBridge() {
19909
20217
  sectionsLoadedRef.current = true;
19910
20218
  pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
19911
20219
  }
19912
- initSectionInstancesFromContent(content, window.location.pathname);
19913
20220
  editContentRef.current = { ...editContentRef.current, ...content };
19914
20221
  reconcileNavbarItemsFromContent(editContentRef.current);
19915
20222
  reconcileFooterOrderFromContent(editContentRef.current);
@@ -20054,12 +20361,35 @@ function OhhwellsBridge() {
20054
20361
  window.addEventListener("message", handleAiSetBrand);
20055
20362
  const handleAiSetStyles = (e) => {
20056
20363
  if (e.data?.type !== "ow:ai-set-styles") return;
20057
- const value = typeof e.data.value === "string" ? e.data.value : "";
20364
+ let value = typeof e.data.value === "string" ? e.data.value : "";
20058
20365
  const previous = stylesRef.current;
20366
+ let previousSections;
20367
+ const store = parseStyleStore(value);
20368
+ if (store) {
20369
+ const folded = foldAlignIntoTrees(parseAiSectionsState(aiSectionsRef.current), store);
20370
+ if (folded.changed) {
20371
+ const nextSections = serializeAiSectionsState(folded.state);
20372
+ if (nextSections !== aiSectionsRef.current) {
20373
+ previousSections = aiSectionsRef.current;
20374
+ aiSectionsRef.current = nextSections;
20375
+ applyAiSectionsToDom(folded.state);
20376
+ postToParentRef.current({
20377
+ type: "ow:change",
20378
+ nodes: [{ key: AI_SECTIONS_KEY, text: nextSections }]
20379
+ });
20380
+ }
20381
+ value = JSON.stringify(folded.store);
20382
+ }
20383
+ }
20059
20384
  stylesRef.current = value;
20060
20385
  applyStylesToDom(parseStyleStore(value));
20061
20386
  postToParentRef.current({ type: "ow:change", nodes: [{ key: STYLE_STORE_KEY, text: value }] });
20062
- postToParentRef.current({ type: "ow:ai-styles-applied", previous, value });
20387
+ postToParentRef.current({
20388
+ type: "ow:ai-styles-applied",
20389
+ previous,
20390
+ value,
20391
+ ...previousSections !== void 0 ? { previousSections } : {}
20392
+ });
20063
20393
  };
20064
20394
  window.addEventListener("message", handleAiSetStyles);
20065
20395
  const handleGetBrand = (e) => {
@@ -20131,6 +20461,34 @@ function OhhwellsBridge() {
20131
20461
  });
20132
20462
  };
20133
20463
  window.addEventListener("message", handleDeleteSection);
20464
+ const handleDuplicateSection = (e) => {
20465
+ if (e.data?.type !== "ow:duplicate-section") return;
20466
+ const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
20467
+ if (!instanceId) return;
20468
+ const newId = newInstanceId();
20469
+ const currentEntries = getPageSectionOrderEntries(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname);
20470
+ const result = duplicateSectionInstance(instanceId, newId, window.location.pathname, currentEntries);
20471
+ if (!result) return;
20472
+ const { entries, keyRekeys } = result;
20473
+ const orderJson = JSON.stringify(entries);
20474
+ const nodes = [{ key: SECTION_ORDER_KEY, text: orderJson }];
20475
+ for (const { from, to } of keyRekeys) {
20476
+ const inherited = editContentRef.current[from];
20477
+ if (inherited !== void 0) nodes.push({ key: to, text: inherited });
20478
+ }
20479
+ editContentRef.current = {
20480
+ ...editContentRef.current,
20481
+ ...Object.fromEntries(nodes.map(({ key, text }) => [key, text]))
20482
+ };
20483
+ setAiSectionOrder(orderJson, window.location.pathname);
20484
+ postToParentRef.current({ type: "ow:change", nodes });
20485
+ window.dispatchEvent(new Event("resize"));
20486
+ const duplicateHeight = document.body.scrollHeight;
20487
+ if (duplicateHeight > 50) postToParentRef.current({ type: "ow:height", height: duplicateHeight });
20488
+ const clone = document.querySelector(`[data-ohw-instance="${CSS.escape(newId)}"]`);
20489
+ if (clone) aiSectionApiRef.current?.selectFromElement(clone);
20490
+ };
20491
+ window.addEventListener("message", handleDuplicateSection);
20134
20492
  const handleDeactivate = (e) => {
20135
20493
  if (e.data?.type !== "ow:deactivate") return;
20136
20494
  if (document.documentElement.hasAttribute("data-ohw-panel-dragging")) return;
@@ -20140,6 +20498,12 @@ function OhhwellsBridge() {
20140
20498
  closeLinkPopoverRef.current();
20141
20499
  return;
20142
20500
  }
20501
+ if (floatingPanelOpenRef.current) {
20502
+ setFloatingPanelRef.current(null);
20503
+ deselectRef.current();
20504
+ deactivateRef.current();
20505
+ return;
20506
+ }
20143
20507
  deselectRef.current();
20144
20508
  deactivateRef.current();
20145
20509
  clearMediaSelectionRef.current();
@@ -20414,8 +20778,12 @@ function OhhwellsBridge() {
20414
20778
  if (inserted) {
20415
20779
  const tracker = getSectionsTracker();
20416
20780
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
20417
- const h = document.body.scrollHeight;
20418
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
20781
+ const reportHeight = () => {
20782
+ const h = document.body.scrollHeight;
20783
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
20784
+ };
20785
+ reportHeight();
20786
+ setTimeout(reportHeight, 500);
20419
20787
  }
20420
20788
  };
20421
20789
  const handleSwitchSchedule = (e) => {
@@ -20817,11 +21185,12 @@ function OhhwellsBridge() {
20817
21185
  window.removeEventListener("message", handleMoveSection);
20818
21186
  window.removeEventListener("message", handlePanelDragging);
20819
21187
  window.removeEventListener("message", handleDeleteSection);
21188
+ window.removeEventListener("message", handleDuplicateSection);
20820
21189
  window.removeEventListener("message", handleDeactivate);
20821
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
20822
21190
  window.removeEventListener("message", handleToastAction);
20823
21191
  window.removeEventListener("message", handleFormCount);
20824
21192
  window.removeEventListener("message", handleUiEscape);
21193
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
20825
21194
  autoSaveTimers.current.forEach(clearTimeout);
20826
21195
  autoSaveTimers.current.clear();
20827
21196
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -21024,7 +21393,7 @@ function OhhwellsBridge() {
21024
21393
  postToParent2({
21025
21394
  type: "ow:ready",
21026
21395
  version: "1",
21027
- bridgeVersion: "0.1.81",
21396
+ bridgeVersion: "0.1.84",
21028
21397
  path: pathname,
21029
21398
  nodes: collectEditableNodes(editContentRef.current),
21030
21399
  sections
@@ -21487,6 +21856,7 @@ function OhhwellsBridge() {
21487
21856
  return /* @__PURE__ */ jsxs20(Fragment8, { children: [
21488
21857
  /* @__PURE__ */ jsx33("div", { id: "ohw-loader", suppressHydrationWarning: true, style: { ...OHW_LOADER_STYLE, display: "none" }, children: /* @__PURE__ */ jsx33(OhwLoaderSpinner, {}) }),
21489
21858
  /* @__PURE__ */ jsx33("script", { suppressHydrationWarning: true, dangerouslySetInnerHTML: { __html: OHW_LOADER_PREHYDRATE_SCRIPT } }),
21859
+ subdomain && !isEditMode && showBranding && /* @__PURE__ */ jsx33(MadeWithOhhWells, {}),
21490
21860
  bridgeRoot ? createPortal2(
21491
21861
  /* @__PURE__ */ jsxs20(Fragment8, { children: [
21492
21862
  /* @__PURE__ */ jsx33("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
@@ -21942,6 +22312,59 @@ function OhhwellsBridge() {
21942
22312
  ) : null
21943
22313
  ] });
21944
22314
  }
22315
+
22316
+ // src/ui/EmptySection.tsx
22317
+ import Link3 from "next/link";
22318
+ import { Fragment as Fragment9, jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
22319
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
22320
+ return /* @__PURE__ */ jsxs21(Fragment9, { children: [
22321
+ /* @__PURE__ */ jsx34(
22322
+ "p",
22323
+ {
22324
+ style: {
22325
+ fontFamily: "var(--brand-font-body)",
22326
+ fontSize: "0.75rem",
22327
+ fontWeight: 500,
22328
+ letterSpacing: "0.15em",
22329
+ textTransform: "uppercase",
22330
+ color: "var(--brand-accent)",
22331
+ marginBottom: "1.5rem"
22332
+ },
22333
+ children: /* @__PURE__ */ jsx34(Link3, { href: homeHref, style: { color: "inherit" }, children: /* @__PURE__ */ jsx34("span", { ...eyebrowKey ? { "data-ohw-editable": "text", "data-ohw-key": eyebrowKey } : {}, children: "Home" }) })
22334
+ }
22335
+ ),
22336
+ /* @__PURE__ */ jsx34(
22337
+ "h1",
22338
+ {
22339
+ style: {
22340
+ fontFamily: "var(--brand-font-heading)",
22341
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
22342
+ lineHeight: 1.1,
22343
+ letterSpacing: "-0.025em",
22344
+ color: "var(--brand-text)",
22345
+ marginBottom: "1rem"
22346
+ },
22347
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
22348
+ children: title
22349
+ }
22350
+ ),
22351
+ /* @__PURE__ */ jsx34(
22352
+ "p",
22353
+ {
22354
+ style: {
22355
+ fontFamily: "var(--brand-font-body)",
22356
+ fontSize: "1rem",
22357
+ lineHeight: 1.7,
22358
+ fontWeight: 300,
22359
+ color: "var(--brand-text-muted)",
22360
+ maxWidth: "340px"
22361
+ },
22362
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
22363
+ children: "This page doesn't have any content yet."
22364
+ }
22365
+ )
22366
+ ] });
22367
+ }
21945
22368
  export {
21946
22369
  AI_DEFAULT_BRAND,
21947
22370
  AI_TREE_SCHEMA_VERSIONS,
@@ -21958,6 +22381,7 @@ export {
21958
22381
  DropdownMenuItem,
21959
22382
  DropdownMenuSeparator,
21960
22383
  DropdownMenuTrigger,
22384
+ EmptySection,
21961
22385
  ItemActionToolbar,
21962
22386
  ItemInteractionLayer,
21963
22387
  LinkEditorPanel,