@ohhwells/bridge 0.1.83 → 0.1.84-next.249

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,10 +491,24 @@ 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
514
  if (el.hasAttribute(attr)) return;
@@ -450,6 +552,10 @@ function clearNodeProps(root) {
450
552
  function buttonSurfaceOf(el) {
451
553
  return el.closest("a, button") ?? el;
452
554
  }
555
+ function alignSubjectOf(el) {
556
+ const button = el.closest('[data-ohw-role="button"]');
557
+ return button?.parentElement ?? el;
558
+ }
453
559
  function applyStylesToDom(store) {
454
560
  ensureStyleSheet();
455
561
  clearSectionAttrs(document);
@@ -495,6 +601,18 @@ function applyStylesToDom(store) {
495
601
  el.style.setProperty("font-size", `${override.fontSize}px`, "important");
496
602
  el.setAttribute(NODE_WROTE_ATTR, "");
497
603
  }
604
+ if (override.align !== void 0) {
605
+ const subject = alignSubjectOf(el);
606
+ saveInline(subject, "text-align");
607
+ saveInline(subject, "justify-content");
608
+ subject.style.setProperty("text-align", override.align, "important");
609
+ subject.style.setProperty(
610
+ "justify-content",
611
+ ALIGN_JUSTIFY[override.align] ?? "flex-start",
612
+ "important"
613
+ );
614
+ subject.setAttribute(NODE_WROTE_ATTR, "");
615
+ }
498
616
  if (override.buttonBackground !== void 0 || override.buttonText !== void 0) {
499
617
  const surface = buttonSurfaceOf(el);
500
618
  if (override.buttonBackground !== void 0) {
@@ -590,6 +708,22 @@ function accentBandContext(brand) {
590
708
  function textAttrs(ctx, path) {
591
709
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
592
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");
593
727
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
594
728
  function MediaBox({
595
729
  refValue,
@@ -602,13 +736,17 @@ function MediaBox({
602
736
  const url = refValue ? ctx.resolveMedia(refValue) : null;
603
737
  const isIcon = /^(lucide|simple):/.test(refValue);
604
738
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
605
- 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
+ } : {};
606
743
  if (isIcon) {
607
744
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
608
745
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
609
746
  "span",
610
747
  {
611
748
  "data-ai-icon": refValue,
749
+ ...editAttrs,
612
750
  style: {
613
751
  display: "inline-flex",
614
752
  width: 48,
@@ -703,7 +841,7 @@ function TextBlock({ slots, ctx, path }) {
703
841
  }
704
842
  function SectionHeaderBlock({ node, ctx, path }) {
705
843
  const slots = node.slots ?? {};
706
- const align = slots.alignment === "center" ? "center" : "left";
844
+ const align = node.align ?? (slots.alignment === "center" ? "center" : "left");
707
845
  const children = node.children ?? [];
708
846
  const buttonRowIdx = children.findIndex((c) => c.type === "button-row");
709
847
  const buttonRow = buttonRowIdx >= 0 ? children[buttonRowIdx] : void 0;
@@ -747,7 +885,7 @@ function SectionHeaderBlock({ node, ctx, path }) {
747
885
  display: "flex",
748
886
  gap: AI_TREE_TOKENS.spacing6,
749
887
  marginTop: AI_TREE_TOKENS.spacing8,
750
- justifyContent: align === "center" ? "center" : "flex-start"
888
+ justifyContent: align === "center" ? "center" : align === "right" ? "flex-end" : "flex-start"
751
889
  },
752
890
  children: (buttonRow.children ?? []).map((button, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
753
891
  ButtonEl,
@@ -1080,7 +1218,7 @@ function CardBlock({ node, ctx, path }) {
1080
1218
  editPath: `${path}.media`
1081
1219
  }
1082
1220
  ) : null;
1083
- const centered = slots.alignment === "center";
1221
+ const centered = (node.align ?? slots.alignment) === "center";
1084
1222
  const content = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1085
1223
  "div",
1086
1224
  {
@@ -1494,7 +1632,7 @@ function CollectionBlock({ node, ctx, path }) {
1494
1632
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1495
1633
  "div",
1496
1634
  {
1497
- "data-ai-grid": "",
1635
+ "data-ai-grid": String(itemsPerRow),
1498
1636
  style: {
1499
1637
  display: "grid",
1500
1638
  gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
@@ -1799,11 +1937,25 @@ function AiTreeRenderer({
1799
1937
  }
1800
1938
  })();
1801
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
+ } : {};
1802
1953
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1803
1954
  "section",
1804
1955
  {
1805
1956
  "data-ai-section": tree.tag ?? "",
1806
1957
  ...bgAttrs,
1958
+ "data-ai-responsive": "",
1807
1959
  style: {
1808
1960
  position: "relative",
1809
1961
  padding: `${pad}px 0`,
@@ -1814,12 +1966,13 @@ function AiTreeRenderer({
1814
1966
  color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
1815
1967
  },
1816
1968
  children: [
1817
- 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 }),
1818
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})` } }),
1819
1972
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1820
1973
  "div",
1821
1974
  {
1822
- "data-ai-container": "",
1975
+ "data-ai-section-inner": "",
1823
1976
  style: {
1824
1977
  position: "relative",
1825
1978
  maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
@@ -1830,12 +1983,12 @@ function AiTreeRenderer({
1830
1983
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1831
1984
  "div",
1832
1985
  {
1833
- "data-ai-row": "",
1986
+ "data-ai-columns": "",
1834
1987
  style: {
1835
1988
  display: "grid",
1836
1989
  gridTemplateColumns: "repeat(12, 1fr)",
1837
1990
  gap: AI_TREE_TOKENS.spacing6,
1838
- alignItems: distributed === "space-between" ? "stretch" : (distributed ?? settings.verticalPosition) === "top" ? "start" : "center",
1991
+ alignItems: rowAlignItems(row.align),
1839
1992
  marginTop: r2 > 0 ? AI_TREE_TOKENS.spacing8 : 0
1840
1993
  },
1841
1994
  children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -1845,6 +1998,8 @@ function AiTreeRenderer({
1845
1998
  style: {
1846
1999
  gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`,
1847
2000
  minWidth: 0,
2001
+ // Horizontal placement of the block's content within its column.
2002
+ ...cellAlignStyle(block.align),
1848
2003
  // space-between: each column becomes a flex column whose content spreads over
1849
2004
  // the full row height instead of clumping at the top.
1850
2005
  ...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
@@ -7432,6 +7587,9 @@ function applyFieldType(wrapper, type) {
7432
7587
  el.style.removeProperty("min-height");
7433
7588
  el.style.removeProperty("resize");
7434
7589
  el.removeAttribute("rows");
7590
+ if (el.className) {
7591
+ el.className = el.className.split(/\s+/).filter((token) => !/textarea/i.test(token)).join(" ");
7592
+ }
7435
7593
  };
7436
7594
  const applyDefaults = (el) => {
7437
7595
  el.setAttribute("placeholder", defaults.placeholder);
@@ -7853,6 +8011,7 @@ function MediaOverlay({
7853
8011
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
7854
8012
  );
7855
8013
  }, [isVideo]);
8014
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
7856
8015
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
7857
8016
  const box = {
7858
8017
  position: "fixed",
@@ -7982,17 +8141,17 @@ function MediaOverlay({
7982
8141
  },
7983
8142
  children: [
7984
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 }),
7985
- isVideo ? "Replace video" : "Replace image"
8144
+ replaceLabel
7986
8145
  ]
7987
8146
  }
7988
8147
  ),
7989
- replaceMode === "none" ? null : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8148
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
7990
8149
  Button,
7991
8150
  {
7992
8151
  "data-ohw-media-overlay": "",
7993
8152
  variant: "outline",
7994
8153
  size: "sm",
7995
- "aria-label": isVideo ? "Replace video" : "Replace image",
8154
+ "aria-label": replaceLabel,
7996
8155
  className: "gap-1.5 cursor-pointer hover:bg-background",
7997
8156
  style: {
7998
8157
  ...OVERLAY_BUTTON_STYLE,
@@ -8015,7 +8174,7 @@ function MediaOverlay({
8015
8174
  },
8016
8175
  children: [
8017
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 }),
8018
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
8177
+ replaceMode === "full" ? replaceLabel : null
8019
8178
  ]
8020
8179
  }
8021
8180
  )
@@ -8221,6 +8380,27 @@ function deleteSectionInstance(instanceId, currentPath, existingEntries) {
8221
8380
  function restoreSectionInstance(instanceId, currentPath, existingEntries) {
8222
8381
  return setSectionRemoved(instanceId, currentPath, existingEntries, false);
8223
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
+ }
8224
8404
  function newInstanceId() {
8225
8405
  return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `instance-${Date.now()}-${Math.random().toString(36).slice(2)}`;
8226
8406
  }
@@ -8235,14 +8415,20 @@ function getPageSectionOrderEntries(raw, currentPath) {
8235
8415
  }
8236
8416
  function rekeySectionSubtree(root, instanceId) {
8237
8417
  const suffix = `::${instanceId}`;
8418
+ const pairs = [];
8238
8419
  const rekey = (el, attr) => {
8239
8420
  const current = el.getAttribute(attr);
8240
- 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 });
8241
8426
  };
8242
8427
  if (root.hasAttribute("data-ohw-key")) rekey(root, "data-ohw-key");
8243
8428
  if (root.hasAttribute("data-ohw-href-key")) rekey(root, "data-ohw-href-key");
8244
8429
  root.querySelectorAll("[data-ohw-key]").forEach((el) => rekey(el, "data-ohw-key"));
8245
8430
  root.querySelectorAll("[data-ohw-href-key]").forEach((el) => rekey(el, "data-ohw-href-key"));
8431
+ return pairs;
8246
8432
  }
8247
8433
  function initSectionInstancesFromContent(content, currentPath) {
8248
8434
  document.querySelectorAll("[data-ohw-section]:not([data-ohw-instance])").forEach((el) => {
@@ -11682,7 +11868,7 @@ function applySocialsDisplayToRow(row, display) {
11682
11868
  const icon = item.querySelector(ICON_SELECTOR);
11683
11869
  if (label) label.style.display = display.text ? "" : "none";
11684
11870
  if (icon) icon.style.display = display.icon ? "" : "none";
11685
- layOutIconAndLabel(item, Boolean(display.text && display.icon));
11871
+ layOutIconAndLabel(item, Boolean(display.text));
11686
11872
  });
11687
11873
  allowRowToWrap(row);
11688
11874
  }
@@ -11694,6 +11880,9 @@ function layOutIconAndLabel(item, on) {
11694
11880
  item.style.gap = "";
11695
11881
  item.style.whiteSpace = "";
11696
11882
  item.style.flex = "";
11883
+ item.style.width = "";
11884
+ item.style.height = "";
11885
+ item.style.padding = "";
11697
11886
  return;
11698
11887
  }
11699
11888
  item.style.display = on ? "inline-flex" : "";
@@ -11701,6 +11890,9 @@ function layOutIconAndLabel(item, on) {
11701
11890
  item.style.gap = on ? "8px" : "";
11702
11891
  item.style.whiteSpace = on ? "nowrap" : "";
11703
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" : "";
11704
11896
  }
11705
11897
  function allowRowToWrap(row) {
11706
11898
  const display = row.ownerDocument.defaultView?.getComputedStyle(row).display ?? "";
@@ -12656,6 +12848,15 @@ function resolveLogoDisplayText(text) {
12656
12848
  function isFooterLogoRoot(root) {
12657
12849
  return Boolean(root.closest("footer") || root.closest('[data-ohw-section="footer"]'));
12658
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
+ }
12659
12860
  function imageKeyForRoot(root) {
12660
12861
  return isFooterLogoRoot(root) ? "footer-logo" : "nav-logo-image";
12661
12862
  }
@@ -12705,9 +12906,10 @@ function applyLogoIdentity(text, isPlaceholder) {
12705
12906
  });
12706
12907
  return display;
12707
12908
  }
12708
- function applyLogoImage(url, alt) {
12909
+ function applyLogoImage(url, alt, placement) {
12709
12910
  const displayAlt = resolveLogoDisplayText(alt);
12710
12911
  document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
12912
+ if (placement && (isFooterLogoRoot(root) ? "footer" : "navbar") !== placement) return;
12711
12913
  ensureLogoHrefKey(root);
12712
12914
  const imageKey = imageKeyForRoot(root);
12713
12915
  const textKey = textKeyForRoot(root);
@@ -12828,7 +13030,12 @@ function applyLogoFromContent(content) {
12828
13030
  const logoImageUrl = typeof rawLogoImage === "string" && rawLogoImage.trim() ? rawLogoImage.trim() : null;
12829
13031
  const imageExplicitlyCleared = LOGO_IMAGE_KEYS.some((key) => key in content) && !logoImageUrl;
12830
13032
  const logoIsPlaceholder = LOGO_PLACEHOLDER_KEY in content ? content[LOGO_PLACEHOLDER_KEY] !== "false" : !logoImageUrl && (!logoText.trim() || logoText === PLACEHOLDER_BUSINESS_NAME);
12831
- 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) {
12832
13039
  applyLogoImage(logoImageUrl, logoAlt);
12833
13040
  } else {
12834
13041
  if (imageExplicitlyCleared) applyLogoImage(null, logoAlt);
@@ -12959,6 +13166,7 @@ function readLogoSizeState(content, placement) {
12959
13166
  function getLogoElement(el) {
12960
13167
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
12961
13168
  if (marked) return marked;
13169
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
12962
13170
  const root = el.closest("nav, [data-ohw-nav-root], footer");
12963
13171
  if (!root) return null;
12964
13172
  const anchor = el.closest("a");
@@ -14887,21 +15095,10 @@ function parseSchedulingInsertAfter(insertAfter) {
14887
15095
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
14888
15096
  };
14889
15097
  }
14890
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
14891
- const parsed = parseSchedulingInsertAfter(insertAfter);
14892
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
14893
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
14894
- return { effectiveInsertAfter, insertBefore };
14895
- }
14896
- function getSchedulingMountPoint(insertAfter) {
14897
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
14898
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
14899
- if (!anchorEl && anchor === "scheduling") {
14900
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
14901
- anchorEl = widgets.at(-1) ?? null;
14902
- }
14903
- if (!anchorEl) return null;
14904
- 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 };
14905
15102
  }
14906
15103
  function schedulingMountDepth(insertAfter) {
14907
15104
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -14918,8 +15115,7 @@ function getPageSchedulingEntries(raw) {
14918
15115
  }
14919
15116
  }
14920
15117
  function isSchedulingWidgetMissing(entry) {
14921
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
14922
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
15118
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
14923
15119
  }
14924
15120
  function hasMissingSchedulingWidgets(entries) {
14925
15121
  return entries.some(isSchedulingWidgetMissing);
@@ -14949,16 +15145,17 @@ function initSectionsFromContent(content, removeExisting = false) {
14949
15145
  } catch {
14950
15146
  }
14951
15147
  }
14952
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
14953
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
14954
- 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);
14955
15151
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
14956
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
14957
- 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;
14958
15155
  const container = document.createElement("div");
14959
15156
  container.dataset.ohwSectionContainer = "scheduling";
14960
- if (insertBefore) {
14961
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
15157
+ if (beforeId) {
15158
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
14962
15159
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
14963
15160
  if (!beforePoint) return false;
14964
15161
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -14969,19 +15166,25 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
14969
15166
  }
14970
15167
  tail.insertAdjacentElement("afterend", container);
14971
15168
  }
14972
- const root = (0, import_client2.createRoot)(container);
14973
- (0, import_react_dom3.flushSync)(() => {
14974
- root.render(
14975
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
14976
- SchedulingWidget,
14977
- {
14978
- notifyOnConnect,
14979
- initialScheduleId: scheduleId,
14980
- insertAfter: effectiveInsertAfter
14981
- }
14982
- )
14983
- );
14984
- });
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
+ }
14985
15188
  const tracker = getSectionsTracker();
14986
15189
  let sections = [];
14987
15190
  try {
@@ -14989,10 +15192,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
14989
15192
  } catch {
14990
15193
  }
14991
15194
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
14992
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
15195
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
14993
15196
  sections.push({
14994
15197
  type: "scheduling",
14995
- insertAfter: effectiveInsertAfter,
15198
+ insertAfter: widgetId,
15199
+ anchorId,
15200
+ beforeId: beforeId ?? null,
14996
15201
  pagePath: window.location.pathname,
14997
15202
  ...scheduleId ? { scheduleId } : {}
14998
15203
  });
@@ -15006,7 +15211,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
15006
15211
  for (let i = pending.length - 1; i >= 0; i--) {
15007
15212
  const entry = pending[i];
15008
15213
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
15009
- 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)) {
15010
15216
  pending.splice(i, 1);
15011
15217
  }
15012
15218
  }
@@ -15164,6 +15370,11 @@ function applyLinkByKey(key, val) {
15164
15370
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
15165
15371
  }
15166
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
+ }
15167
15378
  function isInsideFloatingPanel(target) {
15168
15379
  return Boolean(target.closest("[data-ohw-floating-panel]"));
15169
15380
  }
@@ -15171,11 +15382,6 @@ function isPointOverFloatingPanel(clientX, clientY) {
15171
15382
  const el = document.elementFromPoint(clientX, clientY);
15172
15383
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
15173
15384
  }
15174
- function isInsideLinkEditor(target) {
15175
- return Boolean(
15176
- 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"]')
15177
- );
15178
- }
15179
15385
  function getHrefKeyFromElement(el) {
15180
15386
  if (!el) return null;
15181
15387
  const anchor = el.closest("[data-ohw-href-key]");
@@ -15434,7 +15640,7 @@ function getNavigationSelectionParent(el) {
15434
15640
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
15435
15641
  return getFooterLinksContainer();
15436
15642
  }
15437
- 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)) {
15438
15644
  return getNavigationRoot(el);
15439
15645
  }
15440
15646
  return null;
@@ -15649,7 +15855,6 @@ var ICONS = {
15649
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"/>',
15650
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"/>'
15651
15857
  };
15652
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
15653
15858
  var SELECTION_CHROME_GAP2 = 4;
15654
15859
  var TOOLBAR_STROKE_GAP2 = 4;
15655
15860
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -16029,6 +16234,8 @@ function StateToggle({
16029
16234
  );
16030
16235
  }
16031
16236
  var contentCache = /* @__PURE__ */ new Map();
16237
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
16238
+ var brandingCache = /* @__PURE__ */ new Map();
16032
16239
  var OHW_LOADER_STYLE = {
16033
16240
  position: "fixed",
16034
16241
  inset: 0,
@@ -16066,6 +16273,89 @@ function OhwLoaderSpinner() {
16066
16273
  )
16067
16274
  ] });
16068
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
+ }
16069
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){}})();`;
16070
16360
  function resolveSubdomain(subdomainFromQuery) {
16071
16361
  if (subdomainFromQuery) return subdomainFromQuery;
@@ -16125,6 +16415,7 @@ function OhhwellsBridge() {
16125
16415
  }
16126
16416
  }, []);
16127
16417
  const [fetchState, setFetchState] = (0, import_react17.useState)("idle");
16418
+ const [showBranding, setShowBranding] = (0, import_react17.useState)(false);
16128
16419
  const autoSaveTimers = (0, import_react17.useRef)(/* @__PURE__ */ new Map());
16129
16420
  const activeElRef = (0, import_react17.useRef)(null);
16130
16421
  const pointerHeldRef = (0, import_react17.useRef)(false);
@@ -16473,13 +16764,6 @@ function OhhwellsBridge() {
16473
16764
  const [isItemDragging, setIsItemDragging] = (0, import_react17.useState)(false);
16474
16765
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react17.useState)(false);
16475
16766
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
16476
- const [floatingPanel, setFloatingPanel] = (0, import_react17.useState)(null);
16477
- const floatingPanelOpenRef = (0, import_react17.useRef)(false);
16478
- floatingPanelOpenRef.current = floatingPanel !== null;
16479
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react17.useState)(null);
16480
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react17.useState)(null);
16481
- const [editorViewport, setEditorViewport] = (0, import_react17.useState)("desktop");
16482
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react17.useState)(null);
16483
16767
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react17.useState)(null);
16484
16768
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react17.useState)(null);
16485
16769
  const footerDragRef = (0, import_react17.useRef)(null);
@@ -16497,6 +16781,13 @@ function OhhwellsBridge() {
16497
16781
  const brandKitRef = (0, import_react17.useRef)("");
16498
16782
  const stylesRef = (0, import_react17.useRef)("");
16499
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);
16500
16791
  const [sitePages, setSitePages] = (0, import_react17.useState)([]);
16501
16792
  const [sectionsByPath, setSectionsByPath] = (0, import_react17.useState)({});
16502
16793
  const sectionsPrefetchGenRef = (0, import_react17.useRef)(0);
@@ -16505,7 +16796,18 @@ function OhhwellsBridge() {
16505
16796
  const linkPopoverOpenRef = (0, import_react17.useRef)(false);
16506
16797
  const linkPopoverGraceUntilRef = (0, import_react17.useRef)(0);
16507
16798
  setLinkPopoverRef.current = setLinkPopover;
16799
+ setFloatingPanelRef.current = setFloatingPanel;
16508
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
+ }, []);
16509
16811
  const {
16510
16812
  navDragRef,
16511
16813
  navDropSlots,
@@ -17833,14 +18135,15 @@ function OhhwellsBridge() {
17833
18135
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
17834
18136
  }
17835
18137
  applyBrandChrome(content);
18138
+ initSectionInstancesFromContent(content, window.location.pathname);
17836
18139
  for (const [key, val] of Object.entries(content)) {
17837
18140
  if (key === "__ohw_sections") continue;
17838
18141
  if (key === AI_SECTIONS_KEY) continue;
18142
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
18143
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
17839
18144
  if (key === BRAND_KIT_KEY) continue;
17840
18145
  if (key === STYLE_STORE_KEY) continue;
17841
18146
  if (BRAND_CHROME_KEYS.has(key)) continue;
17842
- if (key === LOGO_PLACEHOLDER_KEY) continue;
17843
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
17844
18147
  if (applyVideoSettingNode(key, val)) continue;
17845
18148
  if (applyCarouselNode(key, val)) continue;
17846
18149
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -17888,7 +18191,6 @@ function OhhwellsBridge() {
17888
18191
  if (isEditModeRef.current) requestMissingSocialIconsRef.current();
17889
18192
  enforceLinkHrefs();
17890
18193
  initSectionsFromContent(content, true);
17891
- initSectionInstancesFromContent(content, window.location.pathname);
17892
18194
  sectionsLoadedRef.current = true;
17893
18195
  pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
17894
18196
  if (imageLoads.length === 0) return Promise.resolve();
@@ -17900,16 +18202,22 @@ function OhhwellsBridge() {
17900
18202
  };
17901
18203
  const cached = contentCache.get(subdomain);
17902
18204
  if (cached) {
18205
+ setShowBranding(brandingCache.get(subdomain) ?? false);
17903
18206
  applyContent(cached).finally(() => setFetchState("done"));
17904
18207
  return;
17905
18208
  }
17906
18209
  let cancelled = false;
17907
18210
  setFetchState("loading");
17908
18211
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
17909
- 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) => {
17910
18215
  if (cancelled) return;
17911
18216
  const content = data?.content ?? {};
18217
+ const branding = Boolean(data?.showBranding);
17912
18218
  contentCache.set(subdomain, content);
18219
+ brandingCache.set(subdomain, branding);
18220
+ setShowBranding(branding);
17913
18221
  return applyContent(content);
17914
18222
  }).catch(() => {
17915
18223
  }).finally(() => {
@@ -18042,11 +18350,11 @@ function OhhwellsBridge() {
18042
18350
  for (const [key, val] of Object.entries(content)) {
18043
18351
  if (key === "__ohw_sections") continue;
18044
18352
  if (key === AI_SECTIONS_KEY) continue;
18353
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
18354
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
18045
18355
  if (key === BRAND_KIT_KEY) continue;
18046
18356
  if (key === STYLE_STORE_KEY) continue;
18047
18357
  if (BRAND_CHROME_KEYS.has(key)) continue;
18048
- if (key === LOGO_PLACEHOLDER_KEY) continue;
18049
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
18050
18358
  if (applyVideoSettingNode(key, val)) continue;
18051
18359
  if (applyCarouselNode(key, val)) continue;
18052
18360
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -18092,6 +18400,17 @@ function OhhwellsBridge() {
18092
18400
  debounceTimer = setTimeout(applyFromCache, 150);
18093
18401
  };
18094
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
+ }
18095
18414
  observer = new MutationObserver(scheduleApply);
18096
18415
  observer.observe(document.body, { childList: true, subtree: true });
18097
18416
  return () => {
@@ -18205,25 +18524,13 @@ function OhhwellsBridge() {
18205
18524
  };
18206
18525
  const t1 = setTimeout(measure, 50);
18207
18526
  const t2 = setTimeout(measure, 500);
18208
- let lastWidth = window.innerWidth;
18209
- let resizeTimers = [];
18210
- const clearResizeTimers = () => {
18211
- resizeTimers.forEach(clearTimeout);
18212
- resizeTimers = [];
18213
- };
18214
- const handleResize = () => {
18215
- if (window.innerWidth === lastWidth) return;
18216
- lastWidth = window.innerWidth;
18217
- clearResizeTimers();
18218
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
18219
- };
18220
- window.addEventListener("resize", handleResize);
18527
+ const ro = new ResizeObserver(schedule);
18528
+ ro.observe(document.body);
18221
18529
  return () => {
18222
18530
  clearTimeout(t1);
18223
18531
  clearTimeout(t2);
18224
18532
  if (raf != null) cancelAnimationFrame(raf);
18225
- clearResizeTimers();
18226
- window.removeEventListener("resize", handleResize);
18533
+ ro.disconnect();
18227
18534
  };
18228
18535
  }, [pathname, isEditMode, postToParent2]);
18229
18536
  (0, import_react17.useEffect)(() => {
@@ -18469,9 +18776,6 @@ function OhhwellsBridge() {
18469
18776
  if (target.closest("[data-ohw-state-toggle]")) return;
18470
18777
  if (target.closest("[data-ohw-max-badge]")) return;
18471
18778
  if (isInsideLinkEditor(target)) return;
18472
- if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
18473
- clearMediaSelectionRef.current();
18474
- }
18475
18779
  if (isInsideFloatingPanel(target)) return;
18476
18780
  if (target.closest("[data-ohw-form-toolbar]")) return;
18477
18781
  if (target.closest(
@@ -18479,6 +18783,9 @@ function OhhwellsBridge() {
18479
18783
  )) {
18480
18784
  return;
18481
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
+ }
18482
18789
  {
18483
18790
  const formEl = getFormElement(target);
18484
18791
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -18630,14 +18937,6 @@ function OhhwellsBridge() {
18630
18937
  }
18631
18938
  const clickedButton = findClosestButtonLike(target);
18632
18939
  const buttonOnMedia = Boolean(clickedButton && isMediaEditable(editable) && editable.contains(clickedButton));
18633
- console.log("[click-debug]", {
18634
- editableType: editable.dataset.ohwEditable,
18635
- editableTag: editable.tagName,
18636
- targetTag: target.tagName,
18637
- clickedButtonTag: clickedButton?.tagName ?? null,
18638
- buttonOnMedia,
18639
- isMediaEditableEditable: isMediaEditable(editable)
18640
- });
18641
18940
  if (isMediaEditable(editable) && !buttonOnMedia) {
18642
18941
  e.preventDefault();
18643
18942
  e.stopPropagation();
@@ -18664,11 +18963,6 @@ function OhhwellsBridge() {
18664
18963
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
18665
18964
  const hrefCtx = getHrefKeyFromElement(hrefLookupTarget);
18666
18965
  const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
18667
- console.log("[click-debug 2]", {
18668
- hrefLookupTargetTag: hrefLookupTarget.tagName,
18669
- hrefCtx: hrefCtx ? { key: hrefCtx.key } : null,
18670
- navAnchorTag: navAnchor?.tagName ?? null
18671
- });
18672
18966
  if (navAnchor) {
18673
18967
  e.preventDefault();
18674
18968
  e.stopPropagation();
@@ -18838,6 +19132,9 @@ function OhhwellsBridge() {
18838
19132
  setHoveredItemRect(null);
18839
19133
  hoveredNavContainerRef.current = null;
18840
19134
  setHoveredNavContainerRect(null);
19135
+ siblingHintElRef.current = null;
19136
+ setSiblingHintRect(null);
19137
+ setSiblingHintRects([]);
18841
19138
  return;
18842
19139
  }
18843
19140
  {
@@ -18956,7 +19253,6 @@ function OhhwellsBridge() {
18956
19253
  hoveredNavContainerRef.current = null;
18957
19254
  setHoveredNavContainerRect(null);
18958
19255
  hoveredItemElRef.current = editable;
18959
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
18960
19256
  }
18961
19257
  }
18962
19258
  }
@@ -19253,7 +19549,7 @@ function OhhwellsBridge() {
19253
19549
  }
19254
19550
  };
19255
19551
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
19256
- if (linkPopoverOpenRef.current) {
19552
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
19257
19553
  if (hoveredImageRef.current) {
19258
19554
  hoveredImageRef.current = null;
19259
19555
  hoveredImageHasTextOverlapRef.current = false;
@@ -19618,8 +19914,7 @@ function OhhwellsBridge() {
19618
19914
  };
19619
19915
  const handleMouseMove = (e) => {
19620
19916
  const { clientX, clientY } = e;
19621
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
19622
- if (isOverEditorChrome(clientX, clientY)) {
19917
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
19623
19918
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
19624
19919
  formHoverElRef.current = null;
19625
19920
  setFormHoverRect(null);
@@ -19627,6 +19922,12 @@ function OhhwellsBridge() {
19627
19922
  setHoveredItemRect(null);
19628
19923
  hoveredNavContainerRef.current = null;
19629
19924
  setHoveredNavContainerRect(null);
19925
+ siblingHintElRef.current = null;
19926
+ setSiblingHintRect(null);
19927
+ setSiblingHintRects([]);
19928
+ dismissImageHover();
19929
+ clearImageHover();
19930
+ setSectionGap(null);
19630
19931
  return;
19631
19932
  }
19632
19933
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -19638,7 +19939,11 @@ function OhhwellsBridge() {
19638
19939
  if (e.data?.type !== "ow:pointer-sync") return;
19639
19940
  const { clientX, clientY } = e.data;
19640
19941
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
19641
- 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
+ }
19642
19947
  if (probeSocialsRowAt(clientX, clientY)) return;
19643
19948
  probeSectionGapAt(clientX, clientY);
19644
19949
  probeImageAt(clientX, clientY);
@@ -19938,6 +20243,7 @@ function OhhwellsBridge() {
19938
20243
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
19939
20244
  }
19940
20245
  applyBrandChrome(content);
20246
+ initSectionInstancesFromContent(content, window.location.pathname);
19941
20247
  let sectionsJson = null;
19942
20248
  for (const [key, val] of Object.entries(content)) {
19943
20249
  if (key === "__ohw_sections") {
@@ -19945,11 +20251,11 @@ function OhhwellsBridge() {
19945
20251
  continue;
19946
20252
  }
19947
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;
19948
20256
  if (key === BRAND_KIT_KEY) continue;
19949
20257
  if (key === STYLE_STORE_KEY) continue;
19950
20258
  if (BRAND_CHROME_KEYS.has(key)) continue;
19951
- if (key === LOGO_PLACEHOLDER_KEY) continue;
19952
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
19953
20259
  if (applyVideoSettingNode(key, val)) continue;
19954
20260
  if (applyCarouselNode(key, val)) continue;
19955
20261
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -19979,7 +20285,6 @@ function OhhwellsBridge() {
19979
20285
  sectionsLoadedRef.current = true;
19980
20286
  pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
19981
20287
  }
19982
- initSectionInstancesFromContent(content, window.location.pathname);
19983
20288
  editContentRef.current = { ...editContentRef.current, ...content };
19984
20289
  reconcileNavbarItemsFromContent(editContentRef.current);
19985
20290
  reconcileFooterOrderFromContent(editContentRef.current);
@@ -20124,12 +20429,35 @@ function OhhwellsBridge() {
20124
20429
  window.addEventListener("message", handleAiSetBrand);
20125
20430
  const handleAiSetStyles = (e) => {
20126
20431
  if (e.data?.type !== "ow:ai-set-styles") return;
20127
- const value = typeof e.data.value === "string" ? e.data.value : "";
20432
+ let value = typeof e.data.value === "string" ? e.data.value : "";
20128
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
+ }
20129
20452
  stylesRef.current = value;
20130
20453
  applyStylesToDom(parseStyleStore(value));
20131
20454
  postToParentRef.current({ type: "ow:change", nodes: [{ key: STYLE_STORE_KEY, text: value }] });
20132
- 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
+ });
20133
20461
  };
20134
20462
  window.addEventListener("message", handleAiSetStyles);
20135
20463
  const handleGetBrand = (e) => {
@@ -20201,6 +20529,34 @@ function OhhwellsBridge() {
20201
20529
  });
20202
20530
  };
20203
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);
20204
20560
  const handleDeactivate = (e) => {
20205
20561
  if (e.data?.type !== "ow:deactivate") return;
20206
20562
  if (document.documentElement.hasAttribute("data-ohw-panel-dragging")) return;
@@ -20210,6 +20566,12 @@ function OhhwellsBridge() {
20210
20566
  closeLinkPopoverRef.current();
20211
20567
  return;
20212
20568
  }
20569
+ if (floatingPanelOpenRef.current) {
20570
+ setFloatingPanelRef.current(null);
20571
+ deselectRef.current();
20572
+ deactivateRef.current();
20573
+ return;
20574
+ }
20213
20575
  deselectRef.current();
20214
20576
  deactivateRef.current();
20215
20577
  clearMediaSelectionRef.current();
@@ -20484,8 +20846,12 @@ function OhhwellsBridge() {
20484
20846
  if (inserted) {
20485
20847
  const tracker = getSectionsTracker();
20486
20848
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
20487
- const h = document.body.scrollHeight;
20488
- 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);
20489
20855
  }
20490
20856
  };
20491
20857
  const handleSwitchSchedule = (e) => {
@@ -20887,11 +21253,12 @@ function OhhwellsBridge() {
20887
21253
  window.removeEventListener("message", handleMoveSection);
20888
21254
  window.removeEventListener("message", handlePanelDragging);
20889
21255
  window.removeEventListener("message", handleDeleteSection);
21256
+ window.removeEventListener("message", handleDuplicateSection);
20890
21257
  window.removeEventListener("message", handleDeactivate);
20891
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
20892
21258
  window.removeEventListener("message", handleToastAction);
20893
21259
  window.removeEventListener("message", handleFormCount);
20894
21260
  window.removeEventListener("message", handleUiEscape);
21261
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
20895
21262
  autoSaveTimers.current.forEach(clearTimeout);
20896
21263
  autoSaveTimers.current.clear();
20897
21264
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -21094,7 +21461,7 @@ function OhhwellsBridge() {
21094
21461
  postToParent2({
21095
21462
  type: "ow:ready",
21096
21463
  version: "1",
21097
- bridgeVersion: "0.1.82",
21464
+ bridgeVersion: "0.1.84",
21098
21465
  path: pathname,
21099
21466
  nodes: collectEditableNodes(editContentRef.current),
21100
21467
  sections
@@ -21557,6 +21924,7 @@ function OhhwellsBridge() {
21557
21924
  return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
21558
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, {}) }),
21559
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, {}),
21560
21928
  bridgeRoot ? (0, import_react_dom4.createPortal)(
21561
21929
  /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
21562
21930
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
@@ -22012,6 +22380,59 @@ function OhhwellsBridge() {
22012
22380
  ) : null
22013
22381
  ] });
22014
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
+ }
22015
22436
  // Annotate the CommonJS export names for ESM import in node:
22016
22437
  0 && (module.exports = {
22017
22438
  AI_DEFAULT_BRAND,
@@ -22029,6 +22450,7 @@ function OhhwellsBridge() {
22029
22450
  DropdownMenuItem,
22030
22451
  DropdownMenuSeparator,
22031
22452
  DropdownMenuTrigger,
22453
+ EmptySection,
22032
22454
  ItemActionToolbar,
22033
22455
  ItemInteractionLayer,
22034
22456
  LinkEditorPanel,