@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.cjs CHANGED
@@ -46,6 +46,7 @@ __export(index_exports, {
46
46
  DropdownMenuItem: () => DropdownMenuItem,
47
47
  DropdownMenuSeparator: () => DropdownMenuSeparator,
48
48
  DropdownMenuTrigger: () => DropdownMenuTrigger,
49
+ EmptySection: () => EmptySection,
49
50
  ItemActionToolbar: () => ItemActionToolbar,
50
51
  ItemInteractionLayer: () => ItemInteractionLayer,
51
52
  LinkEditorPanel: () => LinkEditorPanel,
@@ -142,6 +143,7 @@ function isRenderableTree(value) {
142
143
 
143
144
  // src/lib/ai-sections-store.ts
144
145
  var AI_SECTIONS_KEY = "__ohw_ai_sections";
146
+ var AI_SLOT_KEY_PREFIX = "ai.";
145
147
  var EMPTY_AI_SECTIONS = { v: 1, sections: [] };
146
148
  function parseAiSectionsState(raw) {
147
149
  if (!raw) return EMPTY_AI_SECTIONS;
@@ -185,6 +187,63 @@ function applyTreeToState(state, payload) {
185
187
  const others = state.sections.filter((existing) => existing.id !== entry.id);
186
188
  return { ...state, v: 1, sections: [...others, entry] };
187
189
  }
190
+ function foldAlignIntoTrees(state, store) {
191
+ const byId = new Map(state.sections.map((entry) => [entry.id, entry]));
192
+ const nextTrees = /* @__PURE__ */ new Map();
193
+ const treeFor = (id) => {
194
+ const cloned = nextTrees.get(id);
195
+ if (cloned) return cloned;
196
+ const entry = byId.get(id);
197
+ if (!entry) return void 0;
198
+ const fresh = {
199
+ ...entry.tree,
200
+ rows: entry.tree.rows.map((row) => ({ ...row, blocks: row.blocks.map((block) => ({ ...block })) }))
201
+ };
202
+ nextTrees.set(id, fresh);
203
+ return fresh;
204
+ };
205
+ const nodes = {};
206
+ for (const [key, override] of Object.entries(store.nodes)) {
207
+ const match = override.align !== void 0 && key.startsWith(AI_SLOT_KEY_PREFIX) ? key.match(/^ai\.(.+?)\.r(\d+)\.b(\d+)(?:\.|$)/) : null;
208
+ const tree = match ? treeFor(match[1]) : void 0;
209
+ const block = match && tree ? tree.rows[Number(match[2])]?.blocks[Number(match[3])] : void 0;
210
+ if (!block) {
211
+ nodes[key] = override;
212
+ continue;
213
+ }
214
+ block.align = override.align;
215
+ const rest = { ...override };
216
+ delete rest.align;
217
+ if (Object.keys(rest).length > 0) nodes[key] = rest;
218
+ }
219
+ const sections = {};
220
+ for (const [sectionId, override] of Object.entries(store.sections)) {
221
+ const tree = override.align !== void 0 ? treeFor(sectionId) : void 0;
222
+ if (!tree) {
223
+ sections[sectionId] = override;
224
+ continue;
225
+ }
226
+ for (const row of tree.rows) {
227
+ for (const block of row.blocks) block.align = override.align;
228
+ }
229
+ const rest = { ...override };
230
+ delete rest.align;
231
+ if (Object.keys(rest).length > 0) sections[sectionId] = rest;
232
+ }
233
+ if (nextTrees.size === 0) return { state, store, changed: false };
234
+ return {
235
+ state: {
236
+ ...state,
237
+ v: 1,
238
+ sections: state.sections.map((entry) => {
239
+ const tree = nextTrees.get(entry.id);
240
+ return tree ? { ...entry, tree } : entry;
241
+ })
242
+ },
243
+ store: { v: 1, sections, nodes },
244
+ changed: true
245
+ };
246
+ }
188
247
  function removeFromState(state, id) {
189
248
  return { ...state, v: 1, sections: state.sections.filter((entry) => entry.id !== id) };
190
249
  }
@@ -256,6 +315,17 @@ var BRAND_VAR_PREFIX = "--ohw-brand-";
256
315
  var BRAND_VAR_NAMES = ["primary", "accent", "light", "dark", "surface", "border", "muted"].map(
257
316
  (role) => `${BRAND_VAR_PREFIX}${role}`
258
317
  );
318
+ var LEGACY_BRAND_VAR_NAMES = [
319
+ "primary",
320
+ "accent",
321
+ "background",
322
+ "text",
323
+ "text-muted",
324
+ "surface",
325
+ "border",
326
+ "on-primary",
327
+ "navbar-background"
328
+ ].map((role) => `--brand-${role}`);
259
329
  var FONT_VARS = {
260
330
  heading: ["--font-heading", "--font-display", "--brand-font-heading"],
261
331
  body: ["--font-body", "--brand-font-body"]
@@ -264,14 +334,29 @@ var BRAND_FONT_LINK_ID = "ohw-brand-fonts";
264
334
  function brandColorVars(kit) {
265
335
  const { dark, primary, accent, light } = kit.palette;
266
336
  const mix = (a, aPct, b) => `color-mix(in srgb, ${a} ${aPct}%, ${b})`;
337
+ const surface = mix(light, 95, dark);
338
+ const border = mix(light, 85, dark);
339
+ const muted = mix(dark, 62, light);
267
340
  return {
268
341
  [`${BRAND_VAR_PREFIX}primary`]: primary,
269
342
  [`${BRAND_VAR_PREFIX}accent`]: accent,
270
343
  [`${BRAND_VAR_PREFIX}light`]: light,
271
344
  [`${BRAND_VAR_PREFIX}dark`]: dark,
272
- [`${BRAND_VAR_PREFIX}surface`]: mix(light, 95, dark),
273
- [`${BRAND_VAR_PREFIX}border`]: mix(light, 85, dark),
274
- [`${BRAND_VAR_PREFIX}muted`]: mix(dark, 62, light)
345
+ [`${BRAND_VAR_PREFIX}surface`]: surface,
346
+ [`${BRAND_VAR_PREFIX}border`]: border,
347
+ [`${BRAND_VAR_PREFIX}muted`]: muted,
348
+ // The BrandProvider contract every template actually renders from (see LEGACY_BRAND_VAR_NAMES).
349
+ "--brand-primary": primary,
350
+ "--brand-accent": accent,
351
+ "--brand-background": light,
352
+ "--brand-text": dark,
353
+ "--brand-text-muted": muted,
354
+ "--brand-surface": surface,
355
+ "--brand-border": border,
356
+ // Buttons/bands painted in the primary colour assume it's dark/saturated enough to need
357
+ // light text on top — the same assumption LOGO_IMAGE's light-on-dark navbar mark makes.
358
+ "--brand-on-primary": light,
359
+ "--brand-navbar-background": dark
275
360
  };
276
361
  }
277
362
  function parseBrandKit(raw) {
@@ -312,7 +397,7 @@ function loadBrandFonts(families) {
312
397
  function applyBrandToDom(kit) {
313
398
  const root = document.documentElement;
314
399
  if (!kit) {
315
- for (const name of BRAND_VAR_NAMES) root.style.removeProperty(name);
400
+ for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES]) root.style.removeProperty(name);
316
401
  for (const name of [...FONT_VARS.heading, ...FONT_VARS.body]) root.style.removeProperty(name);
317
402
  document.getElementById(BRAND_FONT_LINK_ID)?.remove();
318
403
  return;
@@ -377,6 +462,9 @@ function styleSheetCss() {
377
462
  `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px}px !important; padding-bottom: ${px}px !important; }`
378
463
  );
379
464
  }
465
+ for (const align of ["left", "center", "right"]) {
466
+ rules.push(`[data-ohw-style-align="${align}"] { text-align: ${align} !important; }`);
467
+ }
380
468
  return rules.join("\n");
381
469
  }
382
470
  var STYLE_FONT_LINK_ID = "ohw-style-fonts";
@@ -403,13 +491,29 @@ var SECTION_ATTRS = {
403
491
  textDistribution: "data-ohw-style-distribution",
404
492
  headlineScale: "data-ohw-style-headline",
405
493
  imageAspect: "data-ohw-style-aspect",
406
- spacing: "data-ohw-style-spacing"
494
+ spacing: "data-ohw-style-spacing",
495
+ align: "data-ohw-style-align"
407
496
  };
408
497
  var NODE_WROTE_ATTR = "data-ohw-style-node";
409
- var NODE_PROPS = ["color", "font-family", "font-size", "background"];
498
+ var NODE_PROPS = [
499
+ "color",
500
+ "font-family",
501
+ "font-size",
502
+ "background",
503
+ "text-align",
504
+ "justify-content",
505
+ "align-items"
506
+ ];
507
+ var ALIGN_JUSTIFY = {
508
+ left: "flex-start",
509
+ center: "center",
510
+ right: "flex-end"
511
+ };
410
512
  function saveInline(el, prop) {
411
513
  const attr = `data-ohw-style-prev-${prop}`;
412
- if (!el.hasAttribute(attr)) el.setAttribute(attr, el.style.getPropertyValue(prop));
514
+ if (el.hasAttribute(attr)) return;
515
+ const value = el.style.getPropertyValue(prop) || (prop === "background" ? el.style.getPropertyValue("background-color") : "");
516
+ el.setAttribute(attr, value);
413
517
  }
414
518
  function restoreInline(el, prop) {
415
519
  const attr = `data-ohw-style-prev-${prop}`;
@@ -448,6 +552,13 @@ function clearNodeProps(root) {
448
552
  function buttonSurfaceOf(el) {
449
553
  return el.closest("a, button") ?? el;
450
554
  }
555
+ function alignSubjectOf(el) {
556
+ const button = el.closest('[data-ohw-role="button"]');
557
+ return button?.parentElement ?? el;
558
+ }
559
+ function horizontalFlexProp(el) {
560
+ return getComputedStyle(el).flexDirection.startsWith("column") ? "align-items" : "justify-content";
561
+ }
451
562
  function applyStylesToDom(store) {
452
563
  ensureStyleSheet();
453
564
  clearSectionAttrs(document);
@@ -460,7 +571,8 @@ function applyStylesToDom(store) {
460
571
  const sections = document.querySelectorAll(
461
572
  `[data-ohw-section="${CSS.escape(sectionId)}"]`
462
573
  );
463
- for (const section of Array.from(sections)) {
574
+ for (const marker of Array.from(sections)) {
575
+ const section = marker.querySelector(":scope > [data-ai-section]") ?? marker;
464
576
  for (const [prop, attr] of Object.entries(SECTION_ATTRS)) {
465
577
  const value = override[prop];
466
578
  if (value === void 0) continue;
@@ -492,6 +604,15 @@ function applyStylesToDom(store) {
492
604
  el.style.setProperty("font-size", `${override.fontSize}px`, "important");
493
605
  el.setAttribute(NODE_WROTE_ATTR, "");
494
606
  }
607
+ if (override.align !== void 0) {
608
+ const subject = alignSubjectOf(el);
609
+ const flexProp = horizontalFlexProp(subject);
610
+ saveInline(subject, "text-align");
611
+ saveInline(subject, flexProp);
612
+ subject.style.setProperty("text-align", override.align, "important");
613
+ subject.style.setProperty(flexProp, ALIGN_JUSTIFY[override.align] ?? "flex-start", "important");
614
+ subject.setAttribute(NODE_WROTE_ATTR, "");
615
+ }
495
616
  if (override.buttonBackground !== void 0 || override.buttonText !== void 0) {
496
617
  const surface = buttonSurfaceOf(el);
497
618
  if (override.buttonBackground !== void 0) {
@@ -587,6 +708,22 @@ function accentBandContext(brand) {
587
708
  function textAttrs(ctx, path) {
588
709
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
589
710
  }
711
+ var AI_RESPONSIVE_CSS = [
712
+ "@media (max-width: 960px) {",
713
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
714
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
715
+ "}",
716
+ "@media (max-width: 640px) {",
717
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
718
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
719
+ // Group containers flatten to a column on phones; span placements come along for free.
720
+ " [data-ai-responsive] [data-ai-group] { display: flex !important; flex-direction: column !important; }",
721
+ " [data-ai-responsive] [data-ai-group] > * { grid-column: auto !important; }",
722
+ " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
723
+ " [data-ai-responsive] { overflow-x: hidden; }",
724
+ " [data-ai-responsive] img { max-width: 100%; }",
725
+ "}"
726
+ ].join("\n");
590
727
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
591
728
  function MediaBox({
592
729
  refValue,
@@ -599,13 +736,17 @@ function MediaBox({
599
736
  const url = refValue ? ctx.resolveMedia(refValue) : null;
600
737
  const isIcon = /^(lucide|simple):/.test(refValue);
601
738
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
602
- const editAttrs = ctx.keyFor && editPath && !isIcon ? { "data-ohw-key": ctx.keyFor(editPath), "data-ohw-editable": "image" } : {};
739
+ const editAttrs = ctx.keyFor && editPath ? {
740
+ "data-ohw-key": ctx.keyFor(editPath),
741
+ "data-ohw-editable": isIcon ? "icon" : "image"
742
+ } : {};
603
743
  if (isIcon) {
604
744
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
605
745
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
606
746
  "span",
607
747
  {
608
748
  "data-ai-icon": refValue,
749
+ ...editAttrs,
609
750
  style: {
610
751
  display: "inline-flex",
611
752
  width: 48,
@@ -700,7 +841,7 @@ function TextBlock({ slots, ctx, path }) {
700
841
  }
701
842
  function SectionHeaderBlock({ node, ctx, path }) {
702
843
  const slots = node.slots ?? {};
703
- const align = slots.alignment === "center" ? "center" : "left";
844
+ const align = node.align ?? (slots.alignment === "center" ? "center" : "left");
704
845
  const children = node.children ?? [];
705
846
  const buttonRowIdx = children.findIndex((c) => c.type === "button-row");
706
847
  const buttonRow = buttonRowIdx >= 0 ? children[buttonRowIdx] : void 0;
@@ -744,7 +885,7 @@ function SectionHeaderBlock({ node, ctx, path }) {
744
885
  display: "flex",
745
886
  gap: AI_TREE_TOKENS.spacing6,
746
887
  marginTop: AI_TREE_TOKENS.spacing8,
747
- justifyContent: align === "center" ? "center" : "flex-start"
888
+ justifyContent: align === "center" ? "center" : align === "right" ? "flex-end" : "flex-start"
748
889
  },
749
890
  children: (buttonRow.children ?? []).map((button, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
750
891
  ButtonEl,
@@ -1077,7 +1218,7 @@ function CardBlock({ node, ctx, path }) {
1077
1218
  editPath: `${path}.media`
1078
1219
  }
1079
1220
  ) : null;
1080
- const centered = slots.alignment === "center";
1221
+ const centered = (node.align ?? slots.alignment) === "center";
1081
1222
  const content = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1082
1223
  "div",
1083
1224
  {
@@ -1491,7 +1632,7 @@ function CollectionBlock({ node, ctx, path }) {
1491
1632
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1492
1633
  "div",
1493
1634
  {
1494
- "data-ai-grid": "",
1635
+ "data-ai-grid": String(itemsPerRow),
1495
1636
  style: {
1496
1637
  display: "grid",
1497
1638
  gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
@@ -1796,11 +1937,25 @@ function AiTreeRenderer({
1796
1937
  }
1797
1938
  })();
1798
1939
  const distributed = !isOverlay && settings.textDistribution;
1940
+ const rowAlignItems = (rowAlign) => {
1941
+ if (rowAlign === "top") return "start";
1942
+ if (rowAlign === "bottom") return "end";
1943
+ if (rowAlign === "center" || rowAlign === "stretch") return rowAlign;
1944
+ if (distributed === "space-between") return "stretch";
1945
+ return (distributed ?? settings.verticalPosition) === "top" ? "start" : "center";
1946
+ };
1947
+ const cellAlignStyle = (blockAlign) => blockAlign ? {
1948
+ display: "flex",
1949
+ flexDirection: "column",
1950
+ alignItems: blockAlign === "left" ? "flex-start" : blockAlign === "right" ? "flex-end" : "center",
1951
+ textAlign: blockAlign
1952
+ } : {};
1799
1953
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1800
1954
  "section",
1801
1955
  {
1802
1956
  "data-ai-section": tree.tag ?? "",
1803
1957
  ...bgAttrs,
1958
+ "data-ai-responsive": "",
1804
1959
  style: {
1805
1960
  position: "relative",
1806
1961
  padding: `${pad}px 0`,
@@ -1811,12 +1966,13 @@ function AiTreeRenderer({
1811
1966
  color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
1812
1967
  },
1813
1968
  children: [
1814
- isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
1969
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
1815
1970
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
1971
+ isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
1816
1972
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1817
1973
  "div",
1818
1974
  {
1819
- "data-ai-container": "",
1975
+ "data-ai-section-inner": "",
1820
1976
  style: {
1821
1977
  position: "relative",
1822
1978
  maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
@@ -1827,12 +1983,12 @@ function AiTreeRenderer({
1827
1983
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1828
1984
  "div",
1829
1985
  {
1830
- "data-ai-row": "",
1986
+ "data-ai-columns": "",
1831
1987
  style: {
1832
1988
  display: "grid",
1833
1989
  gridTemplateColumns: "repeat(12, 1fr)",
1834
1990
  gap: AI_TREE_TOKENS.spacing6,
1835
- alignItems: distributed === "space-between" ? "stretch" : (distributed ?? settings.verticalPosition) === "top" ? "start" : "center",
1991
+ alignItems: rowAlignItems(row.align),
1836
1992
  marginTop: r2 > 0 ? AI_TREE_TOKENS.spacing8 : 0
1837
1993
  },
1838
1994
  children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -1842,6 +1998,8 @@ function AiTreeRenderer({
1842
1998
  style: {
1843
1999
  gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`,
1844
2000
  minWidth: 0,
2001
+ // Horizontal placement of the block's content within its column.
2002
+ ...cellAlignStyle(block.align),
1845
2003
  // space-between: each column becomes a flex column whose content spreads over
1846
2004
  // the full row height instead of clumping at the top.
1847
2005
  ...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
@@ -7429,6 +7587,9 @@ function applyFieldType(wrapper, type) {
7429
7587
  el.style.removeProperty("min-height");
7430
7588
  el.style.removeProperty("resize");
7431
7589
  el.removeAttribute("rows");
7590
+ if (el.className) {
7591
+ el.className = el.className.split(/\s+/).filter((token) => !/textarea/i.test(token)).join(" ");
7592
+ }
7432
7593
  };
7433
7594
  const applyDefaults = (el) => {
7434
7595
  el.setAttribute("placeholder", defaults.placeholder);
@@ -7850,6 +8011,7 @@ function MediaOverlay({
7850
8011
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
7851
8012
  );
7852
8013
  }, [isVideo]);
8014
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
7853
8015
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
7854
8016
  const box = {
7855
8017
  position: "fixed",
@@ -7979,17 +8141,17 @@ function MediaOverlay({
7979
8141
  },
7980
8142
  children: [
7981
8143
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
7982
- isVideo ? "Replace video" : "Replace image"
8144
+ replaceLabel
7983
8145
  ]
7984
8146
  }
7985
8147
  ),
7986
- replaceMode === "none" ? null : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8148
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
7987
8149
  Button,
7988
8150
  {
7989
8151
  "data-ohw-media-overlay": "",
7990
8152
  variant: "outline",
7991
8153
  size: "sm",
7992
- "aria-label": isVideo ? "Replace video" : "Replace image",
8154
+ "aria-label": replaceLabel,
7993
8155
  className: "gap-1.5 cursor-pointer hover:bg-background",
7994
8156
  style: {
7995
8157
  ...OVERLAY_BUTTON_STYLE,
@@ -8012,7 +8174,7 @@ function MediaOverlay({
8012
8174
  },
8013
8175
  children: [
8014
8176
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
8015
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
8177
+ replaceMode === "full" ? replaceLabel : null
8016
8178
  ]
8017
8179
  }
8018
8180
  )
@@ -8218,6 +8380,27 @@ function deleteSectionInstance(instanceId, currentPath, existingEntries) {
8218
8380
  function restoreSectionInstance(instanceId, currentPath, existingEntries) {
8219
8381
  return setSectionRemoved(instanceId, currentPath, existingEntries, false);
8220
8382
  }
8383
+ function duplicateSectionInstance(instanceId, newId, currentPath, existingEntries) {
8384
+ const original = findByInstanceId(instanceId);
8385
+ if (!original) return null;
8386
+ const clone = original.cloneNode(true);
8387
+ clone.setAttribute("data-ohw-instance", newId);
8388
+ const keyRekeys = rekeySectionSubtree(clone, newId);
8389
+ original.insertAdjacentElement("afterend", clone);
8390
+ const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
8391
+ const entries = topLevelSections().map((el, order) => {
8392
+ const id = instanceIdOf(el);
8393
+ return {
8394
+ instanceId: id,
8395
+ type: el.getAttribute("data-ohw-section") ?? "",
8396
+ order,
8397
+ pagePath: currentPath,
8398
+ ...byId.get(id)?.removed ? { removed: true } : {}
8399
+ };
8400
+ });
8401
+ applyPersistedOrder(entries);
8402
+ return { entries, keyRekeys };
8403
+ }
8221
8404
  function newInstanceId() {
8222
8405
  return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `instance-${Date.now()}-${Math.random().toString(36).slice(2)}`;
8223
8406
  }
@@ -8232,14 +8415,20 @@ function getPageSectionOrderEntries(raw, currentPath) {
8232
8415
  }
8233
8416
  function rekeySectionSubtree(root, instanceId) {
8234
8417
  const suffix = `::${instanceId}`;
8418
+ const pairs = [];
8235
8419
  const rekey = (el, attr) => {
8236
8420
  const current = el.getAttribute(attr);
8237
- if (current) el.setAttribute(attr, `${current}${suffix}`);
8421
+ if (!current) return;
8422
+ const base = current.includes("::") ? current.slice(0, current.indexOf("::")) : current;
8423
+ const next = `${base}${suffix}`;
8424
+ el.setAttribute(attr, next);
8425
+ pairs.push({ from: current, to: next });
8238
8426
  };
8239
8427
  if (root.hasAttribute("data-ohw-key")) rekey(root, "data-ohw-key");
8240
8428
  if (root.hasAttribute("data-ohw-href-key")) rekey(root, "data-ohw-href-key");
8241
8429
  root.querySelectorAll("[data-ohw-key]").forEach((el) => rekey(el, "data-ohw-key"));
8242
8430
  root.querySelectorAll("[data-ohw-href-key]").forEach((el) => rekey(el, "data-ohw-href-key"));
8431
+ return pairs;
8243
8432
  }
8244
8433
  function initSectionInstancesFromContent(content, currentPath) {
8245
8434
  document.querySelectorAll("[data-ohw-section]:not([data-ohw-instance])").forEach((el) => {
@@ -11679,7 +11868,7 @@ function applySocialsDisplayToRow(row, display) {
11679
11868
  const icon = item.querySelector(ICON_SELECTOR);
11680
11869
  if (label) label.style.display = display.text ? "" : "none";
11681
11870
  if (icon) icon.style.display = display.icon ? "" : "none";
11682
- layOutIconAndLabel(item, Boolean(display.text && display.icon));
11871
+ layOutIconAndLabel(item, Boolean(display.text));
11683
11872
  });
11684
11873
  allowRowToWrap(row);
11685
11874
  }
@@ -11691,6 +11880,9 @@ function layOutIconAndLabel(item, on) {
11691
11880
  item.style.gap = "";
11692
11881
  item.style.whiteSpace = "";
11693
11882
  item.style.flex = "";
11883
+ item.style.width = "";
11884
+ item.style.height = "";
11885
+ item.style.padding = "";
11694
11886
  return;
11695
11887
  }
11696
11888
  item.style.display = on ? "inline-flex" : "";
@@ -11698,6 +11890,9 @@ function layOutIconAndLabel(item, on) {
11698
11890
  item.style.gap = on ? "8px" : "";
11699
11891
  item.style.whiteSpace = on ? "nowrap" : "";
11700
11892
  item.style.flex = on ? "0 0 auto" : "";
11893
+ item.style.width = on ? "auto" : "";
11894
+ item.style.height = on ? "auto" : "";
11895
+ item.style.padding = on ? "0 12px" : "";
11701
11896
  }
11702
11897
  function allowRowToWrap(row) {
11703
11898
  const display = row.ownerDocument.defaultView?.getComputedStyle(row).display ?? "";
@@ -12653,6 +12848,15 @@ function resolveLogoDisplayText(text) {
12653
12848
  function isFooterLogoRoot(root) {
12654
12849
  return Boolean(root.closest("footer") || root.closest('[data-ohw-section="footer"]'));
12655
12850
  }
12851
+ var NAV_IMAGE_KEYS = ["nav-logo-image", "logo-image"];
12852
+ var FOOTER_IMAGE_KEYS = ["footer-logo", "footer-logo-image", "footer-logo-img"];
12853
+ function firstNonEmptyContentValue(content, keys) {
12854
+ for (const key of keys) {
12855
+ const value = content[key];
12856
+ if (typeof value === "string" && value.trim()) return value.trim();
12857
+ }
12858
+ return null;
12859
+ }
12656
12860
  function imageKeyForRoot(root) {
12657
12861
  return isFooterLogoRoot(root) ? "footer-logo" : "nav-logo-image";
12658
12862
  }
@@ -12702,9 +12906,10 @@ function applyLogoIdentity(text, isPlaceholder) {
12702
12906
  });
12703
12907
  return display;
12704
12908
  }
12705
- function applyLogoImage(url, alt) {
12909
+ function applyLogoImage(url, alt, placement) {
12706
12910
  const displayAlt = resolveLogoDisplayText(alt);
12707
12911
  document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
12912
+ if (placement && (isFooterLogoRoot(root) ? "footer" : "navbar") !== placement) return;
12708
12913
  ensureLogoHrefKey(root);
12709
12914
  const imageKey = imageKeyForRoot(root);
12710
12915
  const textKey = textKeyForRoot(root);
@@ -12825,7 +13030,12 @@ function applyLogoFromContent(content) {
12825
13030
  const logoImageUrl = typeof rawLogoImage === "string" && rawLogoImage.trim() ? rawLogoImage.trim() : null;
12826
13031
  const imageExplicitlyCleared = LOGO_IMAGE_KEYS.some((key) => key in content) && !logoImageUrl;
12827
13032
  const logoIsPlaceholder = LOGO_PLACEHOLDER_KEY in content ? content[LOGO_PLACEHOLDER_KEY] !== "false" : !logoImageUrl && (!logoText.trim() || logoText === PLACEHOLDER_BUSINESS_NAME);
12828
- if (logoImageUrl) {
13033
+ const navImageUrl = firstNonEmptyContentValue(content, NAV_IMAGE_KEYS);
13034
+ const footerImageUrl = firstNonEmptyContentValue(content, FOOTER_IMAGE_KEYS);
13035
+ if (navImageUrl && footerImageUrl && navImageUrl !== footerImageUrl) {
13036
+ applyLogoImage(navImageUrl, logoAlt, "navbar");
13037
+ applyLogoImage(footerImageUrl, logoAlt, "footer");
13038
+ } else if (logoImageUrl) {
12829
13039
  applyLogoImage(logoImageUrl, logoAlt);
12830
13040
  } else {
12831
13041
  if (imageExplicitlyCleared) applyLogoImage(null, logoAlt);
@@ -12956,6 +13166,7 @@ function readLogoSizeState(content, placement) {
12956
13166
  function getLogoElement(el) {
12957
13167
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
12958
13168
  if (marked) return marked;
13169
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
12959
13170
  const root = el.closest("nav, [data-ohw-nav-root], footer");
12960
13171
  if (!root) return null;
12961
13172
  const anchor = el.closest("a");
@@ -14884,21 +15095,10 @@ function parseSchedulingInsertAfter(insertAfter) {
14884
15095
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
14885
15096
  };
14886
15097
  }
14887
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
14888
- const parsed = parseSchedulingInsertAfter(insertAfter);
14889
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
14890
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
14891
- return { effectiveInsertAfter, insertBefore };
14892
- }
14893
- function getSchedulingMountPoint(insertAfter) {
14894
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
14895
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
14896
- if (!anchorEl && anchor === "scheduling") {
14897
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
14898
- anchorEl = widgets.at(-1) ?? null;
14899
- }
14900
- if (!anchorEl) return null;
14901
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
15098
+ function resolveEntryAnchor(entry) {
15099
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
15100
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
15101
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
14902
15102
  }
14903
15103
  function schedulingMountDepth(insertAfter) {
14904
15104
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -14915,8 +15115,7 @@ function getPageSchedulingEntries(raw) {
14915
15115
  }
14916
15116
  }
14917
15117
  function isSchedulingWidgetMissing(entry) {
14918
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
14919
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
15118
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
14920
15119
  }
14921
15120
  function hasMissingSchedulingWidgets(entries) {
14922
15121
  return entries.some(isSchedulingWidgetMissing);
@@ -14946,16 +15145,17 @@ function initSectionsFromContent(content, removeExisting = false) {
14946
15145
  } catch {
14947
15146
  }
14948
15147
  }
14949
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
14950
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
14951
- const sectionId = schedulingSectionId(effectiveInsertAfter);
15148
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
15149
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
15150
+ const sectionId = schedulingSectionId(widgetId);
14952
15151
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
14953
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
14954
- if (!mountPoint) return false;
15152
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
15153
+ if (!anchorEl) return false;
15154
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
14955
15155
  const container = document.createElement("div");
14956
15156
  container.dataset.ohwSectionContainer = "scheduling";
14957
- if (insertBefore) {
14958
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
15157
+ if (beforeId) {
15158
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
14959
15159
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
14960
15160
  if (!beforePoint) return false;
14961
15161
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -14966,19 +15166,25 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
14966
15166
  }
14967
15167
  tail.insertAdjacentElement("afterend", container);
14968
15168
  }
14969
- const root = (0, import_client2.createRoot)(container);
14970
- (0, import_react_dom3.flushSync)(() => {
14971
- root.render(
14972
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
14973
- SchedulingWidget,
14974
- {
14975
- notifyOnConnect,
14976
- initialScheduleId: scheduleId,
14977
- insertAfter: effectiveInsertAfter
14978
- }
14979
- )
14980
- );
14981
- });
15169
+ try {
15170
+ const root = (0, import_client2.createRoot)(container);
15171
+ (0, import_react_dom3.flushSync)(() => {
15172
+ root.render(
15173
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
15174
+ SchedulingWidget,
15175
+ {
15176
+ notifyOnConnect,
15177
+ initialScheduleId: scheduleId,
15178
+ insertAfter: widgetId
15179
+ }
15180
+ )
15181
+ );
15182
+ });
15183
+ } catch (err) {
15184
+ console.error("[ow:scheduling] render threw", err);
15185
+ container.remove();
15186
+ return false;
15187
+ }
14982
15188
  const tracker = getSectionsTracker();
14983
15189
  let sections = [];
14984
15190
  try {
@@ -14986,10 +15192,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
14986
15192
  } catch {
14987
15193
  }
14988
15194
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
14989
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
15195
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
14990
15196
  sections.push({
14991
15197
  type: "scheduling",
14992
- insertAfter: effectiveInsertAfter,
15198
+ insertAfter: widgetId,
15199
+ anchorId,
15200
+ beforeId: beforeId ?? null,
14993
15201
  pagePath: window.location.pathname,
14994
15202
  ...scheduleId ? { scheduleId } : {}
14995
15203
  });
@@ -15003,7 +15211,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
15003
15211
  for (let i = pending.length - 1; i >= 0; i--) {
15004
15212
  const entry = pending[i];
15005
15213
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
15006
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId ?? null)) {
15214
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
15215
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
15007
15216
  pending.splice(i, 1);
15008
15217
  }
15009
15218
  }
@@ -15161,6 +15370,11 @@ function applyLinkByKey(key, val) {
15161
15370
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
15162
15371
  }
15163
15372
  }
15373
+ function isInsideLinkEditor(target) {
15374
+ return Boolean(
15375
+ 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"]')
15376
+ );
15377
+ }
15164
15378
  function isInsideFloatingPanel(target) {
15165
15379
  return Boolean(target.closest("[data-ohw-floating-panel]"));
15166
15380
  }
@@ -15168,11 +15382,6 @@ function isPointOverFloatingPanel(clientX, clientY) {
15168
15382
  const el = document.elementFromPoint(clientX, clientY);
15169
15383
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
15170
15384
  }
15171
- function isInsideLinkEditor(target) {
15172
- return Boolean(
15173
- 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"]')
15174
- );
15175
- }
15176
15385
  function getHrefKeyFromElement(el) {
15177
15386
  if (!el) return null;
15178
15387
  const anchor = el.closest("[data-ohw-href-key]");
@@ -15431,7 +15640,7 @@ function getNavigationSelectionParent(el) {
15431
15640
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
15432
15641
  return getFooterLinksContainer();
15433
15642
  }
15434
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
15643
+ 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)) {
15435
15644
  return getNavigationRoot(el);
15436
15645
  }
15437
15646
  return null;
@@ -15646,7 +15855,6 @@ var ICONS = {
15646
15855
  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"/>',
15647
15856
  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"/>'
15648
15857
  };
15649
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
15650
15858
  var SELECTION_CHROME_GAP2 = 4;
15651
15859
  var TOOLBAR_STROKE_GAP2 = 4;
15652
15860
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -16026,6 +16234,8 @@ function StateToggle({
16026
16234
  );
16027
16235
  }
16028
16236
  var contentCache = /* @__PURE__ */ new Map();
16237
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
16238
+ var brandingCache = /* @__PURE__ */ new Map();
16029
16239
  var OHW_LOADER_STYLE = {
16030
16240
  position: "fixed",
16031
16241
  inset: 0,
@@ -16063,6 +16273,89 @@ function OhwLoaderSpinner() {
16063
16273
  )
16064
16274
  ] });
16065
16275
  }
16276
+ function OhwBrandMark() {
16277
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
16278
+ "svg",
16279
+ {
16280
+ width: "16",
16281
+ height: "16",
16282
+ viewBox: "0 0 48 48",
16283
+ fill: "none",
16284
+ "aria-hidden": true,
16285
+ style: { display: "block", flexShrink: 0 },
16286
+ xmlns: "http://www.w3.org/2000/svg",
16287
+ children: [
16288
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
16289
+ "mask",
16290
+ {
16291
+ id: "ohw-badge-mark",
16292
+ style: { maskType: "luminance" },
16293
+ maskUnits: "userSpaceOnUse",
16294
+ x: "0",
16295
+ y: "0",
16296
+ width: "48",
16297
+ height: "48",
16298
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("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" })
16299
+ }
16300
+ ),
16301
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("g", { mask: "url(#ohw-badge-mark)", children: [
16302
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("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" }),
16303
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("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" }),
16304
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("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" }),
16305
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("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" }),
16306
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("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" })
16307
+ ] })
16308
+ ]
16309
+ }
16310
+ );
16311
+ }
16312
+ var OHW_BADGE_STYLE = {
16313
+ position: "fixed",
16314
+ left: 20,
16315
+ bottom: 20,
16316
+ zIndex: 2147483e3,
16317
+ boxSizing: "border-box",
16318
+ display: "inline-flex",
16319
+ alignItems: "center",
16320
+ gap: 0,
16321
+ padding: "6px 8px",
16322
+ margin: 0,
16323
+ background: "#ffffff",
16324
+ border: "1px solid #e7e5e4",
16325
+ borderRadius: 9999,
16326
+ boxShadow: "0 1px 3px rgba(0, 0, 0, 0.1)",
16327
+ color: "#0c0a09",
16328
+ textDecoration: "none",
16329
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
16330
+ };
16331
+ var OHW_BADGE_LABEL_STYLE = {
16332
+ padding: "0 4px",
16333
+ fontSize: 14,
16334
+ lineHeight: "24px",
16335
+ fontWeight: 500,
16336
+ fontStyle: "normal",
16337
+ letterSpacing: "normal",
16338
+ textTransform: "none",
16339
+ color: "#0c0a09",
16340
+ whiteSpace: "nowrap"
16341
+ };
16342
+ function MadeWithOhhWells() {
16343
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
16344
+ "a",
16345
+ {
16346
+ href: "https://ohhwells.com",
16347
+ target: "_blank",
16348
+ rel: "noopener noreferrer",
16349
+ "aria-label": "Made with OhhWells",
16350
+ "data-ohw-badge": "",
16351
+ style: OHW_BADGE_STYLE,
16352
+ children: [
16353
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(OhwBrandMark, {}),
16354
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { style: OHW_BADGE_LABEL_STYLE, children: "Made with OhhWells" })
16355
+ ]
16356
+ }
16357
+ );
16358
+ }
16066
16359
  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){}})();`;
16067
16360
  function resolveSubdomain(subdomainFromQuery) {
16068
16361
  if (subdomainFromQuery) return subdomainFromQuery;
@@ -16122,6 +16415,7 @@ function OhhwellsBridge() {
16122
16415
  }
16123
16416
  }, []);
16124
16417
  const [fetchState, setFetchState] = (0, import_react17.useState)("idle");
16418
+ const [showBranding, setShowBranding] = (0, import_react17.useState)(false);
16125
16419
  const autoSaveTimers = (0, import_react17.useRef)(/* @__PURE__ */ new Map());
16126
16420
  const activeElRef = (0, import_react17.useRef)(null);
16127
16421
  const pointerHeldRef = (0, import_react17.useRef)(false);
@@ -16470,13 +16764,6 @@ function OhhwellsBridge() {
16470
16764
  const [isItemDragging, setIsItemDragging] = (0, import_react17.useState)(false);
16471
16765
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react17.useState)(false);
16472
16766
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
16473
- const [floatingPanel, setFloatingPanel] = (0, import_react17.useState)(null);
16474
- const floatingPanelOpenRef = (0, import_react17.useRef)(false);
16475
- floatingPanelOpenRef.current = floatingPanel !== null;
16476
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react17.useState)(null);
16477
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react17.useState)(null);
16478
- const [editorViewport, setEditorViewport] = (0, import_react17.useState)("desktop");
16479
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react17.useState)(null);
16480
16767
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react17.useState)(null);
16481
16768
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react17.useState)(null);
16482
16769
  const footerDragRef = (0, import_react17.useRef)(null);
@@ -16494,6 +16781,13 @@ function OhhwellsBridge() {
16494
16781
  const brandKitRef = (0, import_react17.useRef)("");
16495
16782
  const stylesRef = (0, import_react17.useRef)("");
16496
16783
  const pendingDeleteUndoRef = (0, import_react17.useRef)(null);
16784
+ const [floatingPanel, setFloatingPanel] = (0, import_react17.useState)(null);
16785
+ const floatingPanelOpenRef = (0, import_react17.useRef)(false);
16786
+ const setFloatingPanelRef = (0, import_react17.useRef)(setFloatingPanel);
16787
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react17.useState)(null);
16788
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react17.useState)(null);
16789
+ const [editorViewport, setEditorViewport] = (0, import_react17.useState)("desktop");
16790
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react17.useState)(null);
16497
16791
  const [sitePages, setSitePages] = (0, import_react17.useState)([]);
16498
16792
  const [sectionsByPath, setSectionsByPath] = (0, import_react17.useState)({});
16499
16793
  const sectionsPrefetchGenRef = (0, import_react17.useRef)(0);
@@ -16502,7 +16796,18 @@ function OhhwellsBridge() {
16502
16796
  const linkPopoverOpenRef = (0, import_react17.useRef)(false);
16503
16797
  const linkPopoverGraceUntilRef = (0, import_react17.useRef)(0);
16504
16798
  setLinkPopoverRef.current = setLinkPopover;
16799
+ setFloatingPanelRef.current = setFloatingPanel;
16505
16800
  linkPopoverSessionRef.current = linkPopover;
16801
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
16802
+ (0, import_react17.useEffect)(() => {
16803
+ const syncViewport = () => {
16804
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
16805
+ setEditorViewport((prev) => prev === next ? prev : next);
16806
+ };
16807
+ syncViewport();
16808
+ window.addEventListener("resize", syncViewport);
16809
+ return () => window.removeEventListener("resize", syncViewport);
16810
+ }, []);
16506
16811
  const {
16507
16812
  navDragRef,
16508
16813
  navDropSlots,
@@ -17830,14 +18135,15 @@ function OhhwellsBridge() {
17830
18135
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
17831
18136
  }
17832
18137
  applyBrandChrome(content);
18138
+ initSectionInstancesFromContent(content, window.location.pathname);
17833
18139
  for (const [key, val] of Object.entries(content)) {
17834
18140
  if (key === "__ohw_sections") continue;
17835
18141
  if (key === AI_SECTIONS_KEY) continue;
18142
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
18143
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
17836
18144
  if (key === BRAND_KIT_KEY) continue;
17837
18145
  if (key === STYLE_STORE_KEY) continue;
17838
18146
  if (BRAND_CHROME_KEYS.has(key)) continue;
17839
- if (key === LOGO_PLACEHOLDER_KEY) continue;
17840
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
17841
18147
  if (applyVideoSettingNode(key, val)) continue;
17842
18148
  if (applyCarouselNode(key, val)) continue;
17843
18149
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -17885,7 +18191,6 @@ function OhhwellsBridge() {
17885
18191
  if (isEditModeRef.current) requestMissingSocialIconsRef.current();
17886
18192
  enforceLinkHrefs();
17887
18193
  initSectionsFromContent(content, true);
17888
- initSectionInstancesFromContent(content, window.location.pathname);
17889
18194
  sectionsLoadedRef.current = true;
17890
18195
  pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
17891
18196
  if (imageLoads.length === 0) return Promise.resolve();
@@ -17897,16 +18202,22 @@ function OhhwellsBridge() {
17897
18202
  };
17898
18203
  const cached = contentCache.get(subdomain);
17899
18204
  if (cached) {
18205
+ setShowBranding(brandingCache.get(subdomain) ?? false);
17900
18206
  applyContent(cached).finally(() => setFetchState("done"));
17901
18207
  return;
17902
18208
  }
17903
18209
  let cancelled = false;
17904
18210
  setFetchState("loading");
17905
18211
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
17906
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18212
+ const initialPath = pathname;
18213
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
18214
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
17907
18215
  if (cancelled) return;
17908
18216
  const content = data?.content ?? {};
18217
+ const branding = Boolean(data?.showBranding);
17909
18218
  contentCache.set(subdomain, content);
18219
+ brandingCache.set(subdomain, branding);
18220
+ setShowBranding(branding);
17910
18221
  return applyContent(content);
17911
18222
  }).catch(() => {
17912
18223
  }).finally(() => {
@@ -18039,11 +18350,11 @@ function OhhwellsBridge() {
18039
18350
  for (const [key, val] of Object.entries(content)) {
18040
18351
  if (key === "__ohw_sections") continue;
18041
18352
  if (key === AI_SECTIONS_KEY) continue;
18353
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
18354
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
18042
18355
  if (key === BRAND_KIT_KEY) continue;
18043
18356
  if (key === STYLE_STORE_KEY) continue;
18044
18357
  if (BRAND_CHROME_KEYS.has(key)) continue;
18045
- if (key === LOGO_PLACEHOLDER_KEY) continue;
18046
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
18047
18358
  if (applyVideoSettingNode(key, val)) continue;
18048
18359
  if (applyCarouselNode(key, val)) continue;
18049
18360
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -18089,6 +18400,17 @@ function OhhwellsBridge() {
18089
18400
  debounceTimer = setTimeout(applyFromCache, 150);
18090
18401
  };
18091
18402
  applyFromCache();
18403
+ const pathCacheKey = `${subdomain}::${pathname}`;
18404
+ if (!fetchedContentPaths.has(pathCacheKey)) {
18405
+ fetchedContentPaths.add(pathCacheKey);
18406
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
18407
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18408
+ if (!data?.content) return;
18409
+ contentCache.set(subdomain, data.content);
18410
+ applyFromCache();
18411
+ }).catch(() => {
18412
+ });
18413
+ }
18092
18414
  observer = new MutationObserver(scheduleApply);
18093
18415
  observer.observe(document.body, { childList: true, subtree: true });
18094
18416
  return () => {
@@ -18202,25 +18524,13 @@ function OhhwellsBridge() {
18202
18524
  };
18203
18525
  const t1 = setTimeout(measure, 50);
18204
18526
  const t2 = setTimeout(measure, 500);
18205
- let lastWidth = window.innerWidth;
18206
- let resizeTimers = [];
18207
- const clearResizeTimers = () => {
18208
- resizeTimers.forEach(clearTimeout);
18209
- resizeTimers = [];
18210
- };
18211
- const handleResize = () => {
18212
- if (window.innerWidth === lastWidth) return;
18213
- lastWidth = window.innerWidth;
18214
- clearResizeTimers();
18215
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
18216
- };
18217
- window.addEventListener("resize", handleResize);
18527
+ const ro = new ResizeObserver(schedule);
18528
+ ro.observe(document.body);
18218
18529
  return () => {
18219
18530
  clearTimeout(t1);
18220
18531
  clearTimeout(t2);
18221
18532
  if (raf != null) cancelAnimationFrame(raf);
18222
- clearResizeTimers();
18223
- window.removeEventListener("resize", handleResize);
18533
+ ro.disconnect();
18224
18534
  };
18225
18535
  }, [pathname, isEditMode, postToParent2]);
18226
18536
  (0, import_react17.useEffect)(() => {
@@ -18466,9 +18776,6 @@ function OhhwellsBridge() {
18466
18776
  if (target.closest("[data-ohw-state-toggle]")) return;
18467
18777
  if (target.closest("[data-ohw-max-badge]")) return;
18468
18778
  if (isInsideLinkEditor(target)) return;
18469
- if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
18470
- clearMediaSelectionRef.current();
18471
- }
18472
18779
  if (isInsideFloatingPanel(target)) return;
18473
18780
  if (target.closest("[data-ohw-form-toolbar]")) return;
18474
18781
  if (target.closest(
@@ -18476,6 +18783,9 @@ function OhhwellsBridge() {
18476
18783
  )) {
18477
18784
  return;
18478
18785
  }
18786
+ if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
18787
+ clearMediaSelectionRef.current();
18788
+ }
18479
18789
  {
18480
18790
  const formEl = getFormElement(target);
18481
18791
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -18627,14 +18937,6 @@ function OhhwellsBridge() {
18627
18937
  }
18628
18938
  const clickedButton = findClosestButtonLike(target);
18629
18939
  const buttonOnMedia = Boolean(clickedButton && isMediaEditable(editable) && editable.contains(clickedButton));
18630
- console.log("[click-debug]", {
18631
- editableType: editable.dataset.ohwEditable,
18632
- editableTag: editable.tagName,
18633
- targetTag: target.tagName,
18634
- clickedButtonTag: clickedButton?.tagName ?? null,
18635
- buttonOnMedia,
18636
- isMediaEditableEditable: isMediaEditable(editable)
18637
- });
18638
18940
  if (isMediaEditable(editable) && !buttonOnMedia) {
18639
18941
  e.preventDefault();
18640
18942
  e.stopPropagation();
@@ -18661,11 +18963,6 @@ function OhhwellsBridge() {
18661
18963
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
18662
18964
  const hrefCtx = getHrefKeyFromElement(hrefLookupTarget);
18663
18965
  const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
18664
- console.log("[click-debug 2]", {
18665
- hrefLookupTargetTag: hrefLookupTarget.tagName,
18666
- hrefCtx: hrefCtx ? { key: hrefCtx.key } : null,
18667
- navAnchorTag: navAnchor?.tagName ?? null
18668
- });
18669
18966
  if (navAnchor) {
18670
18967
  e.preventDefault();
18671
18968
  e.stopPropagation();
@@ -18835,6 +19132,9 @@ function OhhwellsBridge() {
18835
19132
  setHoveredItemRect(null);
18836
19133
  hoveredNavContainerRef.current = null;
18837
19134
  setHoveredNavContainerRect(null);
19135
+ siblingHintElRef.current = null;
19136
+ setSiblingHintRect(null);
19137
+ setSiblingHintRects([]);
18838
19138
  return;
18839
19139
  }
18840
19140
  {
@@ -18953,7 +19253,6 @@ function OhhwellsBridge() {
18953
19253
  hoveredNavContainerRef.current = null;
18954
19254
  setHoveredNavContainerRect(null);
18955
19255
  hoveredItemElRef.current = editable;
18956
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
18957
19256
  }
18958
19257
  }
18959
19258
  }
@@ -19250,7 +19549,7 @@ function OhhwellsBridge() {
19250
19549
  }
19251
19550
  };
19252
19551
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
19253
- if (linkPopoverOpenRef.current) {
19552
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
19254
19553
  if (hoveredImageRef.current) {
19255
19554
  hoveredImageRef.current = null;
19256
19555
  hoveredImageHasTextOverlapRef.current = false;
@@ -19615,8 +19914,7 @@ function OhhwellsBridge() {
19615
19914
  };
19616
19915
  const handleMouseMove = (e) => {
19617
19916
  const { clientX, clientY } = e;
19618
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
19619
- if (isOverEditorChrome(clientX, clientY)) {
19917
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
19620
19918
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
19621
19919
  formHoverElRef.current = null;
19622
19920
  setFormHoverRect(null);
@@ -19624,6 +19922,12 @@ function OhhwellsBridge() {
19624
19922
  setHoveredItemRect(null);
19625
19923
  hoveredNavContainerRef.current = null;
19626
19924
  setHoveredNavContainerRect(null);
19925
+ siblingHintElRef.current = null;
19926
+ setSiblingHintRect(null);
19927
+ setSiblingHintRects([]);
19928
+ dismissImageHover();
19929
+ clearImageHover();
19930
+ setSectionGap(null);
19627
19931
  return;
19628
19932
  }
19629
19933
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -19635,7 +19939,11 @@ function OhhwellsBridge() {
19635
19939
  if (e.data?.type !== "ow:pointer-sync") return;
19636
19940
  const { clientX, clientY } = e.data;
19637
19941
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
19638
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
19942
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
19943
+ dismissImageHover();
19944
+ clearImageHover();
19945
+ return;
19946
+ }
19639
19947
  if (probeSocialsRowAt(clientX, clientY)) return;
19640
19948
  probeSectionGapAt(clientX, clientY);
19641
19949
  probeImageAt(clientX, clientY);
@@ -19935,6 +20243,7 @@ function OhhwellsBridge() {
19935
20243
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
19936
20244
  }
19937
20245
  applyBrandChrome(content);
20246
+ initSectionInstancesFromContent(content, window.location.pathname);
19938
20247
  let sectionsJson = null;
19939
20248
  for (const [key, val] of Object.entries(content)) {
19940
20249
  if (key === "__ohw_sections") {
@@ -19942,11 +20251,11 @@ function OhhwellsBridge() {
19942
20251
  continue;
19943
20252
  }
19944
20253
  if (key === AI_SECTIONS_KEY) continue;
20254
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
20255
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
19945
20256
  if (key === BRAND_KIT_KEY) continue;
19946
20257
  if (key === STYLE_STORE_KEY) continue;
19947
20258
  if (BRAND_CHROME_KEYS.has(key)) continue;
19948
- if (key === LOGO_PLACEHOLDER_KEY) continue;
19949
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
19950
20259
  if (applyVideoSettingNode(key, val)) continue;
19951
20260
  if (applyCarouselNode(key, val)) continue;
19952
20261
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -19976,7 +20285,6 @@ function OhhwellsBridge() {
19976
20285
  sectionsLoadedRef.current = true;
19977
20286
  pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
19978
20287
  }
19979
- initSectionInstancesFromContent(content, window.location.pathname);
19980
20288
  editContentRef.current = { ...editContentRef.current, ...content };
19981
20289
  reconcileNavbarItemsFromContent(editContentRef.current);
19982
20290
  reconcileFooterOrderFromContent(editContentRef.current);
@@ -20121,12 +20429,35 @@ function OhhwellsBridge() {
20121
20429
  window.addEventListener("message", handleAiSetBrand);
20122
20430
  const handleAiSetStyles = (e) => {
20123
20431
  if (e.data?.type !== "ow:ai-set-styles") return;
20124
- const value = typeof e.data.value === "string" ? e.data.value : "";
20432
+ let value = typeof e.data.value === "string" ? e.data.value : "";
20125
20433
  const previous = stylesRef.current;
20434
+ let previousSections;
20435
+ const store = parseStyleStore(value);
20436
+ if (store) {
20437
+ const folded = foldAlignIntoTrees(parseAiSectionsState(aiSectionsRef.current), store);
20438
+ if (folded.changed) {
20439
+ const nextSections = serializeAiSectionsState(folded.state);
20440
+ if (nextSections !== aiSectionsRef.current) {
20441
+ previousSections = aiSectionsRef.current;
20442
+ aiSectionsRef.current = nextSections;
20443
+ applyAiSectionsToDom(folded.state);
20444
+ postToParentRef.current({
20445
+ type: "ow:change",
20446
+ nodes: [{ key: AI_SECTIONS_KEY, text: nextSections }]
20447
+ });
20448
+ }
20449
+ value = JSON.stringify(folded.store);
20450
+ }
20451
+ }
20126
20452
  stylesRef.current = value;
20127
20453
  applyStylesToDom(parseStyleStore(value));
20128
20454
  postToParentRef.current({ type: "ow:change", nodes: [{ key: STYLE_STORE_KEY, text: value }] });
20129
- postToParentRef.current({ type: "ow:ai-styles-applied", previous, value });
20455
+ postToParentRef.current({
20456
+ type: "ow:ai-styles-applied",
20457
+ previous,
20458
+ value,
20459
+ ...previousSections !== void 0 ? { previousSections } : {}
20460
+ });
20130
20461
  };
20131
20462
  window.addEventListener("message", handleAiSetStyles);
20132
20463
  const handleGetBrand = (e) => {
@@ -20198,6 +20529,34 @@ function OhhwellsBridge() {
20198
20529
  });
20199
20530
  };
20200
20531
  window.addEventListener("message", handleDeleteSection);
20532
+ const handleDuplicateSection = (e) => {
20533
+ if (e.data?.type !== "ow:duplicate-section") return;
20534
+ const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
20535
+ if (!instanceId) return;
20536
+ const newId = newInstanceId();
20537
+ const currentEntries = getPageSectionOrderEntries(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname);
20538
+ const result = duplicateSectionInstance(instanceId, newId, window.location.pathname, currentEntries);
20539
+ if (!result) return;
20540
+ const { entries, keyRekeys } = result;
20541
+ const orderJson = JSON.stringify(entries);
20542
+ const nodes = [{ key: SECTION_ORDER_KEY, text: orderJson }];
20543
+ for (const { from, to } of keyRekeys) {
20544
+ const inherited = editContentRef.current[from];
20545
+ if (inherited !== void 0) nodes.push({ key: to, text: inherited });
20546
+ }
20547
+ editContentRef.current = {
20548
+ ...editContentRef.current,
20549
+ ...Object.fromEntries(nodes.map(({ key, text }) => [key, text]))
20550
+ };
20551
+ setAiSectionOrder(orderJson, window.location.pathname);
20552
+ postToParentRef.current({ type: "ow:change", nodes });
20553
+ window.dispatchEvent(new Event("resize"));
20554
+ const duplicateHeight = document.body.scrollHeight;
20555
+ if (duplicateHeight > 50) postToParentRef.current({ type: "ow:height", height: duplicateHeight });
20556
+ const clone = document.querySelector(`[data-ohw-instance="${CSS.escape(newId)}"]`);
20557
+ if (clone) aiSectionApiRef.current?.selectFromElement(clone);
20558
+ };
20559
+ window.addEventListener("message", handleDuplicateSection);
20201
20560
  const handleDeactivate = (e) => {
20202
20561
  if (e.data?.type !== "ow:deactivate") return;
20203
20562
  if (document.documentElement.hasAttribute("data-ohw-panel-dragging")) return;
@@ -20207,6 +20566,12 @@ function OhhwellsBridge() {
20207
20566
  closeLinkPopoverRef.current();
20208
20567
  return;
20209
20568
  }
20569
+ if (floatingPanelOpenRef.current) {
20570
+ setFloatingPanelRef.current(null);
20571
+ deselectRef.current();
20572
+ deactivateRef.current();
20573
+ return;
20574
+ }
20210
20575
  deselectRef.current();
20211
20576
  deactivateRef.current();
20212
20577
  clearMediaSelectionRef.current();
@@ -20481,8 +20846,12 @@ function OhhwellsBridge() {
20481
20846
  if (inserted) {
20482
20847
  const tracker = getSectionsTracker();
20483
20848
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
20484
- const h = document.body.scrollHeight;
20485
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
20849
+ const reportHeight = () => {
20850
+ const h = document.body.scrollHeight;
20851
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
20852
+ };
20853
+ reportHeight();
20854
+ setTimeout(reportHeight, 500);
20486
20855
  }
20487
20856
  };
20488
20857
  const handleSwitchSchedule = (e) => {
@@ -20884,11 +21253,12 @@ function OhhwellsBridge() {
20884
21253
  window.removeEventListener("message", handleMoveSection);
20885
21254
  window.removeEventListener("message", handlePanelDragging);
20886
21255
  window.removeEventListener("message", handleDeleteSection);
21256
+ window.removeEventListener("message", handleDuplicateSection);
20887
21257
  window.removeEventListener("message", handleDeactivate);
20888
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
20889
21258
  window.removeEventListener("message", handleToastAction);
20890
21259
  window.removeEventListener("message", handleFormCount);
20891
21260
  window.removeEventListener("message", handleUiEscape);
21261
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
20892
21262
  autoSaveTimers.current.forEach(clearTimeout);
20893
21263
  autoSaveTimers.current.clear();
20894
21264
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -21091,7 +21461,7 @@ function OhhwellsBridge() {
21091
21461
  postToParent2({
21092
21462
  type: "ow:ready",
21093
21463
  version: "1",
21094
- bridgeVersion: "0.1.81",
21464
+ bridgeVersion: "0.1.84",
21095
21465
  path: pathname,
21096
21466
  nodes: collectEditableNodes(editContentRef.current),
21097
21467
  sections
@@ -21554,6 +21924,7 @@ function OhhwellsBridge() {
21554
21924
  return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
21555
21925
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { id: "ohw-loader", suppressHydrationWarning: true, style: { ...OHW_LOADER_STYLE, display: "none" }, children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(OhwLoaderSpinner, {}) }),
21556
21926
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("script", { suppressHydrationWarning: true, dangerouslySetInnerHTML: { __html: OHW_LOADER_PREHYDRATE_SCRIPT } }),
21927
+ subdomain && !isEditMode && showBranding && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(MadeWithOhhWells, {}),
21557
21928
  bridgeRoot ? (0, import_react_dom4.createPortal)(
21558
21929
  /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
21559
21930
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
@@ -22009,6 +22380,59 @@ function OhhwellsBridge() {
22009
22380
  ) : null
22010
22381
  ] });
22011
22382
  }
22383
+
22384
+ // src/ui/EmptySection.tsx
22385
+ var import_link = __toESM(require("next/link"), 1);
22386
+ var import_jsx_runtime34 = require("react/jsx-runtime");
22387
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
22388
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
22389
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
22390
+ "p",
22391
+ {
22392
+ style: {
22393
+ fontFamily: "var(--brand-font-body)",
22394
+ fontSize: "0.75rem",
22395
+ fontWeight: 500,
22396
+ letterSpacing: "0.15em",
22397
+ textTransform: "uppercase",
22398
+ color: "var(--brand-accent)",
22399
+ marginBottom: "1.5rem"
22400
+ },
22401
+ children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_link.default, { href: homeHref, style: { color: "inherit" }, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { ...eyebrowKey ? { "data-ohw-editable": "text", "data-ohw-key": eyebrowKey } : {}, children: "Home" }) })
22402
+ }
22403
+ ),
22404
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
22405
+ "h1",
22406
+ {
22407
+ style: {
22408
+ fontFamily: "var(--brand-font-heading)",
22409
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
22410
+ lineHeight: 1.1,
22411
+ letterSpacing: "-0.025em",
22412
+ color: "var(--brand-text)",
22413
+ marginBottom: "1rem"
22414
+ },
22415
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
22416
+ children: title
22417
+ }
22418
+ ),
22419
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
22420
+ "p",
22421
+ {
22422
+ style: {
22423
+ fontFamily: "var(--brand-font-body)",
22424
+ fontSize: "1rem",
22425
+ lineHeight: 1.7,
22426
+ fontWeight: 300,
22427
+ color: "var(--brand-text-muted)",
22428
+ maxWidth: "340px"
22429
+ },
22430
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
22431
+ children: "This page doesn't have any content yet."
22432
+ }
22433
+ )
22434
+ ] });
22435
+ }
22012
22436
  // Annotate the CommonJS export names for ESM import in node:
22013
22437
  0 && (module.exports = {
22014
22438
  AI_DEFAULT_BRAND,
@@ -22026,6 +22450,7 @@ function OhhwellsBridge() {
22026
22450
  DropdownMenuItem,
22027
22451
  DropdownMenuSeparator,
22028
22452
  DropdownMenuTrigger,
22453
+ EmptySection,
22029
22454
  ItemActionToolbar,
22030
22455
  ItemInteractionLayer,
22031
22456
  LinkEditorPanel,