@ohhwells/bridge 0.1.92 → 0.1.93-next.281

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -70,6 +70,9 @@ function isRenderableTree(value) {
70
70
  // src/lib/ai-sections-store.ts
71
71
  var AI_SECTIONS_KEY = "__ohw_ai_sections";
72
72
  var AI_SLOT_KEY_PREFIX = "ai.";
73
+ function aiSlotKeyPrefixFor(sectionId) {
74
+ return `${AI_SLOT_KEY_PREFIX}${sectionId}.`;
75
+ }
73
76
  var EMPTY_AI_SECTIONS = { v: 1, sections: [] };
74
77
  function parseAiSectionsState(raw) {
75
78
  if (!raw) return EMPTY_AI_SECTIONS;
@@ -181,6 +184,33 @@ function deleteSectionFromState(state, sectionId) {
181
184
  if (removed.includes(sectionId)) return state;
182
185
  return { ...state, v: 1, sections: state.sections, removed: [...removed, sectionId] };
183
186
  }
187
+ function reapRemovedAiSections(state, store, removedIds, excludeIds = /* @__PURE__ */ new Set()) {
188
+ const generated = new Set(state.sections.map((entry) => entry.id));
189
+ const reapedIds = [...new Set(removedIds)].filter((id) => generated.has(id) && !excludeIds.has(id));
190
+ if (reapedIds.length === 0) {
191
+ return { state, store, reapedIds: [], slotPrefixes: [], changed: false };
192
+ }
193
+ const reaped = new Set(reapedIds);
194
+ const slotPrefixes = reapedIds.map(aiSlotKeyPrefixFor);
195
+ const nextState = {
196
+ ...state,
197
+ v: 1,
198
+ sections: state.sections.filter((entry) => !reaped.has(entry.id))
199
+ };
200
+ let nextStore = store;
201
+ if (store) {
202
+ const sections = {};
203
+ for (const [key, override] of Object.entries(store.sections)) {
204
+ if (!reaped.has(key)) sections[key] = override;
205
+ }
206
+ const nodes = {};
207
+ for (const [key, override] of Object.entries(store.nodes)) {
208
+ if (!slotPrefixes.some((prefix) => key.startsWith(prefix))) nodes[key] = override;
209
+ }
210
+ nextStore = { v: 1, sections, nodes };
211
+ }
212
+ return { state: nextState, store: nextStore, reapedIds, slotPrefixes, changed: true };
213
+ }
184
214
 
185
215
  // src/lib/brand-chrome.ts
186
216
  var BRAND_NAME_KEY = "__ohw_brand_name";
@@ -249,16 +279,17 @@ var LEGACY_BRAND_VAR_NAMES = [
249
279
  "text-muted",
250
280
  "surface",
251
281
  "border",
252
- "on-primary",
253
- "navbar-background"
282
+ "on-primary"
254
283
  ].map((role) => `--brand-${role}`);
255
284
  var FONT_VARS = {
256
285
  heading: ["--font-heading", "--font-display", "--brand-font-heading"],
257
286
  body: ["--font-body", "--brand-font-body"]
258
287
  };
259
288
  var BRAND_FONT_LINK_ID = "ohw-brand-fonts";
260
- function brandColorVars(kit) {
261
- const { dark, primary, accent, light } = kit.palette;
289
+ var CUSTOM_FONT_STYLE_ID = "ohw-brand-custom-fonts";
290
+ var CUSTOM_FONT_FORMATS = /* @__PURE__ */ new Set(["woff2", "woff", "truetype", "opentype"]);
291
+ function brandColorVars(palette) {
292
+ const { dark, primary, accent, light } = palette;
262
293
  const mix = (a, aPct, b) => `color-mix(in srgb, ${a} ${aPct}%, ${b})`;
263
294
  const surface = mix(light, 95, dark);
264
295
  const border = mix(light, 85, dark);
@@ -281,22 +312,42 @@ function brandColorVars(kit) {
281
312
  "--brand-border": border,
282
313
  // Buttons/bands painted in the primary colour assume it's dark/saturated enough to need
283
314
  // light text on top — the same assumption LOGO_IMAGE's light-on-dark navbar mark makes.
284
- "--brand-on-primary": light,
285
- "--brand-navbar-background": dark
315
+ "--brand-on-primary": light
286
316
  };
287
317
  }
318
+ function parseCustomFontWeight(raw) {
319
+ if (typeof raw !== "object" || raw === null) return null;
320
+ const w = raw;
321
+ if (typeof w.weight !== "number" || !Number.isFinite(w.weight)) return null;
322
+ if (typeof w.url !== "string" || !w.url) return null;
323
+ if (typeof w.format !== "string" || !CUSTOM_FONT_FORMATS.has(w.format)) return null;
324
+ const weightEnd = typeof w.weightEnd === "number" && Number.isFinite(w.weightEnd) && w.weightEnd > w.weight ? w.weightEnd : void 0;
325
+ return { weight: w.weight, ...weightEnd !== void 0 ? { weightEnd } : {}, url: w.url, format: w.format };
326
+ }
327
+ function parseCustomFont(raw) {
328
+ if (typeof raw !== "object" || raw === null) return null;
329
+ const f = raw;
330
+ if (typeof f.family !== "string" || !f.family) return null;
331
+ if (typeof f.label !== "string" || !f.label) return null;
332
+ if (!Array.isArray(f.weights)) return null;
333
+ const weights = f.weights.map(parseCustomFontWeight).filter((w) => w !== null);
334
+ if (weights.length === 0) return null;
335
+ return { family: f.family, label: f.label, weights };
336
+ }
288
337
  function parseBrandKit(raw) {
289
338
  if (!raw) return null;
290
339
  try {
291
340
  const parsed = JSON.parse(raw);
292
341
  const p = parsed?.palette;
293
342
  const f = parsed?.fonts;
294
- if (!p || !f || typeof p.dark !== "string" || typeof p.primary !== "string" || typeof p.accent !== "string" || typeof p.light !== "string" || typeof f.heading !== "string" || typeof f.body !== "string") {
295
- return null;
296
- }
343
+ const palette = p && typeof p.dark === "string" && typeof p.primary === "string" && typeof p.accent === "string" && typeof p.light === "string" ? { dark: p.dark, primary: p.primary, accent: p.accent, light: p.light } : void 0;
344
+ const fonts = f && typeof f.heading === "string" && typeof f.body === "string" ? { heading: f.heading, body: f.body } : void 0;
345
+ const customFonts = Array.isArray(parsed?.customFonts) ? parsed.customFonts.map(parseCustomFont).filter((c) => c !== null) : void 0;
346
+ if (!palette && !fonts && !customFonts) return null;
297
347
  return {
298
- palette: { dark: p.dark, primary: p.primary, accent: p.accent, light: p.light },
299
- fonts: { heading: f.heading, body: f.body }
348
+ ...palette ? { palette } : {},
349
+ ...fonts ? { fonts } : {},
350
+ ...customFonts ? { customFonts } : {}
300
351
  };
301
352
  } catch {
302
353
  return null;
@@ -306,11 +357,16 @@ function familyOf(stack) {
306
357
  const first = stack.split(",")[0]?.trim() ?? "";
307
358
  return first.replace(/^['"]|['"]$/g, "");
308
359
  }
360
+ function quoteFamily(family) {
361
+ return `'${family.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
362
+ }
363
+ var WEIGHT_SCALE = [100, 200, 300, 400, 500, 600, 700, 800, 900];
309
364
  function loadBrandFonts(families) {
310
365
  const unique = [...new Set(families.map((f) => f.trim()).filter(Boolean))];
311
366
  if (unique.length === 0) return;
312
- const spec = unique.map((f) => `${f.replace(/ /g, "+")}:400,500,600,700`).join("|");
313
- const href = `https://fonts.googleapis.com/css?family=${spec}&display=swap`;
367
+ const weights = WEIGHT_SCALE.join(";");
368
+ const spec = unique.map((f) => `family=${f.replace(/ /g, "+")}:wght@${weights}`).join("&");
369
+ const href = `https://fonts.googleapis.com/css2?${spec}&display=swap`;
314
370
  let link = document.getElementById(BRAND_FONT_LINK_ID);
315
371
  if (!link) {
316
372
  link = document.createElement("link");
@@ -320,18 +376,54 @@ function loadBrandFonts(families) {
320
376
  }
321
377
  if (link.href !== href) link.href = href;
322
378
  }
379
+ function escapeCssString(value) {
380
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
381
+ }
382
+ function loadCustomFonts(fonts) {
383
+ const list = fonts ?? [];
384
+ let style = document.getElementById(CUSTOM_FONT_STYLE_ID);
385
+ if (list.length === 0) {
386
+ style?.remove();
387
+ return;
388
+ }
389
+ if (!style) {
390
+ style = document.createElement("style");
391
+ style.id = CUSTOM_FONT_STYLE_ID;
392
+ document.head.appendChild(style);
393
+ }
394
+ style.textContent = list.flatMap(
395
+ (font) => font.weights.map((w) => {
396
+ const weightDescriptor = w.weightEnd ? `${w.weight} ${w.weightEnd}` : `${w.weight}`;
397
+ return `@font-face { font-family: '${escapeCssString(font.family)}'; src: url('${escapeCssString(w.url)}') format('${w.format}'); font-weight: ${weightDescriptor}; font-display: swap; }`;
398
+ })
399
+ ).join("\n");
400
+ }
323
401
  function applyBrandToDom(kit) {
324
402
  const root = document.documentElement;
325
403
  if (!kit) {
326
404
  for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES]) root.style.removeProperty(name);
327
405
  for (const name of [...FONT_VARS.heading, ...FONT_VARS.body]) root.style.removeProperty(name);
328
406
  document.getElementById(BRAND_FONT_LINK_ID)?.remove();
407
+ document.getElementById(CUSTOM_FONT_STYLE_ID)?.remove();
329
408
  return;
330
409
  }
331
- for (const [name, value] of Object.entries(brandColorVars(kit))) root.style.setProperty(name, value);
332
- for (const name of FONT_VARS.heading) root.style.setProperty(name, kit.fonts.heading);
333
- for (const name of FONT_VARS.body) root.style.setProperty(name, kit.fonts.body);
334
- loadBrandFonts([familyOf(kit.fonts.heading), familyOf(kit.fonts.body)]);
410
+ if (kit.palette) {
411
+ for (const [name, value] of Object.entries(brandColorVars(kit.palette))) root.style.setProperty(name, value);
412
+ }
413
+ if (kit.customFonts) {
414
+ loadCustomFonts(kit.customFonts);
415
+ }
416
+ if (kit.fonts) {
417
+ const heading = quoteFamily(kit.fonts.heading);
418
+ const body = quoteFamily(kit.fonts.body);
419
+ for (const name of FONT_VARS.heading) root.style.setProperty(name, heading);
420
+ for (const name of FONT_VARS.body) root.style.setProperty(name, body);
421
+ const customFamilies = new Set((kit.customFonts ?? []).map((f) => f.family));
422
+ const googleFamilies = [familyOf(kit.fonts.heading), familyOf(kit.fonts.body)].filter(
423
+ (f) => !customFamilies.has(f)
424
+ );
425
+ loadBrandFonts(googleFamilies);
426
+ }
335
427
  }
336
428
 
337
429
  // src/lib/section-styles.ts
@@ -388,6 +480,12 @@ function styleSheetCss() {
388
480
  `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px}px !important; padding-bottom: ${px}px !important; }`
389
481
  );
390
482
  }
483
+ rules.push(
484
+ `[data-ohw-style-corners="sharp"] :is(.card, [data-ohw-card], img, picture, figure, [data-ohw-editable="bg-image"]) { border-radius: 0 !important; }`,
485
+ `[data-ohw-style-corners="sharp"] :has(> img) { border-radius: 0 !important; }`,
486
+ `[data-ohw-style-corners="rounded"] :is(.card, [data-ohw-card], img, picture, figure, [data-ohw-editable="bg-image"]) { border-radius: max(0.75rem, var(--radius, 0px)) !important; }`,
487
+ `[data-ohw-style-corners="rounded"] :has(> img) { border-radius: max(0.75rem, var(--radius, 0px)) !important; }`
488
+ );
391
489
  for (const align of ["left", "center", "right"]) {
392
490
  rules.push(`[data-ohw-style-align="${align}"] { text-align: ${align} !important; }`);
393
491
  }
@@ -418,6 +516,7 @@ var SECTION_ATTRS = {
418
516
  headlineScale: "data-ohw-style-headline",
419
517
  imageAspect: "data-ohw-style-aspect",
420
518
  spacing: "data-ohw-style-spacing",
519
+ cornerStyle: "data-ohw-style-corners",
421
520
  align: "data-ohw-style-align"
422
521
  };
423
522
  var NODE_WROTE_ATTR = "data-ohw-style-node";
@@ -480,14 +579,39 @@ function buttonSurfaceOf(el) {
480
579
  }
481
580
  function alignSubjectOf(el) {
482
581
  const button = el.closest('[data-ohw-role="button"]');
483
- return button?.parentElement ?? el;
582
+ if (button?.parentElement) return button.parentElement;
583
+ let subject = el;
584
+ while (subject.parentElement && !subject.parentElement.hasAttribute("data-ohw-section") && /^inline/.test(getComputedStyle(subject).display)) {
585
+ subject = subject.parentElement;
586
+ }
587
+ const TEXT_ROW = /^(P|LI|H[1-6]|BLOCKQUOTE|FIGCAPTION|DT|DD)$/;
588
+ const parent = subject.parentElement;
589
+ if (parent && TEXT_ROW.test(parent.tagName) && /^(inline-)?flex$/.test(getComputedStyle(parent).display)) {
590
+ return parent;
591
+ }
592
+ if (parent && !parent.hasAttribute("data-ohw-section")) {
593
+ const pcs = getComputedStyle(parent);
594
+ const children = Array.from(parent.children);
595
+ const textRowChild = (c) => c.hasAttribute("data-ohw-editable") || (c.textContent ?? "").trim().length === 0 || c.getAttribute("aria-hidden") === "true";
596
+ if (/^(inline-)?flex$/.test(pcs.display) && pcs.flexDirection.startsWith("row") && children.length > 0 && children.some((c) => c.hasAttribute("data-ohw-editable")) && children.every(textRowChild)) {
597
+ return parent;
598
+ }
599
+ }
600
+ const row = subject.closest("li");
601
+ if (row && row !== subject && /^(inline-)?flex$/.test(getComputedStyle(row).display)) {
602
+ return row;
603
+ }
604
+ return subject;
484
605
  }
485
606
  function applyStylesToDom(store) {
486
607
  ensureStyleSheet();
487
608
  clearSectionAttrs(document);
488
609
  clearNodeProps(document);
489
610
  loadStyleFonts(
490
- store ? Object.values(store.nodes).flatMap((n) => n.fontFamily ? [n.fontFamily] : []) : []
611
+ store ? [
612
+ ...Object.values(store.nodes).flatMap((n) => n.fontFamily ? [n.fontFamily] : []),
613
+ ...Object.values(store.sections).flatMap((s) => s.fontFamily ? [s.fontFamily] : [])
614
+ ] : []
491
615
  );
492
616
  if (!store) return;
493
617
  for (const [sectionId, override] of Object.entries(store.sections)) {
@@ -507,6 +631,23 @@ function applyStylesToDom(store) {
507
631
  section.style.setProperty("background", override.sectionBackgroundColor, "important");
508
632
  section.setAttribute("data-ohw-style-bgcolor", "");
509
633
  }
634
+ if (override.textColor !== void 0 || override.fontFamily !== void 0) {
635
+ const textEls = marker.querySelectorAll(
636
+ '[data-ohw-editable="text"], [data-ohw-editable="plain"], [data-ohw-field-label]'
637
+ );
638
+ for (const el of Array.from(textEls)) {
639
+ if (override.textColor !== void 0) {
640
+ saveInline(el, "color");
641
+ el.style.setProperty("color", override.textColor, "important");
642
+ el.setAttribute(NODE_WROTE_ATTR, "");
643
+ }
644
+ if (override.fontFamily !== void 0) {
645
+ saveInline(el, "font-family");
646
+ el.style.setProperty("font-family", `'${override.fontFamily}'`, "important");
647
+ el.setAttribute(NODE_WROTE_ATTR, "");
648
+ }
649
+ }
650
+ }
510
651
  }
511
652
  }
512
653
  for (const [key, override] of Object.entries(store.nodes)) {
@@ -553,15 +694,557 @@ function applyStylesToDom(store) {
553
694
  }
554
695
  }
555
696
  }
697
+ for (const [sectionId, override] of Object.entries(store.sections)) {
698
+ if (override.headingColor === void 0) continue;
699
+ const escaped = CSS.escape(sectionId);
700
+ const byInstance = document.querySelectorAll(`[data-ohw-instance="${escaped}"]`);
701
+ const sections = byInstance.length ? byInstance : document.querySelectorAll(`[data-ohw-section="${escaped}"]`);
702
+ for (const marker of Array.from(sections)) {
703
+ const headings = marker.querySelectorAll(
704
+ ":is(h1, h2, h3, h4, h5, h6)[data-ohw-editable], :is(h1, h2, h3, h4, h5, h6) > [data-ohw-editable]"
705
+ );
706
+ for (const el of Array.from(headings)) {
707
+ saveInline(el, "color");
708
+ el.style.setProperty("color", override.headingColor, "important");
709
+ el.setAttribute(NODE_WROTE_ATTR, "");
710
+ }
711
+ }
712
+ }
556
713
  }
557
714
 
558
715
  // src/ui/ai-tree/aiSectionsManager.tsx
559
716
  import { flushSync } from "react-dom";
560
717
  import { createRoot } from "react-dom/client";
561
718
 
719
+ // src/lib/sections.ts
720
+ var LINK_PICKER_EXCLUDED_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
721
+ function isChromeSection(el) {
722
+ return el.matches("header, nav, footer, aside");
723
+ }
724
+ function titleCaseSectionId(id) {
725
+ return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
726
+ }
727
+ function parseSectionsFromRoot(root) {
728
+ const seen = /* @__PURE__ */ new Set();
729
+ const sections = [];
730
+ for (const el of root.querySelectorAll("[data-ohw-section]")) {
731
+ const id = el.getAttribute("data-ohw-section") ?? "";
732
+ if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
733
+ if (el.parentElement?.closest("[data-ohw-section]")) continue;
734
+ if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
735
+ continue;
736
+ seen.add(id);
737
+ const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
738
+ sections.push({ id, label });
739
+ }
740
+ return sections;
741
+ }
742
+ function collectSectionsFromDom() {
743
+ if (typeof document === "undefined") return [];
744
+ return parseSectionsFromRoot(document);
745
+ }
746
+ function parseSectionsFromHtml(html) {
747
+ const doc = new DOMParser().parseFromString(html, "text/html");
748
+ return parseSectionsFromRoot(doc);
749
+ }
750
+
751
+ // src/lib/section-instances.ts
752
+ var SECTION_ORDER_KEY = "__ohw_section_order";
753
+ var REMOVED_ATTR = "data-ohw-section-removed";
754
+ function isRemovedSection(el) {
755
+ return el.hasAttribute(REMOVED_ATTR);
756
+ }
757
+ function movableUnit(el) {
758
+ return el.closest("[data-ohw-section-container]") ?? el;
759
+ }
760
+ function sectionTypeOf(el) {
761
+ return el.getAttribute("data-ohw-section") ?? el.querySelector("[data-ohw-section]")?.getAttribute("data-ohw-section") ?? "";
762
+ }
763
+ function sectionElementOf(el) {
764
+ return el.hasAttribute("data-ohw-section") ? el : el.querySelector("[data-ohw-section]") ?? el;
765
+ }
766
+ function collectTopLevelUnits(predicate) {
767
+ const seen = /* @__PURE__ */ new Set();
768
+ const result = [];
769
+ document.querySelectorAll("[data-ohw-section]").forEach((el) => {
770
+ if (!predicate(el)) return;
771
+ const unit = movableUnit(el);
772
+ if (unit.parentElement?.closest("[data-ohw-section],[data-ohw-section-container]")) return;
773
+ if (seen.has(unit)) return;
774
+ seen.add(unit);
775
+ result.push(unit);
776
+ });
777
+ return result;
778
+ }
779
+ function topLevelSections() {
780
+ return collectTopLevelUnits((el) => !isChromeSection(el) && !isRemovedSection(movableUnit(el)));
781
+ }
782
+ function instanceIdOf(el) {
783
+ return el.dataset.ohwInstance ?? el.dataset.ohwSection ?? "";
784
+ }
785
+ function findByInstanceId(instanceId) {
786
+ const escapedId = CSS.escape(instanceId);
787
+ const direct = document.querySelector(`[data-ohw-instance="${escapedId}"]`);
788
+ if (direct) return movableUnit(direct);
789
+ const bare = document.querySelector(`[data-ohw-section="${escapedId}"]:not([data-ohw-instance])`);
790
+ return bare ? movableUnit(bare) : null;
791
+ }
792
+ function planSectionMove(instanceId, targetIndex, currentPath) {
793
+ const sections = topLevelSections();
794
+ const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
795
+ if (index === -1) return null;
796
+ const dragged = sections[index];
797
+ const others = sections.filter((_, i) => i !== index);
798
+ const clamped = Math.max(0, Math.min(targetIndex, others.length));
799
+ const reordered = [...others.slice(0, clamped), dragged, ...others.slice(clamped)];
800
+ return reordered.map((el, order) => ({
801
+ instanceId: instanceIdOf(el),
802
+ type: sectionTypeOf(el),
803
+ order,
804
+ pagePath: currentPath
805
+ }));
806
+ }
807
+ function moveSectionInstance(instanceId, direction, currentPath) {
808
+ const sections = topLevelSections();
809
+ const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
810
+ if (index === -1) return null;
811
+ const siblingIndex = direction === "up" ? index - 1 : index + 1;
812
+ if (siblingIndex < 0 || siblingIndex >= sections.length) return null;
813
+ const entries = planSectionMove(instanceId, siblingIndex, currentPath);
814
+ if (!entries) return null;
815
+ applyPersistedOrder(entries);
816
+ return entries;
817
+ }
818
+ function syncRemovedFlags(entries) {
819
+ const removedIds = new Set(entries.filter((e) => e.removed).map((e) => e.instanceId));
820
+ document.querySelectorAll(`[${REMOVED_ATTR}]`).forEach((el) => {
821
+ if (!removedIds.has(instanceIdOf(el))) {
822
+ el.style.removeProperty("display");
823
+ el.removeAttribute(REMOVED_ATTR);
824
+ }
825
+ });
826
+ for (const id of removedIds) {
827
+ const el = findByInstanceId(id);
828
+ if (el) {
829
+ el.style.display = "none";
830
+ el.setAttribute(REMOVED_ATTR, "");
831
+ }
832
+ }
833
+ }
834
+ function applyPersistedOrder(entries) {
835
+ syncRemovedFlags(entries);
836
+ if (entries.length === 0) return;
837
+ const sections = topLevelSections();
838
+ if (sections.length === 0) return;
839
+ const orderIndex = new Map(entries.map((e) => [e.instanceId, e.order]));
840
+ const ordered = [...sections].sort((a, b) => {
841
+ const aOrder = orderIndex.get(instanceIdOf(a));
842
+ const bOrder = orderIndex.get(instanceIdOf(b));
843
+ if (aOrder === void 0 && bOrder === void 0) return 0;
844
+ if (aOrder === void 0) return 1;
845
+ if (bOrder === void 0) return -1;
846
+ return aOrder - bOrder;
847
+ });
848
+ let prev = null;
849
+ for (const el of ordered) {
850
+ if (prev) prev.after(el);
851
+ prev = el;
852
+ }
853
+ }
854
+ function setSectionRemoved(instanceId, currentPath, existingEntries, removed) {
855
+ if (!findByInstanceId(instanceId)) return null;
856
+ const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
857
+ const allSections = collectTopLevelUnits((el) => !isChromeSection(el));
858
+ allSections.forEach((el, order) => {
859
+ const id = instanceIdOf(el);
860
+ if (!byId.has(id)) {
861
+ byId.set(id, { instanceId: id, type: sectionTypeOf(el), order, pagePath: currentPath });
862
+ }
863
+ });
864
+ const target = byId.get(instanceId);
865
+ if (!target) return null;
866
+ byId.set(instanceId, { ...target, removed });
867
+ const entries = Array.from(byId.values());
868
+ applyPersistedOrder(entries);
869
+ return entries;
870
+ }
871
+ function deleteSectionInstance(instanceId, currentPath, existingEntries) {
872
+ return setSectionRemoved(instanceId, currentPath, existingEntries, true);
873
+ }
874
+ function restoreSectionInstance(instanceId, currentPath, existingEntries) {
875
+ return setSectionRemoved(instanceId, currentPath, existingEntries, false);
876
+ }
877
+ function duplicateSectionInstance(instanceId, newId, currentPath, existingEntries) {
878
+ const original = findByInstanceId(instanceId);
879
+ if (!original) return null;
880
+ const clone = original.cloneNode(true);
881
+ clone.setAttribute("data-ohw-instance", newId);
882
+ const keyRekeys = rekeySectionSubtree(clone, newId);
883
+ original.insertAdjacentElement("afterend", clone);
884
+ const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
885
+ const entries = topLevelSections().map((el, order) => {
886
+ const id = instanceIdOf(el);
887
+ return {
888
+ instanceId: id,
889
+ type: sectionTypeOf(el),
890
+ order,
891
+ pagePath: currentPath,
892
+ ...byId.get(id)?.removed ? { removed: true } : {}
893
+ };
894
+ });
895
+ applyPersistedOrder(entries);
896
+ return { entries, keyRekeys };
897
+ }
898
+ function newInstanceId() {
899
+ return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `instance-${Date.now()}-${Math.random().toString(36).slice(2)}`;
900
+ }
901
+ function getPageSectionOrderEntries(raw, currentPath) {
902
+ if (!raw) return [];
903
+ try {
904
+ const entries = JSON.parse(raw);
905
+ return entries.filter((e) => !e.pagePath || e.pagePath === currentPath);
906
+ } catch {
907
+ return [];
908
+ }
909
+ }
910
+ function mergePageSectionOrder(raw, currentPath, pageEntries) {
911
+ let all = [];
912
+ if (raw) {
913
+ try {
914
+ const parsed = JSON.parse(raw);
915
+ if (Array.isArray(parsed)) all = parsed;
916
+ } catch {
917
+ }
918
+ }
919
+ const otherPages = all.filter((e) => e && e.pagePath && e.pagePath !== currentPath);
920
+ const pageIds = new Set(pageEntries.map((e) => e.instanceId));
921
+ const removedHere = all.filter(
922
+ (e) => e && (!e.pagePath || e.pagePath === currentPath) && e.removed && !pageIds.has(e.instanceId)
923
+ );
924
+ return [...otherPages, ...removedHere, ...pageEntries];
925
+ }
926
+ function rekeySectionSubtree(root, instanceId) {
927
+ const suffix = `::${instanceId}`;
928
+ const pairs = [];
929
+ const rekey = (el, attr) => {
930
+ const current = el.getAttribute(attr);
931
+ if (!current) return;
932
+ const base = current.includes("::") ? current.slice(0, current.indexOf("::")) : current;
933
+ const next = `${base}${suffix}`;
934
+ el.setAttribute(attr, next);
935
+ pairs.push({ from: current, to: next });
936
+ };
937
+ if (root.hasAttribute("data-ohw-key")) rekey(root, "data-ohw-key");
938
+ if (root.hasAttribute("data-ohw-href-key")) rekey(root, "data-ohw-href-key");
939
+ root.querySelectorAll("[data-ohw-key]").forEach((el) => rekey(el, "data-ohw-key"));
940
+ root.querySelectorAll("[data-ohw-href-key]").forEach((el) => rekey(el, "data-ohw-href-key"));
941
+ return pairs;
942
+ }
943
+ function initSectionInstancesFromContent(content, currentPath) {
944
+ document.querySelectorAll("[data-ohw-section]:not([data-ohw-instance])").forEach((el) => {
945
+ el.setAttribute("data-ohw-instance", el.getAttribute("data-ohw-section") ?? "");
946
+ });
947
+ document.querySelectorAll("[data-ohw-section-container]:not([data-ohw-instance])").forEach((el) => {
948
+ const type = sectionTypeOf(el);
949
+ if (type) el.setAttribute("data-ohw-instance", type);
950
+ });
951
+ const entries = getPageSectionOrderEntries(content[SECTION_ORDER_KEY], currentPath);
952
+ for (const entry of entries) {
953
+ if (entry.instanceId === entry.type) continue;
954
+ if (document.querySelector(`[data-ohw-instance="${CSS.escape(entry.instanceId)}"]`)) continue;
955
+ const original = document.querySelector(
956
+ `[data-ohw-section="${CSS.escape(entry.type)}"][data-ohw-instance="${CSS.escape(entry.type)}"]`
957
+ );
958
+ if (!original) continue;
959
+ const clone = original.cloneNode(true);
960
+ clone.setAttribute("data-ohw-instance", entry.instanceId);
961
+ rekeySectionSubtree(clone, entry.instanceId);
962
+ original.insertAdjacentElement("afterend", clone);
963
+ }
964
+ applyPersistedOrder(entries);
965
+ }
966
+
562
967
  // src/ui/ai-tree/AiTreeRenderer.tsx
563
968
  import React from "react";
564
969
  import { ArrowLeft, ArrowRight, ChevronDown, icons as lucideIcons } from "lucide-react";
970
+
971
+ // src/lib/placeholder-imagery.ts
972
+ var U = (id) => `https://images.unsplash.com/photo-${id}?auto=format&fit=crop&w=1600&q=80`;
973
+ var GENERIC = [
974
+ U("1441986300917-64674bd600d8"),
975
+ U("1486406146926-c627a92ad1ab"),
976
+ U("1497032628192-86f99bcd76bc"),
977
+ U("1521737604893-d14cc237f11d"),
978
+ U("1522071820081-009f0129c71c"),
979
+ U("1519389950473-47ba0277781c"),
980
+ U("1460925895917-afdab827c52f"),
981
+ U("1504384308090-c894fdcc538d")
982
+ ];
983
+ var PEOPLE = [
984
+ U("1500648767791-00dcc994a43e"),
985
+ U("1494790108377-be9c29b29330"),
986
+ U("1507003211169-0a1dd7228f2d"),
987
+ U("1438761681033-6461ffad8d80"),
988
+ U("1544005313-94ddf0286df2"),
989
+ U("1472099645785-5658abf4ff4e"),
990
+ U("1519085360753-af0119f7cbe7"),
991
+ U("1534528741775-53994a69daeb")
992
+ ];
993
+ var THEMED = [
994
+ {
995
+ keywords: ["portrait", "headshot", "person", "people", "team", "staff", "avatar", "founder", "face"],
996
+ pool: PEOPLE
997
+ },
998
+ {
999
+ keywords: ["pet", "dog", "cat", "puppy", "kitten", "vet", "animal"],
1000
+ pool: [
1001
+ U("1548199973-03cce0bbc87b"),
1002
+ U("1450778869180-41d0601e046e"),
1003
+ U("1583511655857-d19b40a7a54e"),
1004
+ U("1587300003388-59208cc962cb"),
1005
+ U("1517849845537-4d257902454a"),
1006
+ U("1601758228041-f3b2795255f1")
1007
+ ]
1008
+ },
1009
+ {
1010
+ keywords: [
1011
+ "baker",
1012
+ "bakery",
1013
+ "cafe",
1014
+ "coffee",
1015
+ "latte",
1016
+ "restaurant",
1017
+ "pastr",
1018
+ "bread",
1019
+ "cake",
1020
+ "cater",
1021
+ "chef",
1022
+ "kitchen",
1023
+ "food",
1024
+ "pizza",
1025
+ "dessert",
1026
+ "brunch",
1027
+ "bistro",
1028
+ "deli",
1029
+ "dish",
1030
+ "menu"
1031
+ ],
1032
+ pool: [
1033
+ U("1509440159596-0249088772ff"),
1034
+ U("1555507036-ab1f4038808a"),
1035
+ U("1517433670267-08bbd4be890f"),
1036
+ U("1486427944299-d1955d23e34d"),
1037
+ U("1504754524776-8f4f37790ca0"),
1038
+ U("1495474472287-4d71bcdd2085"),
1039
+ U("1521017432531-fbd92d768814"),
1040
+ U("1556909114-f6e7ad7d3136")
1041
+ ]
1042
+ },
1043
+ {
1044
+ keywords: [
1045
+ "shop",
1046
+ "store",
1047
+ "boutique",
1048
+ "retail",
1049
+ "clothing",
1050
+ "fashion",
1051
+ "jewel",
1052
+ "gift",
1053
+ "florist",
1054
+ "market",
1055
+ "grocer",
1056
+ "product",
1057
+ "storefront"
1058
+ ],
1059
+ pool: [
1060
+ U("1441984904996-e0b6ba687e04"),
1061
+ U("1472851294608-062f824d29cc"),
1062
+ U("1523381210434-271e8be1f52b"),
1063
+ U("1534452203293-494d7ddbf7e0"),
1064
+ U("1445205170230-053b83016050"),
1065
+ U("1560243563-062bfc001d68")
1066
+ ]
1067
+ },
1068
+ {
1069
+ keywords: [
1070
+ "yoga",
1071
+ "pilates",
1072
+ "fitness",
1073
+ "gym",
1074
+ "workout",
1075
+ "trainer",
1076
+ "wellness",
1077
+ "meditat",
1078
+ "massage",
1079
+ "therap",
1080
+ "physio",
1081
+ "chiro",
1082
+ "nutrition",
1083
+ "spa",
1084
+ "studio"
1085
+ ],
1086
+ pool: [
1087
+ U("1544367567-0f2fcb009e0b"),
1088
+ U("1506126613408-eca07ce68773"),
1089
+ U("1545205597-3d9d02c29597"),
1090
+ U("1552196563-55cd4e45efb3"),
1091
+ U("1518611012118-696072aa579a"),
1092
+ U("1571019613454-1cb2f99b2d8b"),
1093
+ U("1540555700478-4be289fbecef"),
1094
+ U("1519824145371-296894a0daa9")
1095
+ ]
1096
+ },
1097
+ {
1098
+ keywords: [
1099
+ "salon",
1100
+ "hairdress",
1101
+ "haircut",
1102
+ "barber",
1103
+ "manicure",
1104
+ "pedicure",
1105
+ "nails",
1106
+ "beauty",
1107
+ "makeup",
1108
+ "cosmetic",
1109
+ "eyelash",
1110
+ "eyebrow",
1111
+ "skincare",
1112
+ "esthetic",
1113
+ "waxing",
1114
+ "hair"
1115
+ ],
1116
+ pool: [
1117
+ U("1560066984-138dadb4c035"),
1118
+ U("1522337660859-02fbefca4702"),
1119
+ U("1562322140-8baeececf3df"),
1120
+ U("1521590832167-7bcbfaa6381f"),
1121
+ U("1487412947147-5cebf100ffc2"),
1122
+ U("1526045478516-99145907023c")
1123
+ ]
1124
+ },
1125
+ {
1126
+ keywords: [
1127
+ "cleaning",
1128
+ "plumb",
1129
+ "electric",
1130
+ "landscap",
1131
+ "contractor",
1132
+ "handyman",
1133
+ "renov",
1134
+ "hvac",
1135
+ "roofing",
1136
+ "painting",
1137
+ "carpentry",
1138
+ "flooring",
1139
+ "movers",
1140
+ "construction",
1141
+ "tools"
1142
+ ],
1143
+ pool: [
1144
+ U("1581578731548-c64695cc6952"),
1145
+ U("1504307651254-35680f356dfd"),
1146
+ U("1581092160562-40aa08e78837"),
1147
+ U("1621905251189-08b45d6a269e"),
1148
+ U("1558618666-fcd25c85cd64"),
1149
+ U("1585128792020-803d29415281")
1150
+ ]
1151
+ },
1152
+ {
1153
+ keywords: [
1154
+ "legal",
1155
+ "attorney",
1156
+ "lawyer",
1157
+ "account",
1158
+ "bookkeep",
1159
+ "consult",
1160
+ "coaching",
1161
+ "financ",
1162
+ "insurance",
1163
+ "realtor",
1164
+ "estate",
1165
+ "marketing",
1166
+ "agency",
1167
+ "office",
1168
+ "business",
1169
+ "desk"
1170
+ ],
1171
+ pool: [
1172
+ U("1497366216548-37526070297c"),
1173
+ U("1497366811353-6870744d04b2"),
1174
+ U("1454165804606-c3d57bc86b40"),
1175
+ U("1521791136064-7986c2920216"),
1176
+ U("1556761175-b413da4baf72"),
1177
+ U("1542744173-8e7e53415bb0")
1178
+ ]
1179
+ },
1180
+ {
1181
+ keywords: [
1182
+ "wedding",
1183
+ "event",
1184
+ "party",
1185
+ "celebrat",
1186
+ "venue",
1187
+ "community",
1188
+ "nonprofit",
1189
+ "charity",
1190
+ "workshop",
1191
+ "photograph",
1192
+ "concert"
1193
+ ],
1194
+ pool: [
1195
+ U("1511578314322-379afb476865"),
1196
+ U("1501281668745-f7f57925c3b4"),
1197
+ U("1523580494863-6f3031224c94"),
1198
+ U("1540575467063-178a50c2df87"),
1199
+ U("1505236858219-8359eb29e329"),
1200
+ U("1528605248644-14dd04022da1")
1201
+ ]
1202
+ }
1203
+ ];
1204
+ function poolForSubject(subject) {
1205
+ for (const theme of THEMED) {
1206
+ if (theme.keywords.some((k) => subject.includes(k))) {
1207
+ return theme.pool;
1208
+ }
1209
+ }
1210
+ return GENERIC;
1211
+ }
1212
+ function mixedHash(text) {
1213
+ let hash = 2166136261;
1214
+ for (let i = 0; i < text.length; i++) {
1215
+ hash ^= text.charCodeAt(i);
1216
+ hash = Math.imul(hash, 16777619);
1217
+ }
1218
+ return hash >>> 16 & 65535;
1219
+ }
1220
+ function resolvePlaceholderRef(ref) {
1221
+ const match = /^placeholder:([a-z0-9-]+)$/.exec(ref);
1222
+ if (!match) return null;
1223
+ const subject = match[1];
1224
+ const pool = poolForSubject(subject.replace(/-\d+$/, ""));
1225
+ return pool[mixedHash(ref) % pool.length];
1226
+ }
1227
+ function collectPlaceholderRefs(tree) {
1228
+ const seen = /* @__PURE__ */ new Set();
1229
+ for (const match of JSON.stringify(tree ?? null).matchAll(/"(placeholder:[a-z0-9-]+)"/gu)) {
1230
+ seen.add(match[1]);
1231
+ }
1232
+ return [...seen];
1233
+ }
1234
+ function buildPlaceholderMap(tree) {
1235
+ const map = {};
1236
+ const cursor = /* @__PURE__ */ new Map();
1237
+ for (const ref of collectPlaceholderRefs(tree)) {
1238
+ const subject = ref.slice("placeholder:".length).replace(/-\d+$/, "");
1239
+ const pool = poolForSubject(subject);
1240
+ const start = cursor.get(pool) ?? mixedHash(ref) % pool.length;
1241
+ map[ref] = pool[start % pool.length];
1242
+ cursor.set(pool, start + 1);
1243
+ }
1244
+ return map;
1245
+ }
1246
+
1247
+ // src/ui/ai-tree/AiTreeRenderer.tsx
565
1248
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
566
1249
  function lucideByName(name) {
567
1250
  const pascal = name.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
@@ -575,6 +1258,7 @@ var typeStyle = (spec, font) => ({
575
1258
  fontWeight: spec.weight
576
1259
  });
577
1260
  var str = (value) => typeof value === "string" ? value : "";
1261
+ var cardRadius = (slots) => slots.cornerStyle === "sharp" ? 0 : AI_TREE_TOKENS.radiusCard;
578
1262
  var featureLines = (value) => value.split(/\n|<br\s*\/?>/i).map((line) => line.trim()).filter(Boolean);
579
1263
  var CHECK_MASK = `data:image/svg+xml,${encodeURIComponent(
580
1264
  '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>'
@@ -601,6 +1285,25 @@ var FEATURE_LINE_CSS = [
601
1285
  `background-color:currentColor;-webkit-mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px;`,
602
1286
  `mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px}`
603
1287
  ].join("");
1288
+ function buttonShellStyle(ctx, fullWidth) {
1289
+ const bs = ctx.buttonStyle;
1290
+ if (bs) {
1291
+ return {
1292
+ borderRadius: bs.radius,
1293
+ ...bs.padding ? { padding: bs.padding } : {},
1294
+ ...bs.fontFamily ? { fontFamily: bs.fontFamily } : { fontFamily: ctx.brand.fonts.body },
1295
+ ...bs.fontSize ? { fontSize: bs.fontSize } : {},
1296
+ ...bs.fontWeight ? { fontWeight: bs.fontWeight } : {},
1297
+ ...bs.letterSpacing && bs.letterSpacing !== "normal" ? { letterSpacing: bs.letterSpacing } : {},
1298
+ ...bs.textTransform && bs.textTransform !== "none" ? { textTransform: bs.textTransform } : {}
1299
+ };
1300
+ }
1301
+ return {
1302
+ borderRadius: AI_TREE_TOKENS.radiusButton,
1303
+ padding: fullWidth ? `${AI_TREE_TOKENS.spacing2}px ${AI_TREE_TOKENS.spacing4}px` : `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`,
1304
+ ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
1305
+ };
1306
+ }
604
1307
  function hexLuminance(color) {
605
1308
  const m = /^#([0-9a-f]{6})$/i.exec(color.trim());
606
1309
  if (!m) return null;
@@ -617,6 +1320,12 @@ function hexContrast(a, b) {
617
1320
  const [hi, lo] = la > lb ? [la, lb] : [lb, la];
618
1321
  return (hi + 0.05) / (lo + 0.05);
619
1322
  }
1323
+ function primaryButtonLabel(brand) {
1324
+ const darkC = hexContrast(brand.palette.primary, brand.palette.dark);
1325
+ const lightC = hexContrast(brand.palette.primary, AI_TREE_TOKENS.textPrimaryForeground);
1326
+ if (darkC === null || lightC === null) return AI_TREE_TOKENS.textPrimaryForeground;
1327
+ return darkC > lightC ? brand.palette.dark : AI_TREE_TOKENS.textPrimaryForeground;
1328
+ }
620
1329
  function accentBandContext(brand) {
621
1330
  const p = brand.palette;
622
1331
  const lightWins = (hexContrast(p.light, p.primary) ?? 99) >= (hexContrast(p.dark, p.primary) ?? 0);
@@ -634,6 +1343,22 @@ function accentBandContext(brand) {
634
1343
  function textAttrs(ctx, path) {
635
1344
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
636
1345
  }
1346
+ var AI_RESPONSIVE_CSS = [
1347
+ "@media (max-width: 960px) {",
1348
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
1349
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
1350
+ "}",
1351
+ "@media (max-width: 640px) {",
1352
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
1353
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
1354
+ // Group containers flatten to a column on phones; span placements come along for free.
1355
+ " [data-ai-responsive] [data-ai-group] { display: flex !important; flex-direction: column !important; }",
1356
+ " [data-ai-responsive] [data-ai-group] > * { grid-column: auto !important; }",
1357
+ " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
1358
+ " [data-ai-responsive] { overflow-x: hidden; }",
1359
+ " [data-ai-responsive] img { max-width: 100%; }",
1360
+ "}"
1361
+ ].join("\n");
637
1362
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
638
1363
  function MediaBox({
639
1364
  refValue,
@@ -646,13 +1371,17 @@ function MediaBox({
646
1371
  const url = refValue ? ctx.resolveMedia(refValue) : null;
647
1372
  const isIcon = /^(lucide|simple):/.test(refValue);
648
1373
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
649
- const editAttrs = ctx.keyFor && editPath && !isIcon ? { "data-ohw-key": ctx.keyFor(editPath), "data-ohw-editable": "image" } : {};
1374
+ const editAttrs = ctx.keyFor && editPath ? {
1375
+ "data-ohw-key": ctx.keyFor(editPath),
1376
+ "data-ohw-editable": isIcon ? "icon" : "image"
1377
+ } : {};
650
1378
  if (isIcon) {
651
1379
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
652
1380
  return /* @__PURE__ */ jsx(
653
1381
  "span",
654
1382
  {
655
1383
  "data-ai-icon": refValue,
1384
+ ...editAttrs,
656
1385
  style: {
657
1386
  display: "inline-flex",
658
1387
  width: 48,
@@ -716,12 +1445,15 @@ function ButtonEl({
716
1445
  width: fullWidth ? "100%" : void 0,
717
1446
  alignItems: "center",
718
1447
  justifyContent: "center",
719
- padding: fullWidth ? `${AI_TREE_TOKENS.spacing2}px ${AI_TREE_TOKENS.spacing4}px` : `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`,
720
- borderRadius: AI_TREE_TOKENS.radiusButton,
721
1448
  textDecoration: "none",
722
1449
  cursor: "pointer",
723
- ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body),
724
- ...secondary ? { border: `1px solid ${ctx.brand.palette.dark}`, color: ctx.brand.palette.dark } : { background: ctx.brand.palette.primary, color: ctx.buttonLabel ?? AI_TREE_TOKENS.textPrimaryForeground }
1450
+ ...buttonShellStyle(ctx, fullWidth),
1451
+ ...secondary ? { border: `1px solid ${ctx.brand.palette.dark}`, color: ctx.brand.palette.dark } : (
1452
+ // Off-band, prefer the template button's measured fill/label pair — a template may
1453
+ // fill its CTAs with any token (hvac: accent bg, primary text). On an accent band
1454
+ // the flipped palette keeps contrast, so the brand-driven colours stay.
1455
+ !ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : { background: ctx.brand.palette.primary, color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand) }
1456
+ )
725
1457
  },
726
1458
  children: /* @__PURE__ */ jsx("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
727
1459
  }
@@ -897,10 +1629,11 @@ function PricingCard({ node, ctx, path }) {
897
1629
  return /* @__PURE__ */ jsxs(
898
1630
  "div",
899
1631
  {
1632
+ "data-ohw-card": "",
900
1633
  style: {
901
1634
  background: hasBg ? ctx.brand.palette.light : "transparent",
902
1635
  border: `1px solid ${dark}`,
903
- borderRadius: AI_TREE_TOKENS.radiusCard,
1636
+ borderRadius: cardRadius(slots),
904
1637
  padding: AI_TREE_TOKENS.paddingBlock,
905
1638
  display: "flex",
906
1639
  flexDirection: "column",
@@ -1005,10 +1738,11 @@ function TestimonialCard({ node, ctx, path }) {
1005
1738
  return /* @__PURE__ */ jsx(
1006
1739
  "div",
1007
1740
  {
1741
+ "data-ohw-card": "",
1008
1742
  "data-ai-avatar-pos": avatarPos ?? void 0,
1009
1743
  style: {
1010
1744
  background: hasBg ? ctx.cardSurface : "transparent",
1011
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1745
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1012
1746
  overflow: "hidden",
1013
1747
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1014
1748
  minWidth: 0
@@ -1043,10 +1777,11 @@ function TeamCard({ node, ctx, path }) {
1043
1777
  return /* @__PURE__ */ jsxs(
1044
1778
  "div",
1045
1779
  {
1780
+ "data-ohw-card": "",
1046
1781
  "data-ai-avatar-pos": avatarPos ?? void 0,
1047
1782
  style: {
1048
1783
  background: hasBg ? ctx.cardSurface : "transparent",
1049
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1784
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1050
1785
  overflow: "hidden",
1051
1786
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1052
1787
  minWidth: 0,
@@ -1217,9 +1952,10 @@ function CardBlock({ node, ctx, path }) {
1217
1952
  return /* @__PURE__ */ jsxs(
1218
1953
  "div",
1219
1954
  {
1955
+ "data-ohw-card": "",
1220
1956
  style: {
1221
1957
  background: hasBg ? ctx.cardSurface : "transparent",
1222
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1958
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1223
1959
  overflow: "hidden",
1224
1960
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1225
1961
  display: horizontal ? "flex" : "block",
@@ -1247,7 +1983,7 @@ function CardBlock({ node, ctx, path }) {
1247
1983
  ) : /* @__PURE__ */ jsx(
1248
1984
  "div",
1249
1985
  {
1250
- style: /^(lucide|simple):/.test(mediaRef) ? { padding: `${AI_TREE_TOKENS.paddingBlock}px ${AI_TREE_TOKENS.paddingBlock}px 0` } : hasBg ? void 0 : { borderRadius: AI_TREE_TOKENS.radiusCard, overflow: "hidden" },
1986
+ style: /^(lucide|simple):/.test(mediaRef) ? { padding: `${AI_TREE_TOKENS.paddingBlock}px ${AI_TREE_TOKENS.paddingBlock}px 0` } : hasBg ? void 0 : { borderRadius: cardRadius(slots), overflow: "hidden" },
1251
1987
  children: media
1252
1988
  }
1253
1989
  )),
@@ -1538,7 +2274,7 @@ function CollectionBlock({ node, ctx, path }) {
1538
2274
  return /* @__PURE__ */ jsx(
1539
2275
  "div",
1540
2276
  {
1541
- "data-ai-grid": "",
2277
+ "data-ai-grid": String(itemsPerRow),
1542
2278
  style: {
1543
2279
  display: "grid",
1544
2280
  gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
@@ -1658,6 +2394,32 @@ function renderNode(node, ctx, path) {
1658
2394
  if (child) {
1659
2395
  return renderNode(child, ctx, `${path}.c0`);
1660
2396
  }
2397
+ if (str(slots.provider) === "map" && str(slots.query)) {
2398
+ const query = str(slots.query);
2399
+ const mapAttrs = ctx.keyFor ? {
2400
+ "data-ohw-key": ctx.keyFor(`${path}.query`),
2401
+ "data-ohw-editable": "map",
2402
+ "data-ohw-map-query": query
2403
+ } : {};
2404
+ return /* @__PURE__ */ jsx(
2405
+ "iframe",
2406
+ {
2407
+ ...mapAttrs,
2408
+ "data-ai-embed": "map",
2409
+ title: str(slots.title) || "Map",
2410
+ src: `https://www.google.com/maps?q=${encodeURIComponent(query)}&output=embed`,
2411
+ loading: "lazy",
2412
+ referrerPolicy: "no-referrer-when-downgrade",
2413
+ style: {
2414
+ width: "100%",
2415
+ minHeight: 320,
2416
+ border: 0,
2417
+ borderRadius: AI_TREE_TOKENS.radiusCard,
2418
+ display: "block"
2419
+ }
2420
+ }
2421
+ );
2422
+ }
1661
2423
  return /* @__PURE__ */ jsx(
1662
2424
  "div",
1663
2425
  {
@@ -1761,15 +2523,14 @@ function renderNode(node, ctx, path) {
1761
2523
  alignSelf: submitAlign,
1762
2524
  border: "none",
1763
2525
  cursor: "pointer",
1764
- padding: "12px 24px",
1765
- // Corner radius follows the host template's own buttons (measured from a template
1766
- // CTA); 8px only when the page has no template button to match.
1767
- borderRadius: ctx.buttonRadius ?? 8,
1768
- // Brand-styled: primary fill, brand-derived label colour (not a fixed token) so it
1769
- // reads correctly on custom palettes.
1770
- background: ctx.brand.palette.primary,
1771
- color: ctx.buttonLabel ?? ctx.brand.palette.light,
1772
- ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
2526
+ // Shape/padding/typography follow the host template's own buttons.
2527
+ ...buttonShellStyle(ctx),
2528
+ // Brand-styled: the template button's measured fill/label pair off-band, else
2529
+ // primary fill with a brand-derived label so it reads on custom palettes.
2530
+ ...!ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : {
2531
+ background: ctx.brand.palette.primary,
2532
+ color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand)
2533
+ }
1773
2534
  },
1774
2535
  children: /* @__PURE__ */ jsx("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
1775
2536
  },
@@ -1804,7 +2565,7 @@ function renderNode(node, ctx, path) {
1804
2565
  function AiTreeRenderer({
1805
2566
  tree,
1806
2567
  brand,
1807
- buttonRadius,
2568
+ buttonStyle,
1808
2569
  resolveMedia,
1809
2570
  editKeyPrefix
1810
2571
  }) {
@@ -1814,13 +2575,18 @@ function AiTreeRenderer({
1814
2575
  const resolvedBrand = brand ?? AI_DEFAULT_BRAND;
1815
2576
  const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
1816
2577
  const blockBrand = band?.brand ?? resolvedBrand;
2578
+ const placeholderMap = buildPlaceholderMap(tree);
1817
2579
  const ctx = {
1818
2580
  brand: blockBrand,
1819
- resolveMedia: resolveMedia ?? (() => null),
2581
+ // An owner/library ref resolves through the host resolver; a `placeholder:<subject>` ref the
2582
+ // host cannot resolve falls back to real stock photography (the per-section map first, then a
2583
+ // standalone resolve), so generated galleries, image rows, and overlay backgrounds arrive with
2584
+ // photos instead of grey boxes.
2585
+ resolveMedia: (ref) => resolveMedia?.(ref) ?? placeholderMap[ref] ?? resolvePlaceholderRef(ref),
1820
2586
  cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
1821
2587
  keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
1822
2588
  sectionAlignment: (tree.settings ?? {}).alignment === "center" ? "center" : "left",
1823
- buttonRadius,
2589
+ buttonStyle,
1824
2590
  ...band ? { buttonLabel: band.buttonLabel } : {}
1825
2591
  };
1826
2592
  const settings = tree.settings ?? {};
@@ -1861,6 +2627,7 @@ function AiTreeRenderer({
1861
2627
  {
1862
2628
  "data-ai-section": tree.tag ?? "",
1863
2629
  ...bgAttrs,
2630
+ "data-ai-responsive": "",
1864
2631
  style: {
1865
2632
  position: "relative",
1866
2633
  padding: `${pad}px 0`,
@@ -1871,12 +2638,13 @@ function AiTreeRenderer({
1871
2638
  color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
1872
2639
  },
1873
2640
  children: [
1874
- isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
2641
+ /* @__PURE__ */ jsx("style", { children: AI_RESPONSIVE_CSS }),
1875
2642
  /* @__PURE__ */ jsx("style", { children: AI_MOBILE_CSS }),
2643
+ isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
1876
2644
  /* @__PURE__ */ jsx(
1877
2645
  "div",
1878
2646
  {
1879
- "data-ai-container": "",
2647
+ "data-ai-section-inner": "",
1880
2648
  style: {
1881
2649
  position: "relative",
1882
2650
  maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
@@ -1887,7 +2655,7 @@ function AiTreeRenderer({
1887
2655
  children: tree.rows.map((row, r2) => /* @__PURE__ */ jsx(
1888
2656
  "div",
1889
2657
  {
1890
- "data-ai-row": "",
2658
+ "data-ai-columns": "",
1891
2659
  style: {
1892
2660
  display: "grid",
1893
2661
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -1926,21 +2694,63 @@ function AiTreeRenderer({
1926
2694
  import { jsx as jsx2 } from "react/jsx-runtime";
1927
2695
  var CONTAINER_ATTR = "data-ohw-ai-generated";
1928
2696
  var REPLACED_ATTR = "data-ohw-ai-replaced-by";
1929
- var REMOVED_ATTR = "data-ohw-ai-removed";
2697
+ var REMOVED_ATTR2 = "data-ohw-ai-removed";
1930
2698
  var TEMPLATE_HIDDEN_ATTR = "data-ohw-ai-template-hidden";
1931
2699
  var CHROME_SECTION_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
2700
+ function isChromeSection2(el) {
2701
+ if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) return true;
2702
+ return el.tagName === "HEADER" || el.tagName === "FOOTER";
2703
+ }
1932
2704
  function readRootVar(name) {
1933
2705
  if (typeof document === "undefined") return "";
1934
2706
  return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
1935
2707
  }
2708
+ function normalizeColorToHex(value) {
2709
+ if (!value || typeof document === "undefined") return value;
2710
+ const canvas = document.createElement("canvas");
2711
+ canvas.width = 1;
2712
+ canvas.height = 1;
2713
+ const ctx = canvas.getContext("2d");
2714
+ if (!ctx) return value;
2715
+ ctx.fillStyle = value;
2716
+ ctx.fillRect(0, 0, 1, 1);
2717
+ const [r2, g, b] = ctx.getImageData(0, 0, 1, 1).data;
2718
+ const toHex = (n) => n.toString(16).padStart(2, "0");
2719
+ return `#${toHex(r2)}${toHex(g)}${toHex(b)}`;
2720
+ }
2721
+ var GENERIC_FONT_KEYWORDS = /* @__PURE__ */ new Set([
2722
+ "serif",
2723
+ "sans-serif",
2724
+ "monospace",
2725
+ "cursive",
2726
+ "fantasy",
2727
+ "system-ui",
2728
+ "ui-serif",
2729
+ "ui-sans-serif",
2730
+ "ui-monospace",
2731
+ "ui-rounded",
2732
+ "math",
2733
+ "emoji",
2734
+ "fangsong"
2735
+ ]);
2736
+ function primaryFontFamily(stack) {
2737
+ const first = stack.split(",").map((part) => part.trim().replace(/^["']|["']$/g, "")).find((part) => part && !GENERIC_FONT_KEYWORDS.has(part.toLowerCase()));
2738
+ return first ?? "";
2739
+ }
2740
+ function humanizeFontFamily(name) {
2741
+ return name.replace(/^__/, "").replace(/_[0-9a-f]{6}$/i, "").replace(/_/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").trim().replace(/\s+/g, " ").replace(/(^|\s)([a-z])/g, (_, sep, c) => sep + c.toUpperCase());
2742
+ }
2743
+ function readFontVar(name) {
2744
+ return humanizeFontFamily(primaryFontFamily(readRootVar(name)));
2745
+ }
1936
2746
  function deriveBrandOverride() {
1937
2747
  const dark = readRootVar("--ohw-brand-dark");
1938
2748
  const primary = readRootVar("--ohw-brand-primary");
1939
2749
  const light = readRootVar("--ohw-brand-light");
1940
2750
  if (!dark || !primary || !light) return null;
1941
2751
  const accent = readRootVar("--ohw-brand-accent");
1942
- const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1943
- const body = readRootVar("--font-body");
2752
+ const heading = readFontVar("--font-heading") || readFontVar("--font-display");
2753
+ const body = readFontVar("--font-body");
1944
2754
  return {
1945
2755
  palette: { dark, primary, accent: accent || dark, light },
1946
2756
  fonts: {
@@ -1949,28 +2759,98 @@ function deriveBrandOverride() {
1949
2759
  }
1950
2760
  };
1951
2761
  }
1952
- function deriveTemplateBrand() {
1953
- const dark = readRootVar("--color-dark");
1954
- const primary = readRootVar("--color-primary");
1955
- const light = readRootVar("--color-light");
2762
+ function deriveTemplateBrandLive() {
2763
+ const dark = readRootVar("--color-dark") || readRootVar("--brand-text");
2764
+ const primary = readRootVar("--color-primary") || readRootVar("--brand-primary");
2765
+ const light = readRootVar("--color-light") || readRootVar("--brand-background");
1956
2766
  if (!dark || !primary || !light) return null;
1957
- const accent = readRootVar("--color-accent");
1958
- const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1959
- const body = readRootVar("--font-body");
2767
+ const accent = readRootVar("--color-accent") || readRootVar("--brand-accent");
2768
+ const heading = readFontVar("--brand-font-heading") || readFontVar("--font-heading") || readFontVar("--font-display");
2769
+ const body = readFontVar("--brand-font-body") || readFontVar("--font-body");
1960
2770
  return {
1961
- palette: { dark, primary, accent: accent || dark, light },
2771
+ palette: {
2772
+ dark: normalizeColorToHex(dark),
2773
+ primary: normalizeColorToHex(primary),
2774
+ accent: normalizeColorToHex(accent || dark),
2775
+ light: normalizeColorToHex(light)
2776
+ },
1962
2777
  fonts: {
1963
2778
  heading: heading || AI_DEFAULT_BRAND.fonts.heading,
1964
2779
  body: body || AI_DEFAULT_BRAND.fonts.body
1965
2780
  }
1966
2781
  };
1967
2782
  }
1968
- function deriveTemplateButtonRadius() {
2783
+ function deriveTemplateFontsLive() {
2784
+ const heading = readFontVar("--brand-font-heading") || readFontVar("--font-heading") || readFontVar("--font-display");
2785
+ const body = readFontVar("--brand-font-body") || readFontVar("--font-body");
2786
+ if (!heading && !body) return null;
2787
+ return {
2788
+ heading: heading || AI_DEFAULT_BRAND.fonts.heading,
2789
+ body: body || AI_DEFAULT_BRAND.fonts.body
2790
+ };
2791
+ }
2792
+ var OVERRIDE_VAR_NAMES = [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES, ...FONT_VARS.heading, ...FONT_VARS.body];
2793
+ function withOverrideStripped(read) {
2794
+ const root = document.documentElement;
2795
+ const restore = OVERRIDE_VAR_NAMES.map((name) => [name, root.style.getPropertyValue(name)]);
2796
+ for (const name of OVERRIDE_VAR_NAMES) root.style.removeProperty(name);
2797
+ try {
2798
+ return read();
2799
+ } finally {
2800
+ for (const [name, value] of restore) if (value) root.style.setProperty(name, value);
2801
+ }
2802
+ }
2803
+ var TEMPLATE_BRAND_SNAPSHOT = typeof document === "undefined" ? null : withOverrideStripped(deriveTemplateBrandLive);
2804
+ var TEMPLATE_FONTS_SNAPSHOT = typeof document === "undefined" ? null : withOverrideStripped(deriveTemplateFontsLive);
2805
+ function deriveTemplateBrand() {
2806
+ return TEMPLATE_BRAND_SNAPSHOT;
2807
+ }
2808
+ function deriveTemplateFonts() {
2809
+ return TEMPLATE_FONTS_SNAPSHOT;
2810
+ }
2811
+ function deriveTemplateButtonStyle() {
1969
2812
  if (typeof document === "undefined") return null;
1970
- const btn = document.querySelector('[data-ohw-role="button"]');
2813
+ const candidates = Array.from(
2814
+ document.querySelectorAll('[data-ohw-role="button"]')
2815
+ ).filter(
2816
+ (el) => !el.closest(`[${CONTAINER_ATTR}]`) && // Whole-card links legitimately carry the button role (serene's clickable treatment
2817
+ // cards) but must never donate the "button look" — a real button holds at most its
2818
+ // one editable label, a card holds a headline, copy and prices.
2819
+ el.querySelectorAll("[data-ohw-editable]").length <= 1
2820
+ );
2821
+ const isFilled = (el) => {
2822
+ const bg = getComputedStyle(el).backgroundColor;
2823
+ if (!bg || bg === "transparent") return false;
2824
+ const alpha = bg.match(/rgba?\([^)]*,\s*([\d.]+)\)$/);
2825
+ return !alpha || parseFloat(alpha[1]) > 0;
2826
+ };
2827
+ const btn = candidates.find(isFilled) ?? candidates[0];
1971
2828
  if (!btn) return null;
1972
- const radius = getComputedStyle(btn).borderTopLeftRadius;
1973
- return radius || null;
2829
+ const cs = getComputedStyle(btn);
2830
+ const filled = isFilled(btn);
2831
+ const corners = [
2832
+ cs.borderTopLeftRadius,
2833
+ cs.borderTopRightRadius,
2834
+ cs.borderBottomRightRadius,
2835
+ cs.borderBottomLeftRadius
2836
+ ].map((v) => v || "0px");
2837
+ const radius = corners.every((v) => v === corners[0]) ? corners[0] : corners.join(" ");
2838
+ const px = (v) => parseFloat(v) || 0;
2839
+ const lineHeight = px(cs.lineHeight) || px(cs.fontSize) * 1.2;
2840
+ const contentH = btn.getBoundingClientRect().height - px(cs.borderTopWidth) - px(cs.borderBottomWidth);
2841
+ const impliedY = Math.round(Math.max(0, (contentH - lineHeight) / 2));
2842
+ const padY = Math.max(px(cs.paddingTop), px(cs.paddingBottom), impliedY);
2843
+ const padX = Math.max(px(cs.paddingLeft), px(cs.paddingRight));
2844
+ return {
2845
+ radius: radius || "10px",
2846
+ padding: `${padY}px ${padX}px`,
2847
+ ...filled ? { background: cs.backgroundColor, color: cs.color } : {},
2848
+ fontFamily: cs.fontFamily || "",
2849
+ fontSize: cs.fontSize || "",
2850
+ fontWeight: cs.fontWeight || "",
2851
+ letterSpacing: cs.letterSpacing || "",
2852
+ textTransform: cs.textTransform || ""
2853
+ };
1974
2854
  }
1975
2855
  var mounted = /* @__PURE__ */ new Map();
1976
2856
  function findTemplateSection(id) {
@@ -1986,6 +2866,19 @@ function findPlacementAnchor(id, exclude) {
1986
2866
  for (const el of document.querySelectorAll(`[data-ohw-section="${CSS.escape(id)}"]`)) {
1987
2867
  if (el === exclude) continue;
1988
2868
  if (el.parentElement?.closest("[data-ohw-section]")) continue;
2869
+ if (isChromeSection2(el)) return null;
2870
+ return el;
2871
+ }
2872
+ return null;
2873
+ }
2874
+ function findFooterSection() {
2875
+ const byId = findTemplateSection("footer");
2876
+ if (byId) return byId;
2877
+ for (const el of Array.from(
2878
+ document.querySelectorAll("footer[data-ohw-section]")
2879
+ ).reverse()) {
2880
+ if (el.hasAttribute(CONTAINER_ATTR)) continue;
2881
+ if (el.parentElement?.closest("[data-ohw-section]")) continue;
1989
2882
  return el;
1990
2883
  }
1991
2884
  return null;
@@ -2012,7 +2905,7 @@ function placeContainer(container, entry) {
2012
2905
  return;
2013
2906
  }
2014
2907
  }
2015
- const footer = findTemplateSection("footer");
2908
+ const footer = findFooterSection();
2016
2909
  if (footer) {
2017
2910
  footer.insertAdjacentElement("beforebegin", container);
2018
2911
  } else {
@@ -2021,18 +2914,18 @@ function placeContainer(container, entry) {
2021
2914
  }
2022
2915
  function syncRemovedSections(state) {
2023
2916
  const removed = new Set(state.removed ?? []);
2024
- for (const el of document.querySelectorAll(`[${REMOVED_ATTR}]`)) {
2917
+ for (const el of document.querySelectorAll(`[${REMOVED_ATTR2}]`)) {
2025
2918
  const id = el.getAttribute("data-ohw-section") ?? "";
2026
2919
  if (!removed.has(id)) {
2027
2920
  el.style.removeProperty("display");
2028
- el.removeAttribute(REMOVED_ATTR);
2921
+ el.removeAttribute(REMOVED_ATTR2);
2029
2922
  }
2030
2923
  }
2031
2924
  for (const id of removed) {
2032
2925
  const section = findTemplateSection(id);
2033
2926
  if (section && !section.hasAttribute(REPLACED_ATTR)) {
2034
2927
  section.style.display = "none";
2035
- section.setAttribute(REMOVED_ATTR, "");
2928
+ section.setAttribute(REMOVED_ATTR2, "");
2036
2929
  }
2037
2930
  }
2038
2931
  }
@@ -2047,9 +2940,9 @@ function syncTemplateHidden(state, pageHasSections) {
2047
2940
  if (!hide) return;
2048
2941
  for (const el of Array.from(document.querySelectorAll("[data-ohw-section]"))) {
2049
2942
  if (el.hasAttribute(CONTAINER_ATTR)) continue;
2050
- if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) continue;
2943
+ if (isChromeSection2(el)) continue;
2051
2944
  if (el.parentElement?.closest("[data-ohw-section]")) continue;
2052
- if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR)) continue;
2945
+ if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR2)) continue;
2053
2946
  el.style.display = "none";
2054
2947
  el.setAttribute(TEMPLATE_HIDDEN_ATTR, "");
2055
2948
  }
@@ -2073,18 +2966,23 @@ function syncReplacedOriginals(state) {
2073
2966
  }
2074
2967
  }
2075
2968
  var sectionOrderIndex = /* @__PURE__ */ new Map();
2969
+ var removedSectionIds = /* @__PURE__ */ new Set();
2076
2970
  function setAiSectionOrder(raw, currentPath) {
2077
2971
  const next = /* @__PURE__ */ new Map();
2972
+ const removed = /* @__PURE__ */ new Set();
2078
2973
  if (raw) {
2079
2974
  try {
2080
2975
  const entries = JSON.parse(raw);
2081
2976
  for (const entry of entries) {
2082
- if (!entry.pagePath || entry.pagePath === currentPath) next.set(entry.instanceId, entry.order);
2977
+ if (entry.pagePath && entry.pagePath !== currentPath) continue;
2978
+ next.set(entry.instanceId, entry.order);
2979
+ if (entry.removed) removed.add(entry.instanceId);
2083
2980
  }
2084
2981
  } catch {
2085
2982
  }
2086
2983
  }
2087
2984
  sectionOrderIndex = next;
2985
+ removedSectionIds = removed;
2088
2986
  }
2089
2987
  function applyExplicitOrder(entries) {
2090
2988
  if (sectionOrderIndex.size === 0) return entries;
@@ -2120,11 +3018,23 @@ function orderByChain(sections) {
2120
3018
  for (const root of roots) visit(root);
2121
3019
  return out.length === sections.length ? out : sections;
2122
3020
  }
3021
+ function syncSoftRemovedGenerated() {
3022
+ for (const [id, section] of mounted) {
3023
+ const el = section.container;
3024
+ if (removedSectionIds.has(id)) {
3025
+ el.style.display = "none";
3026
+ el.setAttribute(REMOVED_ATTR, "");
3027
+ } else if (el.hasAttribute(REMOVED_ATTR)) {
3028
+ el.style.removeProperty("display");
3029
+ el.removeAttribute(REMOVED_ATTR);
3030
+ }
3031
+ }
3032
+ }
2123
3033
  function applyAiSectionsToDom(state, options) {
2124
3034
  if (typeof document === "undefined") return;
2125
3035
  const brandOverride = deriveBrandOverride();
2126
3036
  const templateBrand = deriveTemplateBrand();
2127
- const templateButtonRadius = deriveTemplateButtonRadius();
3037
+ const templateButtonStyle = deriveTemplateButtonStyle();
2128
3038
  const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
2129
3039
  const pagePath = window.location.pathname;
2130
3040
  const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
@@ -2164,7 +3074,7 @@ function applyAiSectionsToDom(state, options) {
2164
3074
  {
2165
3075
  tree: entry.tree,
2166
3076
  brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
2167
- buttonRadius: templateButtonRadius,
3077
+ buttonStyle: templateButtonStyle,
2168
3078
  resolveMedia,
2169
3079
  editKeyPrefix: `ai.${entry.id}`
2170
3080
  }
@@ -2187,6 +3097,7 @@ function applyAiSectionsToDom(state, options) {
2187
3097
  syncReplacedOriginals(state);
2188
3098
  syncRemovedSections(state);
2189
3099
  syncTemplateHidden(state, pageSections.length > 0);
3100
+ syncSoftRemovedGenerated();
2190
3101
  }
2191
3102
 
2192
3103
  // src/useLinkHrefGuardian.ts
@@ -6378,12 +7289,12 @@ var cva = (base, config) => (props) => {
6378
7289
  import { Toggle as TogglePrimitive } from "radix-ui";
6379
7290
  import { jsx as jsx5 } from "react/jsx-runtime";
6380
7291
  var toggleVariants = cva(
6381
- "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
7292
+ "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=on]:bg-bridge-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
6382
7293
  {
6383
7294
  variants: {
6384
7295
  variant: {
6385
7296
  default: "bg-transparent border-0",
6386
- outline: "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground"
7297
+ outline: "border border-input bg-transparent shadow-xs hover:bg-bridge-accent hover:text-accent-foreground"
6387
7298
  },
6388
7299
  size: {
6389
7300
  default: "px-2 py-1",
@@ -6476,10 +7387,10 @@ var DragHandle = React4.forwardRef(
6476
7387
  type,
6477
7388
  "data-slot": "drag-handle",
6478
7389
  className: cn(
6479
- "inline-flex h-7 w-4 shrink-0 items-center justify-center rounded-md transition-all duration-200 max-h-[30px]",
7390
+ "inline-flex h-7 w-4 shrink-0 items-center justify-center rounded-md transition-all duration-200 max-h-7.5",
6480
7391
  "bg-white border border-transparent text-stone-500 shadow-md cursor-grab",
6481
7392
  "enabled:hover:border enabled:hover:border-stone-200 enabled:hover:text-stone-950",
6482
- "enabled:active:border enabled:active:border-primary enabled:active:bg-primary-50 enabled:active:text-stone-950 enabled:active:shadow enabled:active:cursor-grabbing",
7393
+ "enabled:active:border enabled:active:border-bridge-primary enabled:active:bg-primary-50 enabled:active:text-stone-950 enabled:active:shadow enabled:active:cursor-grabbing",
6483
7394
  "disabled:cursor-not-allowed disabled:opacity-40 disabled:text-stone-950 disabled:pointer-events-none",
6484
7395
  className
6485
7396
  ),
@@ -6501,7 +7412,7 @@ var CustomToolbar = React5.forwardRef(({ className, onMouseDown, ...props }, ref
6501
7412
  "data-ohw-toolbar": "",
6502
7413
  className: cn(
6503
7414
  // Figma: bg background, radius 8, gap-1, p-0.5, shadow-md — no border
6504
- "inline-flex h-8 items-center gap-1 rounded-[var(--radius,0.5rem)] bg-background p-0.5 font-sans whitespace-nowrap shadow-md",
7415
+ "inline-flex h-8 items-center gap-1 rounded-(--radius,0.5rem) bg-background p-0.5 font-sans whitespace-nowrap shadow-md",
6505
7416
  className
6506
7417
  ),
6507
7418
  onMouseDown: (e) => {
@@ -6530,7 +7441,7 @@ var CustomToolbarButton = React5.forwardRef(
6530
7441
  type,
6531
7442
  className: cn(
6532
7443
  "inline-flex size-7 shrink-0 items-center justify-center rounded-[calc(var(--radius,0.5rem)-2px)] text-foreground transition-colors",
6533
- active ? "bg-primary text-primary-foreground" : "bg-transparent hover:bg-muted disabled:cursor-not-allowed disabled:text-muted-foreground disabled:opacity-60",
7444
+ active ? "bg-bridge-primary text-primary-foreground" : "bg-transparent hover:bg-muted disabled:cursor-not-allowed disabled:text-muted-foreground disabled:opacity-60",
6534
7445
  className
6535
7446
  ),
6536
7447
  ...props
@@ -6735,9 +7646,7 @@ function ItemActionToolbar({
6735
7646
  ToggleGroup,
6736
7647
  {
6737
7648
  value: dropdownOpen ? "open" : "closed",
6738
- onValueChange: (value) => {
6739
- if (value !== "open" && value !== "closed") return;
6740
- onDropdownOpenChange?.(value === "open");
7649
+ onValueChange: () => {
6741
7650
  },
6742
7651
  className: "h-7 gap-0.5 rounded-[calc(var(--radius,0.5rem)-2px)] bg-muted p-0.5",
6743
7652
  onMouseDown: (e) => {
@@ -6752,6 +7661,11 @@ function ItemActionToolbar({
6752
7661
  size: "sm",
6753
7662
  "aria-label": "Closed",
6754
7663
  className: "h-6 min-w-0 px-2 text-xs font-medium",
7664
+ onMouseDown: (e) => {
7665
+ e.preventDefault();
7666
+ e.stopPropagation();
7667
+ if (dropdownOpen !== false) onDropdownOpenChange?.(false);
7668
+ },
6755
7669
  children: "Closed"
6756
7670
  }
6757
7671
  ),
@@ -6762,6 +7676,11 @@ function ItemActionToolbar({
6762
7676
  size: "sm",
6763
7677
  "aria-label": "Open",
6764
7678
  className: "h-6 min-w-0 px-2 text-xs font-medium",
7679
+ onMouseDown: (e) => {
7680
+ e.preventDefault();
7681
+ e.stopPropagation();
7682
+ if (dropdownOpen !== true) onDropdownOpenChange?.(true);
7683
+ },
6765
7684
  children: "Open"
6766
7685
  }
6767
7686
  )
@@ -7724,13 +8643,13 @@ function FormFieldToolbar({
7724
8643
  ]
7725
8644
  }
7726
8645
  ) }),
7727
- /* @__PURE__ */ jsx13(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-[190px] p-1", children: FIELD_TYPES.map((entry) => {
8646
+ /* @__PURE__ */ jsx13(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-47.5 p-1", children: FIELD_TYPES.map((entry) => {
7728
8647
  const Icon = TYPE_ICONS[entry.type];
7729
8648
  return /* @__PURE__ */ jsxs6(
7730
8649
  DropdownMenuItem,
7731
8650
  {
7732
8651
  onSelect: () => onTypeChange(entry.type),
7733
- className: "rounded-md py-2 text-[13px] " + (entry.type === type ? "bg-primary/10" : ""),
8652
+ className: "rounded-md py-2 text-[13px] " + (entry.type === type ? "bg-bridge-primary/10" : ""),
7734
8653
  children: [
7735
8654
  /* @__PURE__ */ jsx13(Icon, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
7736
8655
  entry.label
@@ -7747,7 +8666,7 @@ function FormFieldToolbar({
7747
8666
  type: "button",
7748
8667
  "aria-pressed": required,
7749
8668
  onClick: onRequiredToggle,
7750
- className: "flex h-7 items-center gap-1.5 whitespace-nowrap rounded-md px-2 text-[13px] font-medium transition-colors " + (required ? "bg-primary/10 text-primary" : "text-foreground hover:bg-muted/70"),
8669
+ className: "flex h-7 items-center gap-1.5 whitespace-nowrap rounded-md px-2 text-[13px] font-medium transition-colors " + (required ? "bg-bridge-primary/10 text-bridge-primary" : "text-foreground hover:bg-muted/70"),
7751
8670
  "data-ohw-field-required-toggle": "",
7752
8671
  children: [
7753
8672
  /* @__PURE__ */ jsx13(Asterisk, { size: 14, strokeWidth: 2, "aria-hidden": true }),
@@ -7767,7 +8686,7 @@ function FormFieldToolbar({
7767
8686
  children: /* @__PURE__ */ jsx13(MoreHorizontal2, { size: 15, "aria-hidden": true })
7768
8687
  }
7769
8688
  ) }),
7770
- /* @__PURE__ */ jsxs6(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-[170px] p-1", children: [
8689
+ /* @__PURE__ */ jsxs6(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-42.5 p-1", children: [
7771
8690
  /* @__PURE__ */ jsxs6(DropdownMenuItem, { onSelect: onDuplicate, className: "rounded-md py-2 text-[13px]", children: [
7772
8691
  /* @__PURE__ */ jsx13(Copy2, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
7773
8692
  "Duplicate"
@@ -7787,7 +8706,7 @@ function FieldTypePicker({ onPick }) {
7787
8706
  "div",
7788
8707
  {
7789
8708
  "data-ohw-field-type-picker": "",
7790
- className: "pointer-events-auto grid w-[420px] grid-cols-3 gap-3 rounded-xl border border-border bg-background p-4 shadow-lg",
8709
+ className: "pointer-events-auto grid w-105 grid-cols-3 gap-3 rounded-xl border border-border bg-background p-4 shadow-lg",
7791
8710
  children: FIELD_TYPES.map((entry) => {
7792
8711
  const Icon = TYPE_ICONS[entry.type];
7793
8712
  return /* @__PURE__ */ jsxs6(
@@ -7795,7 +8714,7 @@ function FieldTypePicker({ onPick }) {
7795
8714
  {
7796
8715
  type: "button",
7797
8716
  onClick: () => onPick(entry.type),
7798
- className: "flex h-[104px] flex-col items-center justify-center gap-3 rounded-xl border border-border text-[15px] font-medium text-foreground transition-colors hover:border-primary hover:bg-primary/5",
8717
+ className: "flex h-26 flex-col items-center justify-center gap-3 rounded-xl border border-border text-[15px] font-medium text-foreground transition-colors hover:border-bridge-primary hover:bg-bridge-primary/5",
7799
8718
  children: [
7800
8719
  /* @__PURE__ */ jsx13(Icon, { size: 26, strokeWidth: 1.5, "aria-hidden": true }),
7801
8720
  entry.label
@@ -7821,7 +8740,7 @@ var buttonVariants = cva(
7821
8740
  {
7822
8741
  variants: {
7823
8742
  variant: {
7824
- default: "bg-primary text-primary-foreground hover:opacity-90",
8743
+ default: "bg-bridge-primary text-primary-foreground hover:opacity-90",
7825
8744
  outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
7826
8745
  ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
7827
8746
  },
@@ -7916,6 +8835,7 @@ function MediaOverlay({
7916
8835
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
7917
8836
  );
7918
8837
  }, [isVideo]);
8838
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
7919
8839
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
7920
8840
  const box = {
7921
8841
  position: "fixed",
@@ -8021,8 +8941,8 @@ function MediaOverlay({
8021
8941
  pointerEvents: hover.hasTextOverlap ? "none" : "auto",
8022
8942
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
8023
8943
  // than hovered. Hover keeps the existing tinted preview.
8024
- boxShadow: selected ? "inset 0 0 0 2px var(--color-primary)" : "inset 0 0 0 1.5px var(--color-primary)",
8025
- background: selected ? "transparent" : "color-mix(in srgb, var(--color-primary) 20%, transparent)"
8944
+ boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
8945
+ background: selected ? "transparent" : "color-mix(in srgb, var(--color-bridge-primary) 20%, transparent)"
8026
8946
  },
8027
8947
  onClick: () => onSelect && !selected ? onSelect(hover.key) : onReplace(hover.key),
8028
8948
  children: [
@@ -8045,17 +8965,17 @@ function MediaOverlay({
8045
8965
  },
8046
8966
  children: [
8047
8967
  isVideo ? /* @__PURE__ */ jsx15(Film, { size: 14 }) : /* @__PURE__ */ jsx15(ImageIcon, { size: 14 }),
8048
- isVideo ? "Replace video" : "Replace image"
8968
+ replaceLabel
8049
8969
  ]
8050
8970
  }
8051
8971
  ),
8052
- replaceMode === "none" ? null : /* @__PURE__ */ jsxs7(
8972
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ jsxs7(
8053
8973
  Button,
8054
8974
  {
8055
8975
  "data-ohw-media-overlay": "",
8056
8976
  variant: "outline",
8057
8977
  size: "sm",
8058
- "aria-label": isVideo ? "Replace video" : "Replace image",
8978
+ "aria-label": replaceLabel,
8059
8979
  className: "gap-1.5 cursor-pointer hover:bg-background",
8060
8980
  style: {
8061
8981
  ...OVERLAY_BUTTON_STYLE,
@@ -8078,7 +8998,7 @@ function MediaOverlay({
8078
8998
  },
8079
8999
  children: [
8080
9000
  isVideo ? /* @__PURE__ */ jsx15(Film, { size: 14 }) : /* @__PURE__ */ jsx15(ImageIcon, { size: 14 }),
8081
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
9001
+ replaceMode === "full" ? replaceLabel : null
8082
9002
  ]
8083
9003
  }
8084
9004
  )
@@ -8116,8 +9036,8 @@ function CarouselOverlay({
8116
9036
  height: rect.height,
8117
9037
  zIndex: 2147483646,
8118
9038
  pointerEvents: "auto",
8119
- boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
8120
- background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
9039
+ boxShadow: "inset 0 0 0 1.5px var(--color-bridge-primary)",
9040
+ background: "color-mix(in srgb, var(--color-bridge-primary) 20%, transparent)"
8121
9041
  },
8122
9042
  onClick: () => onEdit(hover.key),
8123
9043
  children: /* @__PURE__ */ jsxs8(
@@ -8137,229 +9057,17 @@ function CarouselOverlay({
8137
9057
  /* @__PURE__ */ jsx16(GalleryHorizontal, { size: 14 }),
8138
9058
  "Edit gallery"
8139
9059
  ]
8140
- }
8141
- )
8142
- }
8143
- );
8144
- }
8145
-
8146
- // src/ui/ai-section/AiSectionOverlay.tsx
8147
- import { useCallback as useCallback2, useEffect as useEffect4, useRef as useRef4, useState as useState5 } from "react";
8148
- import { Check, X } from "lucide-react";
8149
-
8150
- // src/lib/sections.ts
8151
- var LINK_PICKER_EXCLUDED_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
8152
- function isChromeSection(el) {
8153
- return el.matches("header, nav, footer, aside");
8154
- }
8155
- function titleCaseSectionId(id) {
8156
- return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
8157
- }
8158
- function parseSectionsFromRoot(root) {
8159
- const seen = /* @__PURE__ */ new Set();
8160
- const sections = [];
8161
- for (const el of root.querySelectorAll("[data-ohw-section]")) {
8162
- const id = el.getAttribute("data-ohw-section") ?? "";
8163
- if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
8164
- if (el.parentElement?.closest("[data-ohw-section]")) continue;
8165
- if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
8166
- continue;
8167
- seen.add(id);
8168
- const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
8169
- sections.push({ id, label });
8170
- }
8171
- return sections;
8172
- }
8173
- function collectSectionsFromDom() {
8174
- if (typeof document === "undefined") return [];
8175
- return parseSectionsFromRoot(document);
8176
- }
8177
- function parseSectionsFromHtml(html) {
8178
- const doc = new DOMParser().parseFromString(html, "text/html");
8179
- return parseSectionsFromRoot(doc);
8180
- }
8181
-
8182
- // src/lib/section-instances.ts
8183
- var SECTION_ORDER_KEY = "__ohw_section_order";
8184
- var REMOVED_ATTR2 = "data-ohw-section-removed";
8185
- function isRemovedSection(el) {
8186
- return el.hasAttribute(REMOVED_ATTR2);
8187
- }
8188
- function topLevelSections() {
8189
- return Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
8190
- (el) => !el.parentElement?.closest("[data-ohw-section]") && !isChromeSection(el) && !isRemovedSection(el)
8191
- );
8192
- }
8193
- function instanceIdOf(el) {
8194
- return el.dataset.ohwInstance ?? el.dataset.ohwSection ?? "";
8195
- }
8196
- function findByInstanceId(instanceId) {
8197
- const escapedId = CSS.escape(instanceId);
8198
- return document.querySelector(`[data-ohw-instance="${escapedId}"]`) ?? document.querySelector(`[data-ohw-section="${escapedId}"]:not([data-ohw-instance])`);
8199
- }
8200
- function planSectionMove(instanceId, targetIndex, currentPath) {
8201
- const sections = topLevelSections();
8202
- const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
8203
- if (index === -1) return null;
8204
- const dragged = sections[index];
8205
- const others = sections.filter((_, i) => i !== index);
8206
- const clamped = Math.max(0, Math.min(targetIndex, others.length));
8207
- const reordered = [...others.slice(0, clamped), dragged, ...others.slice(clamped)];
8208
- return reordered.map((el, order) => ({
8209
- instanceId: instanceIdOf(el),
8210
- type: el.getAttribute("data-ohw-section") ?? "",
8211
- order,
8212
- pagePath: currentPath
8213
- }));
8214
- }
8215
- function moveSectionInstance(instanceId, direction, currentPath) {
8216
- const sections = topLevelSections();
8217
- const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
8218
- if (index === -1) return null;
8219
- const siblingIndex = direction === "up" ? index - 1 : index + 1;
8220
- if (siblingIndex < 0 || siblingIndex >= sections.length) return null;
8221
- const entries = planSectionMove(instanceId, siblingIndex, currentPath);
8222
- if (!entries) return null;
8223
- applyPersistedOrder(entries);
8224
- return entries;
8225
- }
8226
- function syncRemovedFlags(entries) {
8227
- const removedIds = new Set(entries.filter((e) => e.removed).map((e) => e.instanceId));
8228
- document.querySelectorAll(`[${REMOVED_ATTR2}]`).forEach((el) => {
8229
- if (!removedIds.has(instanceIdOf(el))) {
8230
- el.style.removeProperty("display");
8231
- el.removeAttribute(REMOVED_ATTR2);
8232
- }
8233
- });
8234
- for (const id of removedIds) {
8235
- const el = findByInstanceId(id);
8236
- if (el) {
8237
- el.style.display = "none";
8238
- el.setAttribute(REMOVED_ATTR2, "");
8239
- }
8240
- }
8241
- }
8242
- function applyPersistedOrder(entries) {
8243
- syncRemovedFlags(entries);
8244
- if (entries.length === 0) return;
8245
- const sections = topLevelSections();
8246
- if (sections.length === 0) return;
8247
- const orderIndex = new Map(entries.map((e) => [e.instanceId, e.order]));
8248
- const ordered = [...sections].sort((a, b) => {
8249
- const aOrder = orderIndex.get(instanceIdOf(a));
8250
- const bOrder = orderIndex.get(instanceIdOf(b));
8251
- if (aOrder === void 0 && bOrder === void 0) return 0;
8252
- if (aOrder === void 0) return 1;
8253
- if (bOrder === void 0) return -1;
8254
- return aOrder - bOrder;
8255
- });
8256
- let prev = null;
8257
- for (const el of ordered) {
8258
- if (prev) prev.after(el);
8259
- prev = el;
8260
- }
8261
- }
8262
- function setSectionRemoved(instanceId, currentPath, existingEntries, removed) {
8263
- if (!findByInstanceId(instanceId)) return null;
8264
- const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
8265
- const allSections = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
8266
- (el) => !el.parentElement?.closest("[data-ohw-section]") && !isChromeSection(el)
8267
- );
8268
- allSections.forEach((el, order) => {
8269
- const id = instanceIdOf(el);
8270
- if (!byId.has(id)) {
8271
- byId.set(id, { instanceId: id, type: el.getAttribute("data-ohw-section") ?? "", order, pagePath: currentPath });
8272
- }
8273
- });
8274
- const target = byId.get(instanceId);
8275
- if (!target) return null;
8276
- byId.set(instanceId, { ...target, removed });
8277
- const entries = Array.from(byId.values());
8278
- applyPersistedOrder(entries);
8279
- return entries;
8280
- }
8281
- function deleteSectionInstance(instanceId, currentPath, existingEntries) {
8282
- return setSectionRemoved(instanceId, currentPath, existingEntries, true);
8283
- }
8284
- function restoreSectionInstance(instanceId, currentPath, existingEntries) {
8285
- return setSectionRemoved(instanceId, currentPath, existingEntries, false);
8286
- }
8287
- function duplicateSectionInstance(instanceId, newId, currentPath, existingEntries) {
8288
- const original = findByInstanceId(instanceId);
8289
- if (!original) return null;
8290
- const clone = original.cloneNode(true);
8291
- clone.setAttribute("data-ohw-instance", newId);
8292
- const keyRekeys = rekeySectionSubtree(clone, newId);
8293
- original.insertAdjacentElement("afterend", clone);
8294
- const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
8295
- const entries = topLevelSections().map((el, order) => {
8296
- const id = instanceIdOf(el);
8297
- return {
8298
- instanceId: id,
8299
- type: el.getAttribute("data-ohw-section") ?? "",
8300
- order,
8301
- pagePath: currentPath,
8302
- ...byId.get(id)?.removed ? { removed: true } : {}
8303
- };
8304
- });
8305
- applyPersistedOrder(entries);
8306
- return { entries, keyRekeys };
8307
- }
8308
- function newInstanceId() {
8309
- return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `instance-${Date.now()}-${Math.random().toString(36).slice(2)}`;
8310
- }
8311
- function getPageSectionOrderEntries(raw, currentPath) {
8312
- if (!raw) return [];
8313
- try {
8314
- const entries = JSON.parse(raw);
8315
- return entries.filter((e) => !e.pagePath || e.pagePath === currentPath);
8316
- } catch {
8317
- return [];
8318
- }
8319
- }
8320
- function rekeySectionSubtree(root, instanceId) {
8321
- const suffix = `::${instanceId}`;
8322
- const pairs = [];
8323
- const rekey = (el, attr) => {
8324
- const current = el.getAttribute(attr);
8325
- if (!current) return;
8326
- const base = current.includes("::") ? current.slice(0, current.indexOf("::")) : current;
8327
- const next = `${base}${suffix}`;
8328
- el.setAttribute(attr, next);
8329
- pairs.push({ from: current, to: next });
8330
- };
8331
- if (root.hasAttribute("data-ohw-key")) rekey(root, "data-ohw-key");
8332
- if (root.hasAttribute("data-ohw-href-key")) rekey(root, "data-ohw-href-key");
8333
- root.querySelectorAll("[data-ohw-key]").forEach((el) => rekey(el, "data-ohw-key"));
8334
- root.querySelectorAll("[data-ohw-href-key]").forEach((el) => rekey(el, "data-ohw-href-key"));
8335
- return pairs;
8336
- }
8337
- function initSectionInstancesFromContent(content, currentPath) {
8338
- document.querySelectorAll("[data-ohw-section]:not([data-ohw-instance])").forEach((el) => {
8339
- el.setAttribute("data-ohw-instance", el.getAttribute("data-ohw-section") ?? "");
8340
- });
8341
- const entries = getPageSectionOrderEntries(content[SECTION_ORDER_KEY], currentPath);
8342
- for (const entry of entries) {
8343
- if (entry.instanceId === entry.type) continue;
8344
- if (document.querySelector(`[data-ohw-instance="${CSS.escape(entry.instanceId)}"]`)) continue;
8345
- const original = document.querySelector(
8346
- `[data-ohw-section="${CSS.escape(entry.type)}"][data-ohw-instance="${CSS.escape(entry.type)}"]`
8347
- );
8348
- if (!original) continue;
8349
- const clone = original.cloneNode(true);
8350
- clone.setAttribute("data-ohw-instance", entry.instanceId);
8351
- rekeySectionSubtree(clone, entry.instanceId);
8352
- original.insertAdjacentElement("afterend", clone);
8353
- }
8354
- applyPersistedOrder(entries);
9060
+ }
9061
+ )
9062
+ }
9063
+ );
8355
9064
  }
8356
9065
 
8357
9066
  // src/ui/ai-section/AiSectionOverlay.tsx
9067
+ import { useCallback as useCallback2, useEffect as useEffect4, useRef as useRef4, useState as useState5 } from "react";
9068
+ import { Check, X } from "lucide-react";
8358
9069
  import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
8359
- function findSectionElement(instanceId) {
8360
- const escaped = CSS.escape(instanceId);
8361
- return document.querySelector(`[data-ohw-instance="${escaped}"]`) ?? document.querySelector(`[data-ohw-section="${escaped}"]:not([data-ohw-instance])`);
8362
- }
9070
+ var findSectionElement = findByInstanceId;
8363
9071
  function readRect(instanceId) {
8364
9072
  const el = findSectionElement(instanceId);
8365
9073
  if (!el) return null;
@@ -8399,7 +9107,7 @@ function useLiveSectionRect(sectionId) {
8399
9107
  }
8400
9108
  function computeSectionBoundaryFlags(instanceId) {
8401
9109
  const topLevel = topLevelSections();
8402
- const index = topLevel.findIndex((el) => (el.dataset.ohwInstance ?? el.dataset.ohwSection) === instanceId);
9110
+ const index = topLevel.findIndex((el) => instanceIdOf(el) === instanceId);
8403
9111
  if (index === -1) return { isFirst: true, isLast: true };
8404
9112
  return { isFirst: index === 0, isLast: index === topLevel.length - 1 };
8405
9113
  }
@@ -8483,18 +9191,20 @@ function AiSectionOverlay({
8483
9191
  selectedIdRef.current = selectedId;
8484
9192
  const report = useCallback2(
8485
9193
  (el) => {
9194
+ const labelSrc = el ? sectionElementOf(el) : null;
8486
9195
  postToParent2({
8487
9196
  type: "ow:section-selected",
8488
- sectionId: el ? el.dataset.ohwInstance ?? el.dataset.ohwSection ?? null : null,
8489
- sectionLabel: el ? el.dataset.ohwSectionLabel ?? titleCaseSectionId(el.dataset.ohwSection ?? "") : null
9197
+ sectionId: el ? instanceIdOf(el) || null : null,
9198
+ sectionLabel: labelSrc ? labelSrc.dataset.ohwSectionLabel ?? titleCaseSectionId(labelSrc.dataset.ohwSection ?? "") : null
8490
9199
  });
8491
9200
  },
8492
9201
  [postToParent2]
8493
9202
  );
8494
9203
  const selectFromElement = useCallback2(
8495
9204
  (el, options) => {
8496
- const sectionEl = el?.closest("[data-ohw-section]") ?? null;
8497
- const id = sectionEl ? sectionEl.dataset.ohwInstance ?? sectionEl.dataset.ohwSection ?? null : null;
9205
+ const inner = el?.closest("[data-ohw-section]") ?? null;
9206
+ const sectionEl = inner ? movableUnit(inner) : null;
9207
+ const id = sectionEl ? instanceIdOf(sectionEl) || null : null;
8498
9208
  if (id === selectedIdRef.current) return;
8499
9209
  setSelectedId(id);
8500
9210
  if (options?.report !== false) report(sectionEl);
@@ -8560,7 +9270,8 @@ function AiSectionOverlay({
8560
9270
  return;
8561
9271
  }
8562
9272
  const sec = t.closest("[data-ohw-section]");
8563
- setHoveredId(sec ? sec.dataset.ohwInstance ?? sec.dataset.ohwSection ?? null : null);
9273
+ const unit = sec ? movableUnit(sec) : null;
9274
+ setHoveredId(unit ? instanceIdOf(unit) || null : null);
8564
9275
  };
8565
9276
  const onLeave = () => setHoveredId(null);
8566
9277
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -9164,7 +9875,7 @@ function SectionTreeItem({
9164
9875
  /* @__PURE__ */ jsx20(
9165
9876
  "div",
9166
9877
  {
9167
- className: "mr-[-1px] h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
9878
+ className: "-mr-px h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
9168
9879
  "aria-hidden": true
9169
9880
  }
9170
9881
  ),
@@ -9183,7 +9894,7 @@ function SectionTreeItem({
9183
9894
  className: cn(
9184
9895
  "flex h-9 min-w-0 flex-1 items-center gap-2 rounded-md border border-border bg-background p-3",
9185
9896
  interactive && "cursor-pointer hover:bg-muted/30",
9186
- interactive && selected && "border-primary"
9897
+ interactive && selected && "border-bridge-primary"
9187
9898
  ),
9188
9899
  children: [
9189
9900
  /* @__PURE__ */ jsx20(
@@ -9313,7 +10024,7 @@ function UrlOrPageInput({
9313
10024
  };
9314
10025
  const fieldClassName = cn(
9315
10026
  "data-ohw-link-field flex h-[36px] w-full items-center overflow-hidden rounded-md border bg-background pl-3 pr-3 py-2 outline-none transition-[border-color,box-shadow]",
9316
- urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
10027
+ urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-bridge-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
9317
10028
  );
9318
10029
  return /* @__PURE__ */ jsxs13("div", { className: "flex w-full flex-col gap-2 p-0", children: [
9319
10030
  /* @__PURE__ */ jsx23(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
@@ -10357,6 +11068,32 @@ function getNavbarDesktopContainer() {
10357
11068
  function getNavbarDrawerContainer() {
10358
11069
  return document.querySelector("[data-ohw-nav-drawer]");
10359
11070
  }
11071
+ function isNavbarListContainerVisible(el) {
11072
+ if (el.hasAttribute("hidden")) return false;
11073
+ if (el.getClientRects().length === 0) return false;
11074
+ const style = window.getComputedStyle(el);
11075
+ return style.display !== "none" && style.visibility !== "hidden";
11076
+ }
11077
+ function getActiveNavbarListContainer(draggedEl) {
11078
+ const desktop = getNavbarDesktopContainer();
11079
+ const drawer = getNavbarDrawerContainer();
11080
+ if (draggedEl) {
11081
+ if (drawer?.contains(draggedEl)) return drawer;
11082
+ if (desktop?.contains(draggedEl)) return desktop;
11083
+ }
11084
+ if (desktop && isNavbarListContainerVisible(desktop)) return desktop;
11085
+ if (drawer && isNavbarListContainerVisible(drawer)) return drawer;
11086
+ return desktop ?? drawer;
11087
+ }
11088
+ function listActiveNavbarItems(draggedEl) {
11089
+ const container = getActiveNavbarListContainer(draggedEl);
11090
+ if (container) {
11091
+ return Array.from(container.querySelectorAll("[data-ohw-href-key]")).filter(
11092
+ isNavbarLinkItem
11093
+ );
11094
+ }
11095
+ return listNavbarItems();
11096
+ }
10360
11097
  function listNavbarItems() {
10361
11098
  const desktop = getNavbarDesktopContainer();
10362
11099
  if (desktop) {
@@ -11469,20 +12206,23 @@ function isEmptyLabelValue(value) {
11469
12206
  function applyStoredValues(item, content) {
11470
12207
  const hrefKey = socialHrefKey(item);
11471
12208
  const iconKey = socialIconKey(item);
12209
+ const baseKey = hrefKey?.replace(/-href$/, "");
11472
12210
  if (hrefKey && content[hrefKey] !== void 0) item.setAttribute("href", content[hrefKey]);
11473
- if (iconKey) {
12211
+ const glyphKey = iconKey ?? baseKey ?? null;
12212
+ const glyphStored = glyphKey ? content[glyphKey]?.trim() : void 0;
12213
+ if (glyphStored) {
12214
+ ensureIconSlot(item);
11474
12215
  const glyph = item.querySelector(ICON_SELECTOR);
11475
- if (glyph && content[iconKey]) {
11476
- applyIconMarkup(glyph, content[iconKey]);
12216
+ if (glyph) {
12217
+ applyIconMarkup(glyph, glyphStored);
11477
12218
  glyph.removeAttribute(SOCIALS_ICON_PLACEHOLDER_ATTR);
11478
12219
  }
11479
- const label = item.querySelector(`[${SOCIALS_LABEL_ATTR}]`);
11480
- label?.setAttribute("data-ohw-key", socialLabelKey(iconKey));
11481
- const stored = content[socialLabelKey(iconKey)];
11482
- if (label && stored !== void 0) {
11483
- const words = isEmptyLabelValue(stored) ? "" : stored;
11484
- if (label.innerHTML !== words) label.innerHTML = words;
11485
- }
12220
+ }
12221
+ const label = socialLabelElement(item);
12222
+ const labelKey = label?.getAttribute("data-ohw-key");
12223
+ if (label && labelKey && content[labelKey] !== void 0) {
12224
+ const words = isEmptyLabelValue(content[labelKey]) ? "" : content[labelKey];
12225
+ if (label.innerHTML !== words) label.innerHTML = words;
11486
12226
  }
11487
12227
  }
11488
12228
  function socialPlatformKey(iconKey) {
@@ -11571,6 +12311,9 @@ function insertSocialItem(row, after, content = {}, { placeholder = true, keys }
11571
12311
  if (placeholder) {
11572
12312
  const label = socialLabelElement(item);
11573
12313
  if (label) label.textContent = PLACEHOLDER_SOCIAL_LABEL;
12314
+ } else if (keys) {
12315
+ const label = socialLabelElement(item);
12316
+ if (label) label.textContent = "";
11574
12317
  }
11575
12318
  applySocialsDisplayToRow(row, display);
11576
12319
  return { item, hrefKey, iconKey, order: getSocialsOrderFromDom(row.ownerDocument) };
@@ -13071,6 +13814,7 @@ function readLogoSizeState(content, placement) {
13071
13814
  function getLogoElement(el) {
13072
13815
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
13073
13816
  if (marked) return marked;
13817
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
13074
13818
  const root = el.closest("nav, [data-ohw-nav-root], footer");
13075
13819
  if (!root) return null;
13076
13820
  const anchor = el.closest("a");
@@ -13364,7 +14108,7 @@ function DisplaySwitch({
13364
14108
  onClick: () => onChange(!checked),
13365
14109
  className: cn(
13366
14110
  "relative h-5 w-9 shrink-0 rounded-full transition-colors",
13367
- checked ? "bg-primary" : "bg-primary-50",
14111
+ checked ? "bg-bridge-primary" : "bg-primary-50",
13368
14112
  disabled ? "cursor-default opacity-50" : "cursor-pointer"
13369
14113
  ),
13370
14114
  children: /* @__PURE__ */ jsx28(
@@ -13372,7 +14116,7 @@ function DisplaySwitch({
13372
14116
  {
13373
14117
  className: cn(
13374
14118
  "absolute top-0.5 h-4 w-4 rounded-full bg-white transition-all",
13375
- checked ? "left-[1.125rem]" : "left-0.5"
14119
+ checked ? "left-4.5" : "left-0.5"
13376
14120
  )
13377
14121
  }
13378
14122
  )
@@ -13433,8 +14177,8 @@ var lockFooterDuringDrag = lockItemDuringDrag;
13433
14177
  var unlockFooterDragInteraction = unlockItemDragInteraction;
13434
14178
 
13435
14179
  // src/lib/nav-dnd.ts
13436
- function listReorderableNavItems() {
13437
- return listNavbarItems().filter((el) => {
14180
+ function listReorderableNavItems(draggedEl) {
14181
+ return listActiveNavbarItems(draggedEl).filter((el) => {
13438
14182
  const key = el.getAttribute("data-ohw-href-key");
13439
14183
  return isNavbarHrefKey(key) && !isNestedNavChild(el);
13440
14184
  });
@@ -13449,8 +14193,8 @@ function resolveParentNavHrefKey(child) {
13449
14193
  const key = trigger?.getAttribute("data-ohw-href-key");
13450
14194
  return key && isNavbarHrefKey(key) ? key : null;
13451
14195
  }
13452
- function listNavChildren(parentHrefKey) {
13453
- const items = listNavbarItems();
14196
+ function listNavChildren(parentHrefKey, draggedEl) {
14197
+ const items = listActiveNavbarItems(draggedEl);
13454
14198
  const parent = items.find((el) => el.getAttribute("data-ohw-href-key") === parentHrefKey);
13455
14199
  if (!parent) return [];
13456
14200
  const group = parent.closest("[data-ohw-nav-group]");
@@ -13460,20 +14204,83 @@ function listNavChildren(parentHrefKey) {
13460
14204
  (el) => isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))
13461
14205
  );
13462
14206
  }
13463
- function activeNavListContainer() {
13464
- const desktop = getNavbarDesktopContainer();
13465
- if (desktop && desktop.getClientRects().length > 0) {
13466
- const style = window.getComputedStyle(desktop);
13467
- if (style.display !== "none" && style.visibility !== "hidden") return desktop;
14207
+ function isVerticalNavLayout(items, draggedEl) {
14208
+ const visible = items.filter((el) => el.getClientRects().length > 0);
14209
+ if (visible.length >= 2) {
14210
+ const a = visible[0].getBoundingClientRect();
14211
+ const b = visible[1].getBoundingClientRect();
14212
+ const verticalGap = b.top - a.bottom;
14213
+ const horizontalGap = b.left - a.right;
14214
+ return verticalGap > horizontalGap;
14215
+ }
14216
+ const container = getActiveNavbarListContainer(draggedEl);
14217
+ if (!container) return false;
14218
+ const style = window.getComputedStyle(container);
14219
+ return style.flexDirection === "column" || style.flexDirection === "column-reverse";
14220
+ }
14221
+ function buildVerticalRootNavDropSlots(items, draggedEl) {
14222
+ const slots = [];
14223
+ const barThickness = 3;
14224
+ if (items.length === 0) {
14225
+ const container = getActiveNavbarListContainer(draggedEl);
14226
+ if (!container) return slots;
14227
+ const rect = container.getBoundingClientRect();
14228
+ slots.push({
14229
+ insertIndex: 0,
14230
+ parentId: null,
14231
+ left: rect.left,
14232
+ top: rect.top + rect.height / 2 - barThickness / 2,
14233
+ width: Math.max(rect.width, 40),
14234
+ height: barThickness,
14235
+ direction: "horizontal"
14236
+ });
14237
+ return slots;
14238
+ }
14239
+ const edgeGap = (() => {
14240
+ if (items.length < 2) return 16;
14241
+ const a = items[0].getBoundingClientRect();
14242
+ const b = items[1].getBoundingClientRect();
14243
+ return Math.max(0, b.top - a.bottom);
14244
+ })();
14245
+ for (let i = 0; i <= items.length; i++) {
14246
+ let top;
14247
+ let width;
14248
+ let left;
14249
+ if (i === 0) {
14250
+ const first = items[0].getBoundingClientRect();
14251
+ top = first.top - edgeGap / 2 - barThickness / 2;
14252
+ left = first.left;
14253
+ width = first.width;
14254
+ } else if (i === items.length) {
14255
+ const last = items[items.length - 1].getBoundingClientRect();
14256
+ const lastGap = items.length >= 2 ? Math.max(0, last.top - items[items.length - 2].getBoundingClientRect().bottom) : edgeGap;
14257
+ top = last.bottom + lastGap / 2 - barThickness / 2;
14258
+ left = last.left;
14259
+ width = last.width;
14260
+ } else {
14261
+ const prev = items[i - 1].getBoundingClientRect();
14262
+ const next = items[i].getBoundingClientRect();
14263
+ top = (prev.bottom + next.top) / 2 - barThickness / 2;
14264
+ left = Math.min(prev.left, next.left);
14265
+ width = Math.max(prev.right, next.right) - left;
14266
+ }
14267
+ slots.push({
14268
+ insertIndex: i,
14269
+ parentId: null,
14270
+ left,
14271
+ top,
14272
+ width: Math.max(width, 40),
14273
+ height: barThickness,
14274
+ direction: "horizontal"
14275
+ });
13468
14276
  }
13469
- return getNavbarDrawerContainer();
14277
+ return slots;
13470
14278
  }
13471
- function buildRootNavDropSlots() {
13472
- const items = listReorderableNavItems();
14279
+ function buildHorizontalRootNavDropSlots(items, draggedEl) {
13473
14280
  const slots = [];
13474
14281
  const barThickness = 3;
13475
14282
  if (items.length === 0) {
13476
- const container = activeNavListContainer();
14283
+ const container = getActiveNavbarListContainer(draggedEl);
13477
14284
  if (!container) return slots;
13478
14285
  const rect = container.getBoundingClientRect();
13479
14286
  slots.push({
@@ -13504,10 +14311,7 @@ function buildRootNavDropSlots() {
13504
14311
  height = first.height;
13505
14312
  } else if (i === items.length) {
13506
14313
  const last = items[items.length - 1].getBoundingClientRect();
13507
- const lastGap = items.length >= 2 ? Math.max(
13508
- 0,
13509
- last.left - items[items.length - 2].getBoundingClientRect().right
13510
- ) : edgeGap;
14314
+ const lastGap = items.length >= 2 ? Math.max(0, last.left - items[items.length - 2].getBoundingClientRect().right) : edgeGap;
13511
14315
  left = last.right + lastGap / 2 - barThickness / 2;
13512
14316
  top = last.top;
13513
14317
  height = last.height;
@@ -13530,8 +14334,15 @@ function buildRootNavDropSlots() {
13530
14334
  }
13531
14335
  return slots;
13532
14336
  }
13533
- function buildChildNavDropSlots(parentHrefKey) {
13534
- const children = listNavChildren(parentHrefKey);
14337
+ function buildRootNavDropSlots(draggedEl) {
14338
+ const items = listReorderableNavItems(draggedEl);
14339
+ if (isVerticalNavLayout(items, draggedEl)) {
14340
+ return buildVerticalRootNavDropSlots(items, draggedEl);
14341
+ }
14342
+ return buildHorizontalRootNavDropSlots(items, draggedEl);
14343
+ }
14344
+ function buildChildNavDropSlots(parentHrefKey, draggedEl) {
14345
+ const children = listNavChildren(parentHrefKey, draggedEl);
13535
14346
  const slots = [];
13536
14347
  const barThickness = 3;
13537
14348
  if (children.length === 0) return slots;
@@ -13552,10 +14363,7 @@ function buildChildNavDropSlots(parentHrefKey) {
13552
14363
  width = first.width;
13553
14364
  } else if (i === children.length) {
13554
14365
  const last = children[children.length - 1].getBoundingClientRect();
13555
- const lastGap = children.length >= 2 ? Math.max(
13556
- 0,
13557
- last.top - children[children.length - 2].getBoundingClientRect().bottom
13558
- ) : edgeGap;
14366
+ const lastGap = children.length >= 2 ? Math.max(0, last.top - children[children.length - 2].getBoundingClientRect().bottom) : edgeGap;
13559
14367
  top = last.bottom + lastGap / 2 - barThickness / 2;
13560
14368
  left = last.left;
13561
14369
  width = last.width;
@@ -13578,18 +14386,19 @@ function buildChildNavDropSlots(parentHrefKey) {
13578
14386
  }
13579
14387
  return slots;
13580
14388
  }
13581
- function buildAllNavDropSlots(draggedHrefKey) {
13582
- const dragged = listNavbarItems().find((el) => el.getAttribute("data-ohw-href-key") === draggedHrefKey);
13583
- if (!dragged) return buildRootNavDropSlots();
14389
+ function buildAllNavDropSlots(draggedHrefKey, draggedEl) {
14390
+ const activeItems = listActiveNavbarItems(draggedEl);
14391
+ const dragged = (draggedEl && activeItems.includes(draggedEl) ? draggedEl : null) ?? activeItems.find((el) => el.getAttribute("data-ohw-href-key") === draggedHrefKey) ?? listNavbarItems().find((el) => el.getAttribute("data-ohw-href-key") === draggedHrefKey);
14392
+ if (!dragged) return buildRootNavDropSlots(draggedEl);
13584
14393
  if (isNestedNavChild(dragged)) {
13585
14394
  const parentKey = resolveParentNavHrefKey(dragged);
13586
14395
  if (!parentKey) return [];
13587
- return buildChildNavDropSlots(parentKey);
14396
+ return buildChildNavDropSlots(parentKey, draggedEl);
13588
14397
  }
13589
- return buildRootNavDropSlots();
14398
+ return buildRootNavDropSlots(draggedEl);
13590
14399
  }
13591
- function hitTestNavDropSlot(clientX, clientY, draggedHrefKey) {
13592
- const slots = buildAllNavDropSlots(draggedHrefKey);
14400
+ function hitTestNavDropSlot(clientX, clientY, draggedHrefKey, draggedEl) {
14401
+ const slots = buildAllNavDropSlots(draggedHrefKey, draggedEl);
13593
14402
  let best = null;
13594
14403
  for (const slot of slots) {
13595
14404
  const cx2 = slot.left + slot.width / 2;
@@ -13640,6 +14449,12 @@ function useNavItemDrag({
13640
14449
  setIsItemDragging(false);
13641
14450
  if (keepOpenEl?.isConnected) {
13642
14451
  setNavGroupForceOpen(keepOpenEl, true);
14452
+ requestAnimationFrame(() => {
14453
+ if (keepOpenEl.isConnected) setNavGroupForceOpen(keepOpenEl, true);
14454
+ requestAnimationFrame(() => {
14455
+ if (keepOpenEl.isConnected) setNavGroupForceOpen(keepOpenEl, true);
14456
+ });
14457
+ });
13643
14458
  } else {
13644
14459
  setNavGroupForceOpen(null, false);
13645
14460
  }
@@ -13655,7 +14470,7 @@ function useNavItemDrag({
13655
14470
  }
13656
14471
  session.activeSlot = activeSlot;
13657
14472
  setSiblingHintRects([]);
13658
- const slots = buildAllNavDropSlots(session.hrefKey);
14473
+ const slots = buildAllNavDropSlots(session.hrefKey, session.draggedEl);
13659
14474
  setNavDropSlots(slots);
13660
14475
  const activeIdx = activeSlot ? slots.findIndex(
13661
14476
  (s) => s.parentId === activeSlot.parentId && s.insertIndex === activeSlot.insertIndex
@@ -13691,7 +14506,12 @@ function useNavItemDrag({
13691
14506
  if (session.wasSelected && selectedElRef.current === session.draggedEl) {
13692
14507
  setToolbarRect(rect);
13693
14508
  }
13694
- const initialSlot = hitTestNavDropSlot(session.lastClientX, session.lastClientY, session.hrefKey);
14509
+ const initialSlot = hitTestNavDropSlot(
14510
+ session.lastClientX,
14511
+ session.lastClientY,
14512
+ session.hrefKey,
14513
+ session.draggedEl
14514
+ );
13695
14515
  refreshNavDragVisuals(session, initialSlot, session.lastClientX, session.lastClientY);
13696
14516
  },
13697
14517
  [
@@ -13713,10 +14533,11 @@ function useNavItemDrag({
13713
14533
  }
13714
14534
  const x = typeof clientX === "number" && (clientX !== 0 || clientY !== 0) ? clientX : session.lastClientX;
13715
14535
  const y = typeof clientY === "number" && (clientX !== 0 || clientY !== 0) ? clientY : session.lastClientY;
13716
- const slot = session.activeSlot ?? hitTestNavDropSlot(x, y, session.hrefKey);
14536
+ const slot = session.activeSlot ?? hitTestNavDropSlot(x, y, session.hrefKey, session.draggedEl);
13717
14537
  const planned = slot != null ? planNavItemMove(session.hrefKey, slot.parentId, slot.insertIndex) : null;
13718
14538
  const wasSelected = session.wasSelected;
13719
14539
  const hrefKey = session.hrefKey;
14540
+ const keepDropdownOpen = Boolean(session.draggedEl.closest("[data-ohw-nav-children]"));
13720
14541
  const applySelectionAfterDrop = () => {
13721
14542
  if (!wasSelected) {
13722
14543
  deselectRef.current();
@@ -13750,6 +14571,14 @@ function useNavItemDrag({
13750
14571
  });
13751
14572
  applySelectionAfterDrop();
13752
14573
  clearNavDragVisuals();
14574
+ if (keepDropdownOpen) {
14575
+ requestAnimationFrame(() => {
14576
+ const desktop = document.querySelector("[data-ohw-nav-container]");
14577
+ const drawer = document.querySelector("[data-ohw-nav-drawer]");
14578
+ const link = desktop?.querySelector(`[data-ohw-href-key="${CSS.escape(hrefKey)}"]`) ?? drawer?.querySelector(`[data-ohw-href-key="${CSS.escape(hrefKey)}"]`) ?? document.querySelector(`[data-ohw-href-key="${CSS.escape(hrefKey)}"]`);
14579
+ if (link) setNavGroupForceOpen(link, true);
14580
+ });
14581
+ }
13753
14582
  requestAnimationFrame(() => {
13754
14583
  if (editContentRef.current[NAV_ORDER_KEY] === planned.orderJson) {
13755
14584
  applyNavForest(planned.forest);
@@ -13789,7 +14618,7 @@ function useNavItemDrag({
13789
14618
  if (!session) return false;
13790
14619
  e.preventDefault();
13791
14620
  if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
13792
- const slot = hitTestNavDropSlot(e.clientX, e.clientY, session.hrefKey);
14621
+ const slot = hitTestNavDropSlot(e.clientX, e.clientY, session.hrefKey, session.draggedEl);
13793
14622
  refreshNavDragVisualsRef.current(session, slot, e.clientX, e.clientY);
13794
14623
  return true;
13795
14624
  },
@@ -13835,7 +14664,7 @@ function useNavItemDrag({
13835
14664
  clearTextSelection();
13836
14665
  const session = navDragRef.current;
13837
14666
  if (!session) return;
13838
- const slot = hitTestNavDropSlot(e.clientX, e.clientY, session.hrefKey);
14667
+ const slot = hitTestNavDropSlot(e.clientX, e.clientY, session.hrefKey, session.draggedEl);
13839
14668
  refreshNavDragVisualsRef.current(session, slot, e.clientX, e.clientY);
13840
14669
  return;
13841
14670
  }
@@ -13851,7 +14680,8 @@ function useNavItemDrag({
13851
14680
  } catch {
13852
14681
  }
13853
14682
  if (linkPopoverOpenRef.current) setLinkPopover(null);
13854
- if (activeElRef.current) deactivateRef.current();
14683
+ const isNestedDrag = Boolean(pending.el.closest("[data-ohw-nav-children]"));
14684
+ if (activeElRef.current && !isNestedDrag) deactivateRef.current();
13855
14685
  const key = pending.el.getAttribute("data-ohw-href-key");
13856
14686
  if (!key) return;
13857
14687
  beginNavDragRef.current({
@@ -13862,6 +14692,9 @@ function useNavItemDrag({
13862
14692
  lastClientY: e.clientY,
13863
14693
  activeSlot: null
13864
14694
  });
14695
+ if (activeElRef.current && isNestedDrag) {
14696
+ deactivateRef.current();
14697
+ }
13865
14698
  };
13866
14699
  const endPointerDrag = (e) => {
13867
14700
  const pending = navPointerDragRef.current;
@@ -13939,6 +14772,7 @@ function useNavItemDrag({
13939
14772
  );
13940
14773
  return {
13941
14774
  navDragRef,
14775
+ navPointerDragRef,
13942
14776
  navDropSlots,
13943
14777
  activeNavDropIndex,
13944
14778
  startNavLinkDrag,
@@ -14143,15 +14977,17 @@ function useSectionDrag({
14143
14977
  clearSectionDragVisuals();
14144
14978
  return;
14145
14979
  }
14146
- const orderJson = JSON.stringify(entries);
14980
+ const orderJson = JSON.stringify(
14981
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
14982
+ );
14147
14983
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
14148
14984
  setAiSectionOrder(orderJson, window.location.pathname);
14149
14985
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
14150
- applyPersistedOrder(entries);
14986
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
14151
14987
  clearSectionDragVisuals();
14152
14988
  requestAnimationFrame(() => {
14153
14989
  if (editContentRef.current[SECTION_ORDER_KEY] === orderJson) {
14154
- applyPersistedOrder(entries);
14990
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
14155
14991
  }
14156
14992
  requestAnimationFrame(() => {
14157
14993
  window.dispatchEvent(new Event("resize"));
@@ -14184,8 +15020,9 @@ function useSectionDrag({
14184
15020
  const target = e.target;
14185
15021
  if (!(target instanceof HTMLElement)) return;
14186
15022
  if (target.closest(SECTION_DRAG_EXCLUDED_SELECTOR)) return;
14187
- const sectionEl = target.closest("[data-ohw-section]");
14188
- if (!sectionEl || isChromeSection(sectionEl) || sectionEl.dataset.ohwSection === "footer") return;
15023
+ const inner = target.closest("[data-ohw-section]");
15024
+ if (!inner || isChromeSection(inner) || inner.dataset.ohwSection === "footer") return;
15025
+ const sectionEl = movableUnit(inner);
14189
15026
  if (!topLevelSections().includes(sectionEl)) return;
14190
15027
  startSectionPressDrag(sectionEl, e.clientX, e.clientY, e.pointerId);
14191
15028
  };
@@ -14452,10 +15289,15 @@ function collectEditableNodes(extraContent, root = document) {
14452
15289
  if (el.dataset.ohwEditable === "link") {
14453
15290
  return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref3(el) };
14454
15291
  }
15292
+ if (el.dataset.ohwEditable === "map") {
15293
+ return { key: el.dataset.ohwKey ?? "", type: "map", text: el.dataset.ohwMapQuery ?? "" };
15294
+ }
14455
15295
  return {
14456
15296
  key: el.dataset.ohwKey ?? "",
14457
15297
  type: el.dataset.ohwEditable ?? "text",
14458
- text: el.dataset.ohwEditable === "plain" ? el.innerText ?? "" : el.innerHTML
15298
+ // innerText does not exist on SVG elements (a circular-badge <textPath> is a legitimate
15299
+ // plain editable) — textContent is the value there.
15300
+ text: el.dataset.ohwEditable === "plain" ? el.innerText ?? el.textContent ?? "" : el.innerHTML
14459
15301
  };
14460
15302
  });
14461
15303
  const hrefEls = Array.from(root.querySelectorAll("[data-ohw-href-key]"));
@@ -14892,7 +15734,7 @@ var badgeVariants = cva(
14892
15734
  {
14893
15735
  variants: {
14894
15736
  variant: {
14895
- default: "border-transparent bg-primary text-primary-foreground",
15737
+ default: "border-transparent bg-bridge-primary text-primary-foreground",
14896
15738
  secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
14897
15739
  destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
14898
15740
  outline: "text-foreground"
@@ -15014,21 +15856,10 @@ function parseSchedulingInsertAfter(insertAfter) {
15014
15856
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
15015
15857
  };
15016
15858
  }
15017
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
15018
- const parsed = parseSchedulingInsertAfter(insertAfter);
15019
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
15020
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
15021
- return { effectiveInsertAfter, insertBefore };
15022
- }
15023
- function getSchedulingMountPoint(insertAfter) {
15024
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
15025
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
15026
- if (!anchorEl && anchor === "scheduling") {
15027
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
15028
- anchorEl = widgets.at(-1) ?? null;
15029
- }
15030
- if (!anchorEl) return null;
15031
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
15859
+ function resolveEntryAnchor(entry) {
15860
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
15861
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
15862
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
15032
15863
  }
15033
15864
  function schedulingMountDepth(insertAfter) {
15034
15865
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -15045,8 +15876,7 @@ function getPageSchedulingEntries(raw) {
15045
15876
  }
15046
15877
  }
15047
15878
  function isSchedulingWidgetMissing(entry) {
15048
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
15049
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
15879
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
15050
15880
  }
15051
15881
  function hasMissingSchedulingWidgets(entries) {
15052
15882
  return entries.some(isSchedulingWidgetMissing);
@@ -15084,18 +15914,18 @@ function initSectionsFromContent(content, removeExisting = false, currentPath =
15084
15914
  } catch {
15085
15915
  }
15086
15916
  }
15087
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
15088
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
15089
- const sectionId = schedulingSectionId(effectiveInsertAfter);
15917
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
15918
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
15919
+ const sectionId = schedulingSectionId(widgetId);
15090
15920
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
15091
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
15092
- if (!mountPoint) return false;
15921
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
15922
+ if (!anchorEl) return false;
15923
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
15093
15924
  const container = document.createElement("div");
15094
15925
  container.dataset.ohwSectionContainer = "scheduling";
15095
- container.dataset.ohwSection = sectionId;
15096
15926
  container.dataset.ohwInstance = sectionId;
15097
- if (insertBefore) {
15098
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
15927
+ if (beforeId) {
15928
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
15099
15929
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
15100
15930
  if (!beforePoint) return false;
15101
15931
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -15106,20 +15936,26 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
15106
15936
  }
15107
15937
  tail.insertAdjacentElement("afterend", container);
15108
15938
  }
15109
- const root = createRoot2(container);
15110
- schedulingRoots.set(container, root);
15111
- flushSync2(() => {
15112
- root.render(
15113
- /* @__PURE__ */ jsx33(
15114
- SchedulingWidget,
15115
- {
15116
- notifyOnConnect,
15117
- initialScheduleId: scheduleId,
15118
- insertAfter: effectiveInsertAfter
15119
- }
15120
- )
15121
- );
15122
- });
15939
+ try {
15940
+ const root = createRoot2(container);
15941
+ schedulingRoots.set(container, root);
15942
+ flushSync2(() => {
15943
+ root.render(
15944
+ /* @__PURE__ */ jsx33(
15945
+ SchedulingWidget,
15946
+ {
15947
+ notifyOnConnect,
15948
+ initialScheduleId: scheduleId,
15949
+ insertAfter: widgetId
15950
+ }
15951
+ )
15952
+ );
15953
+ });
15954
+ } catch (err) {
15955
+ console.error("[ow:scheduling] render threw", err);
15956
+ container.remove();
15957
+ return false;
15958
+ }
15123
15959
  const tracker = getSectionsTracker();
15124
15960
  let sections = [];
15125
15961
  try {
@@ -15127,10 +15963,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
15127
15963
  } catch {
15128
15964
  }
15129
15965
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
15130
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
15966
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
15131
15967
  sections.push({
15132
15968
  type: "scheduling",
15133
- insertAfter: effectiveInsertAfter,
15969
+ insertAfter: widgetId,
15970
+ anchorId,
15971
+ beforeId: beforeId ?? null,
15134
15972
  pagePath: window.location.pathname,
15135
15973
  ...scheduleId ? { scheduleId } : {}
15136
15974
  });
@@ -15144,7 +15982,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
15144
15982
  for (let i = pending.length - 1; i >= 0; i--) {
15145
15983
  const entry = pending[i];
15146
15984
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
15147
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId ?? null)) {
15985
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
15986
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
15148
15987
  pending.splice(i, 1);
15149
15988
  }
15150
15989
  }
@@ -15236,7 +16075,7 @@ var EDITOR_CHROME_SELECTOR = '[data-ohw-toolbar], [data-ohw-form-toolbar], [data
15236
16075
  function isOverEditorChrome(x, y) {
15237
16076
  return document.elementsFromPoint(x, y).some((el) => el instanceof HTMLElement && el.matches(EDITOR_CHROME_SELECTOR));
15238
16077
  }
15239
- var NON_MEDIA_SELECTOR = '[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]):not([data-ohw-editable="icon"]):not([data-ohw-editable="form"])';
16078
+ var NON_MEDIA_SELECTOR = '[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]):not([data-ohw-editable="icon"]):not([data-ohw-editable="form"]):not([data-ohw-editable="map"])';
15240
16079
  function getVideoEl2(el) {
15241
16080
  return el instanceof HTMLVideoElement ? el : el.querySelector("video");
15242
16081
  }
@@ -15292,6 +16131,12 @@ function applyVideoSettingNode(key, val) {
15292
16131
  });
15293
16132
  return true;
15294
16133
  }
16134
+ function applyMapQuery(el, val) {
16135
+ if (!(el instanceof HTMLIFrameElement)) return;
16136
+ const nextSrc = `https://www.google.com/maps?q=${encodeURIComponent(val)}&output=embed`;
16137
+ if (el.src !== nextSrc) el.src = nextSrc;
16138
+ el.setAttribute("data-ohw-map-query", val);
16139
+ }
15295
16140
  function applyLinkByKey(key, val) {
15296
16141
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
15297
16142
  if (el.dataset.ohwEditable === "link") applyLinkHref(el, val);
@@ -15302,6 +16147,11 @@ function applyLinkByKey(key, val) {
15302
16147
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
15303
16148
  }
15304
16149
  }
16150
+ function isInsideLinkEditor(target) {
16151
+ return Boolean(
16152
+ 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"]')
16153
+ );
16154
+ }
15305
16155
  function isInsideFloatingPanel(target) {
15306
16156
  return Boolean(target.closest("[data-ohw-floating-panel]"));
15307
16157
  }
@@ -15309,11 +16159,6 @@ function isPointOverFloatingPanel(clientX, clientY) {
15309
16159
  const el = document.elementFromPoint(clientX, clientY);
15310
16160
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
15311
16161
  }
15312
- function isInsideLinkEditor(target) {
15313
- return Boolean(
15314
- 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"]')
15315
- );
15316
- }
15317
16162
  function getHrefKeyFromElement(el) {
15318
16163
  if (!el) return null;
15319
16164
  const anchor = el.closest("[data-ohw-href-key]");
@@ -15326,6 +16171,7 @@ function isNavDropdownPanelOpen(childrenRoot) {
15326
16171
  if (childrenRoot.closest("[data-ohw-nav-drawer]")) return true;
15327
16172
  const group = childrenRoot.closest("[data-ohw-nav-group]");
15328
16173
  if (group?.hasAttribute("data-ohw-nav-force-open")) return true;
16174
+ if (navDropdownsOpenOnClick()) return false;
15329
16175
  if (childrenRoot.clientHeight < 2 || childrenRoot.clientWidth < 2) return false;
15330
16176
  const style = window.getComputedStyle(childrenRoot);
15331
16177
  if (style.display === "none" || style.visibility === "hidden") return false;
@@ -15369,6 +16215,11 @@ function getNavigationItemAnchor(el) {
15369
16215
  function isNavigationItem2(el) {
15370
16216
  return getNavigationItemAnchor(el) !== null;
15371
16217
  }
16218
+ function isNavFooterScopedNavigationItem(el) {
16219
+ return Boolean(
16220
+ el.closest("nav, footer, [data-ohw-nav-container], [data-ohw-nav-drawer]")
16221
+ );
16222
+ }
15372
16223
  function socialWordsClicked(target, item) {
15373
16224
  const text = target.closest('[data-ohw-editable="text"], [data-ohw-editable="plain"]');
15374
16225
  return Boolean(text && item.contains(text));
@@ -15572,13 +16423,14 @@ function getNavigationSelectionParent(el) {
15572
16423
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
15573
16424
  return getFooterLinksContainer();
15574
16425
  }
15575
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
16426
+ 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)) {
15576
16427
  return getNavigationRoot(el);
15577
16428
  }
15578
16429
  return null;
15579
16430
  }
15580
16431
  function collectNavigationItemSiblingHintRects(selected) {
15581
16432
  if (!isNavigationItem2(selected)) return [];
16433
+ if (!isNavFooterScopedNavigationItem(selected)) return [];
15582
16434
  const socialsRow = findSocialsRow(selected);
15583
16435
  if (socialsRow) {
15584
16436
  return listSocialItems(socialsRow).filter((item) => item !== selected).map((item) => item.getBoundingClientRect());
@@ -15787,7 +16639,6 @@ var ICONS = {
15787
16639
  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"/>',
15788
16640
  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"/>'
15789
16641
  };
15790
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
15791
16642
  var SELECTION_CHROME_GAP2 = 4;
15792
16643
  var TOOLBAR_STROKE_GAP2 = 4;
15793
16644
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -16167,6 +17018,7 @@ function StateToggle({
16167
17018
  );
16168
17019
  }
16169
17020
  var contentCache = /* @__PURE__ */ new Map();
17021
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
16170
17022
  var brandingCache = /* @__PURE__ */ new Map();
16171
17023
  var OHW_LOADER_STYLE = {
16172
17024
  position: "fixed",
@@ -16696,13 +17548,6 @@ function OhhwellsBridge() {
16696
17548
  const [isItemDragging, setIsItemDragging] = useState13(false);
16697
17549
  const [isFooterFrameSelection, setIsFooterFrameSelection] = useState13(false);
16698
17550
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
16699
- const [floatingPanel, setFloatingPanel] = useState13(null);
16700
- const floatingPanelOpenRef = useRef10(false);
16701
- floatingPanelOpenRef.current = floatingPanel !== null;
16702
- const [floatingPanelPos, setFloatingPanelPos] = useState13(null);
16703
- const [logoSizeDraft, setLogoSizeDraft] = useState13(null);
16704
- const [editorViewport, setEditorViewport] = useState13("desktop");
16705
- const [parentScrollSnap, setParentScrollSnap] = useState13(null);
16706
17551
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = useState13(null);
16707
17552
  const [footerHeadingVisible, setFooterHeadingVisible] = useState13(null);
16708
17553
  const footerDragRef = useRef10(null);
@@ -16720,6 +17565,13 @@ function OhhwellsBridge() {
16720
17565
  const brandKitRef = useRef10("");
16721
17566
  const stylesRef = useRef10("");
16722
17567
  const pendingDeleteUndoRef = useRef10(null);
17568
+ const [floatingPanel, setFloatingPanel] = useState13(null);
17569
+ const floatingPanelOpenRef = useRef10(false);
17570
+ const setFloatingPanelRef = useRef10(setFloatingPanel);
17571
+ const [floatingPanelPos, setFloatingPanelPos] = useState13(null);
17572
+ const [logoSizeDraft, setLogoSizeDraft] = useState13(null);
17573
+ const [editorViewport, setEditorViewport] = useState13("desktop");
17574
+ const [parentScrollSnap, setParentScrollSnap] = useState13(null);
16723
17575
  const [sitePages, setSitePages] = useState13([]);
16724
17576
  const [sectionsByPath, setSectionsByPath] = useState13({});
16725
17577
  const sectionsPrefetchGenRef = useRef10(0);
@@ -16728,9 +17580,21 @@ function OhhwellsBridge() {
16728
17580
  const linkPopoverOpenRef = useRef10(false);
16729
17581
  const linkPopoverGraceUntilRef = useRef10(0);
16730
17582
  setLinkPopoverRef.current = setLinkPopover;
17583
+ setFloatingPanelRef.current = setFloatingPanel;
16731
17584
  linkPopoverSessionRef.current = linkPopover;
17585
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
17586
+ useEffect13(() => {
17587
+ const syncViewport = () => {
17588
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
17589
+ setEditorViewport((prev) => prev === next ? prev : next);
17590
+ };
17591
+ syncViewport();
17592
+ window.addEventListener("resize", syncViewport);
17593
+ return () => window.removeEventListener("resize", syncViewport);
17594
+ }, []);
16732
17595
  const {
16733
17596
  navDragRef,
17597
+ navPointerDragRef,
16734
17598
  navDropSlots,
16735
17599
  activeNavDropIndex,
16736
17600
  startNavLinkDrag,
@@ -16933,7 +17797,12 @@ function OhhwellsBridge() {
16933
17797
  if (owner) persistFieldsRef.current(owner);
16934
17798
  }
16935
17799
  activeElRef.current = null;
16936
- setNavGroupForceOpen(null, false);
17800
+ const preserveNavDropdown = Boolean(navPointerDragRef.current?.el?.closest("[data-ohw-nav-children]")) || Boolean(navDragRef.current?.draggedEl?.closest("[data-ohw-nav-children]")) || Boolean(
17801
+ selectedElRef.current?.closest("[data-ohw-nav-children]") && selectedElRef.current?.closest("[data-ohw-nav-group]")?.hasAttribute("data-ohw-nav-force-open")
17802
+ );
17803
+ if (!preserveNavDropdown) {
17804
+ setNavGroupForceOpen(null, false);
17805
+ }
16937
17806
  setReorderHrefKey(null);
16938
17807
  setReorderDragDisabled(false);
16939
17808
  if (!selectedElRef.current) {
@@ -17055,7 +17924,10 @@ function OhhwellsBridge() {
17055
17924
  setSelectedIsSocialsRow(false);
17056
17925
  const isDropdownTrigger = !isNestedNavChild(navAnchor) && (navItemHasDropdownChildren(navAnchor) || navItemOwnsDropdownPanel(navAnchor));
17057
17926
  if (isNestedNavChild(navAnchor)) {
17058
- setNavGroupForceOpen(navAnchor, true);
17927
+ const group = navAnchor.closest("[data-ohw-nav-group]");
17928
+ if (group?.hasAttribute("data-ohw-nav-force-open")) {
17929
+ setNavGroupForceOpen(navAnchor, true);
17930
+ }
17059
17931
  setNavDropdownPreviewOpen(null);
17060
17932
  } else if (isDropdownTrigger) {
17061
17933
  const group = navAnchor.closest("[data-ohw-nav-group]");
@@ -17137,6 +18009,10 @@ function OhhwellsBridge() {
17137
18009
  const handleNavDropdownOpenChange = useCallback8((open) => {
17138
18010
  const selected = selectedElRef.current;
17139
18011
  if (!selected || !isNavigationItem2(selected)) return;
18012
+ if (isNestedNavChild(selected)) return;
18013
+ const group = selected.closest("[data-ohw-nav-group]");
18014
+ const domOpen = Boolean(group?.hasAttribute("data-ohw-nav-force-open"));
18015
+ if (open === domOpen) return;
17140
18016
  setNavGroupForceOpen(selected, open);
17141
18017
  setNavDropdownPreviewOpen(open);
17142
18018
  requestAnimationFrame(() => {
@@ -17666,7 +18542,10 @@ function OhhwellsBridge() {
17666
18542
  clearHrefKeyHover(anchor);
17667
18543
  const isDropdownTrigger = !isNestedNavChild(anchor) && (navItemHasDropdownChildren(anchor) || navItemOwnsDropdownPanel(anchor));
17668
18544
  if (isNestedNavChild(anchor)) {
17669
- setNavGroupForceOpen(anchor, true);
18545
+ const group = anchor.closest("[data-ohw-nav-group]");
18546
+ if (group?.hasAttribute("data-ohw-nav-force-open")) {
18547
+ setNavGroupForceOpen(anchor, true);
18548
+ }
17670
18549
  setNavDropdownPreviewOpen(null);
17671
18550
  } else if (isDropdownTrigger) {
17672
18551
  setNavGroupForceOpen(null, false);
@@ -18053,6 +18932,7 @@ function OhhwellsBridge() {
18053
18932
  }
18054
18933
  if (typeof content[STYLE_STORE_KEY] === "string") {
18055
18934
  stylesRef.current = content[STYLE_STORE_KEY];
18935
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
18056
18936
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
18057
18937
  }
18058
18938
  applyBrandChrome(content);
@@ -18060,11 +18940,11 @@ function OhhwellsBridge() {
18060
18940
  for (const [key, val] of Object.entries(content)) {
18061
18941
  if (key === "__ohw_sections") continue;
18062
18942
  if (key === AI_SECTIONS_KEY) continue;
18943
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
18944
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
18063
18945
  if (key === BRAND_KIT_KEY) continue;
18064
18946
  if (key === STYLE_STORE_KEY) continue;
18065
18947
  if (BRAND_CHROME_KEYS.has(key)) continue;
18066
- if (key === LOGO_PLACEHOLDER_KEY) continue;
18067
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
18068
18948
  if (applyVideoSettingNode(key, val)) continue;
18069
18949
  if (applyCarouselNode(key, val)) continue;
18070
18950
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -18092,6 +18972,8 @@ function OhhwellsBridge() {
18092
18972
  }
18093
18973
  } else if (el.dataset.ohwEditable === "link") {
18094
18974
  applyLinkHref(el, val);
18975
+ } else if (el.dataset.ohwEditable === "map") {
18976
+ applyMapQuery(el, val);
18095
18977
  } else if (el.dataset.ohwEditable === "icon") {
18096
18978
  applyIconMarkup(el, val);
18097
18979
  } else if (el.dataset.ohwEditable === "form") {
@@ -18110,6 +18992,9 @@ function OhhwellsBridge() {
18110
18992
  applySocialsDisplayFromContent(content);
18111
18993
  applySocialsLabelsFromContent(content, document, { fillEmptyOnly: isEditModeRef.current });
18112
18994
  if (isEditModeRef.current) requestMissingSocialIconsRef.current();
18995
+ if (typeof content[STYLE_STORE_KEY] === "string") {
18996
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
18997
+ }
18113
18998
  enforceLinkHrefs();
18114
18999
  initSectionsFromContent(content, true);
18115
19000
  sectionsLoadedRef.current = true;
@@ -18130,7 +19015,9 @@ function OhhwellsBridge() {
18130
19015
  let cancelled = false;
18131
19016
  setFetchState("loading");
18132
19017
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
18133
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
19018
+ const initialPath = pathname;
19019
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
19020
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18134
19021
  if (cancelled) return;
18135
19022
  const content = data?.content ?? {};
18136
19023
  const branding = Boolean(data?.showBranding);
@@ -18239,7 +19126,9 @@ function OhhwellsBridge() {
18239
19126
  }, [isEditMode]);
18240
19127
  useEffect13(() => {
18241
19128
  if (isEditMode || fetchState !== "done") return;
19129
+ console.log("env", process.env.NEXT_PUBLIC_FLOWOPS_API_URL);
18242
19130
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19131
+ console.log({ apiUrl, subdomain });
18243
19132
  bindPublishedForms(apiUrl, subdomain, contentCache.get(subdomain) ?? {});
18244
19133
  }, [isEditMode, fetchState, subdomain]);
18245
19134
  useEffect13(() => {
@@ -18249,10 +19138,10 @@ function OhhwellsBridge() {
18249
19138
  const applyFromCache = () => {
18250
19139
  const content = contentCache.get(subdomain);
18251
19140
  if (!content) return;
18252
- retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
18253
- initSectionInstancesFromContent(content, window.location.pathname);
18254
19141
  observer?.disconnect();
18255
19142
  try {
19143
+ retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
19144
+ initSectionInstancesFromContent(content, window.location.pathname);
18256
19145
  applyBrandChrome(content);
18257
19146
  if (typeof content[BRAND_KIT_KEY] === "string") {
18258
19147
  applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
@@ -18264,16 +19153,17 @@ function OhhwellsBridge() {
18264
19153
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
18265
19154
  }
18266
19155
  if (typeof content[STYLE_STORE_KEY] === "string") {
19156
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
18267
19157
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
18268
19158
  }
18269
19159
  for (const [key, val] of Object.entries(content)) {
18270
19160
  if (key === "__ohw_sections") continue;
18271
19161
  if (key === AI_SECTIONS_KEY) continue;
19162
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
19163
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
18272
19164
  if (key === BRAND_KIT_KEY) continue;
18273
19165
  if (key === STYLE_STORE_KEY) continue;
18274
19166
  if (BRAND_CHROME_KEYS.has(key)) continue;
18275
- if (key === LOGO_PLACEHOLDER_KEY) continue;
18276
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
18277
19167
  if (applyVideoSettingNode(key, val)) continue;
18278
19168
  if (applyCarouselNode(key, val)) continue;
18279
19169
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -18288,6 +19178,8 @@ function OhhwellsBridge() {
18288
19178
  if (video && video.src !== val) applyVideoSrc(video, val);
18289
19179
  } else if (el.dataset.ohwEditable === "link") {
18290
19180
  applyLinkHref(el, val);
19181
+ } else if (el.dataset.ohwEditable === "map") {
19182
+ applyMapQuery(el, val);
18291
19183
  } else if (el.dataset.ohwEditable === "form") {
18292
19184
  } else if (isIconMarkupValue(val)) {
18293
19185
  } else if (el.innerHTML !== val) {
@@ -18303,6 +19195,9 @@ function OhhwellsBridge() {
18303
19195
  applySocialsDisplayFromContent(content);
18304
19196
  applySocialsLabelsFromContent(content, document, { fillEmptyOnly: isEditModeRef.current });
18305
19197
  if (isEditModeRef.current) requestMissingSocialIconsRef.current();
19198
+ if (typeof content[STYLE_STORE_KEY] === "string") {
19199
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
19200
+ }
18306
19201
  document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
18307
19202
  if (!form.querySelector("[data-ohw-form-success]")) {
18308
19203
  reconcileFieldsFromContent(form, content);
@@ -18319,6 +19214,17 @@ function OhhwellsBridge() {
18319
19214
  debounceTimer = setTimeout(applyFromCache, 150);
18320
19215
  };
18321
19216
  applyFromCache();
19217
+ const pathCacheKey = `${subdomain}::${pathname}`;
19218
+ if (!fetchedContentPaths.has(pathCacheKey)) {
19219
+ fetchedContentPaths.add(pathCacheKey);
19220
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19221
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
19222
+ if (!data?.content) return;
19223
+ contentCache.set(subdomain, data.content);
19224
+ applyFromCache();
19225
+ }).catch(() => {
19226
+ });
19227
+ }
18322
19228
  observer = new MutationObserver(scheduleApply);
18323
19229
  observer.observe(document.body, { childList: true, subtree: true });
18324
19230
  return () => {
@@ -18343,6 +19249,10 @@ function OhhwellsBridge() {
18343
19249
  deselectRef.current();
18344
19250
  deactivateRef.current();
18345
19251
  }, [pathname, isEditMode]);
19252
+ useEffect13(() => {
19253
+ if (!isEditMode) return;
19254
+ initSectionInstancesFromContent(editContentRef.current, pathname);
19255
+ }, [pathname, isEditMode]);
18346
19256
  useEffect13(() => {
18347
19257
  const contentForNav = () => {
18348
19258
  if (isEditMode) return editContentRef.current;
@@ -18434,26 +19344,11 @@ function OhhwellsBridge() {
18434
19344
  const t2 = setTimeout(measure, 500);
18435
19345
  const ro = new ResizeObserver(schedule);
18436
19346
  ro.observe(document.body);
18437
- let lastWidth = window.innerWidth;
18438
- let resizeTimers = [];
18439
- const clearResizeTimers = () => {
18440
- resizeTimers.forEach(clearTimeout);
18441
- resizeTimers = [];
18442
- };
18443
- const handleResize = () => {
18444
- if (window.innerWidth === lastWidth) return;
18445
- lastWidth = window.innerWidth;
18446
- clearResizeTimers();
18447
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
18448
- };
18449
- window.addEventListener("resize", handleResize);
18450
19347
  return () => {
18451
19348
  clearTimeout(t1);
18452
19349
  clearTimeout(t2);
18453
19350
  if (raf != null) cancelAnimationFrame(raf);
18454
19351
  ro.disconnect();
18455
- clearResizeTimers();
18456
- window.removeEventListener("resize", handleResize);
18457
19352
  };
18458
19353
  }, [pathname, isEditMode, postToParent2]);
18459
19354
  useEffect13(() => {
@@ -18484,28 +19379,19 @@ function OhhwellsBridge() {
18484
19379
  return;
18485
19380
  }
18486
19381
  const existing = editStylesRef.current;
18487
- let initialVh = window.innerHeight;
18488
- if (existing?.base.textContent) {
18489
- const match = existing.base.textContent.match(/\.min-h-screen[^{]*\{[^}]*min-height:\s*(\d+)px/);
18490
- if (match) initialVh = parseInt(match[1], 10);
18491
- }
19382
+ const canvasHeight = "var(--ohw-canvas-h, 852px)";
18492
19383
  const baseCss = `
18493
19384
  html { height: auto !important; }
18494
19385
  body { height: auto !important; min-height: 0 !important; overflow: hidden !important; }
18495
- .min-h-screen, .min-h-svh, .min-h-dvh { min-height: ${initialVh}px !important; }
18496
- .h-screen, .h-svh, .h-dvh { height: ${initialVh}px !important; }
18497
- [style*="100vh"] { min-height: ${initialVh}px !important; height: ${initialVh}px !important; }
18498
- [style*="100svh"] { min-height: ${initialVh}px !important; height: ${initialVh}px !important; }
18499
- [style*="100dvh"] { min-height: ${initialVh}px !important; height: ${initialVh}px !important; }
18500
- /* A section written as min-height: var(--ohw-canvas-h, 100svh) \u2014 the documented way to
18501
- build a full-viewport section that still grows for its own content \u2014 matches the three
18502
- rules above on substring alone, since the fallback text contains "100svh" too. Forcing
18503
- a literal height on top of that turns "at least one screen" into "exactly one screen",
18504
- so content taller than one screen (a long mobile hero, say) overflows a centered flex
18505
- column upward, under whatever sits above it. Only the min-height half belongs to it. */
18506
- [style*="min-height"][style*="100vh"],
18507
- [style*="min-height"][style*="100svh"],
18508
- [style*="min-height"][style*="100dvh"] { height: auto !important; }
19386
+ .min-h-screen, .min-h-svh, .min-h-dvh { min-height: ${canvasHeight} !important; }
19387
+ .h-screen, .h-svh, .h-dvh { height: ${canvasHeight} !important; }
19388
+ /* Literal inline viewport units only \u2014 skip R11 sections that already reference --ohw-canvas-h
19389
+ (their style attribute contains "100svh" only as a var() fallback string). */
19390
+ [style*="100vh"]:not([style*="--ohw-canvas-h"]) { min-height: ${canvasHeight} !important; height: ${canvasHeight} !important; }
19391
+ [style*="100svh"]:not([style*="--ohw-canvas-h"]) { min-height: ${canvasHeight} !important; height: ${canvasHeight} !important; }
19392
+ [style*="100dvh"]:not([style*="--ohw-canvas-h"]) { min-height: ${canvasHeight} !important; height: ${canvasHeight} !important; }
19393
+ /* R11: min-height via --ohw-canvas-h; height must grow with content taller than one screen. */
19394
+ [style*="min-height"][style*="--ohw-canvas-h"] { height: auto !important; }
18509
19395
  /* Emptied text keeps somewhere to click. A label typed down to nothing collapses to a
18510
19396
  couple of pixels, and getting back into it meant hunting for the caret with the mouse.
18511
19397
  Edit mode only \u2014 the published page shows nothing where there is nothing (OHH-736). */
@@ -18652,6 +19538,10 @@ function OhhwellsBridge() {
18652
19538
  outline-offset: 0 !important;
18653
19539
  box-shadow: none !important;
18654
19540
  }
19541
+ /* Open nav dropdown panels paint above AI section selection chrome (2147483000). */
19542
+ [data-ohw-nav-group][data-ohw-nav-force-open] > [data-ohw-nav-children] {
19543
+ z-index: 2147483100 !important;
19544
+ }
18655
19545
  /* Text edit wins over grab/default (must beat [data-ohw-can-drag] *). */
18656
19546
  [data-ohw-editing],
18657
19547
  [data-ohw-editing] *,
@@ -18670,6 +19560,10 @@ function OhhwellsBridge() {
18670
19560
  [data-ohw-editable-state], [data-ohw-editable-state] * { pointer-events: none !important; }
18671
19561
  [data-ohw-editable-state][data-ohw-active-state] [data-ohw-editable] { pointer-events: auto !important; }
18672
19562
  [data-ohw-editable-state][data-ohw-active-state][data-ohw-editable] { pointer-events: auto !important; }
19563
+ /* SVG hit-tests painted glyphs only, so an editable curved-text badge answered clicks
19564
+ on its letter strokes alone (and a spinning one barely at all). In edit mode the
19565
+ whole box takes the click; the handler resolves it to the editable inside. */
19566
+ svg:has([data-ohw-editable]) { pointer-events: bounding-box !important; }
18673
19567
  `;
18674
19568
  if (!existing) {
18675
19569
  const base = document.createElement("style");
@@ -18708,9 +19602,6 @@ function OhhwellsBridge() {
18708
19602
  if (target.closest("[data-ohw-state-toggle]")) return;
18709
19603
  if (target.closest("[data-ohw-max-badge]")) return;
18710
19604
  if (isInsideLinkEditor(target)) return;
18711
- if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
18712
- clearMediaSelectionRef.current();
18713
- }
18714
19605
  if (isInsideFloatingPanel(target)) return;
18715
19606
  if (target.closest("[data-ohw-form-toolbar]")) return;
18716
19607
  if (target.closest(
@@ -18718,6 +19609,9 @@ function OhhwellsBridge() {
18718
19609
  )) {
18719
19610
  return;
18720
19611
  }
19612
+ if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
19613
+ clearMediaSelectionRef.current();
19614
+ }
18721
19615
  {
18722
19616
  const formEl = getFormElement(target);
18723
19617
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -18840,7 +19734,19 @@ function OhhwellsBridge() {
18840
19734
  openLogoSizePanelRef.current(logoEl);
18841
19735
  return;
18842
19736
  }
18843
- const editable = target.closest("[data-ohw-editable]");
19737
+ let editable = target.closest("[data-ohw-editable]");
19738
+ if (!editable && target instanceof SVGElement) {
19739
+ const inner = target.closest("svg")?.querySelectorAll("[data-ohw-editable]");
19740
+ if (inner && inner.length === 1) editable = inner[0];
19741
+ }
19742
+ if (editable && !(editable instanceof HTMLElement)) {
19743
+ e.preventDefault();
19744
+ e.stopPropagation();
19745
+ deselectRef.current();
19746
+ deactivateRef.current();
19747
+ aiSectionApiRef.current?.selectFromElement(editable);
19748
+ return;
19749
+ }
18844
19750
  if (editable) {
18845
19751
  if (editable.dataset.ohwEditable === "link") {
18846
19752
  e.preventDefault();
@@ -18869,14 +19775,6 @@ function OhhwellsBridge() {
18869
19775
  }
18870
19776
  const clickedButton = findClosestButtonLike(target);
18871
19777
  const buttonOnMedia = Boolean(clickedButton && isMediaEditable(editable) && editable.contains(clickedButton));
18872
- console.log("[click-debug]", {
18873
- editableType: editable.dataset.ohwEditable,
18874
- editableTag: editable.tagName,
18875
- targetTag: target.tagName,
18876
- clickedButtonTag: clickedButton?.tagName ?? null,
18877
- buttonOnMedia,
18878
- isMediaEditableEditable: isMediaEditable(editable)
18879
- });
18880
19778
  if (isMediaEditable(editable) && !buttonOnMedia) {
18881
19779
  e.preventDefault();
18882
19780
  e.stopPropagation();
@@ -18903,11 +19801,6 @@ function OhhwellsBridge() {
18903
19801
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
18904
19802
  const hrefCtx = getHrefKeyFromElement(hrefLookupTarget);
18905
19803
  const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
18906
- console.log("[click-debug 2]", {
18907
- hrefLookupTargetTag: hrefLookupTarget.tagName,
18908
- hrefCtx: hrefCtx ? { key: hrefCtx.key } : null,
18909
- navAnchorTag: navAnchor?.tagName ?? null
18910
- });
18911
19804
  if (navAnchor) {
18912
19805
  e.preventDefault();
18913
19806
  e.stopPropagation();
@@ -19077,6 +19970,9 @@ function OhhwellsBridge() {
19077
19970
  setHoveredItemRect(null);
19078
19971
  hoveredNavContainerRef.current = null;
19079
19972
  setHoveredNavContainerRect(null);
19973
+ siblingHintElRef.current = null;
19974
+ setSiblingHintRect(null);
19975
+ setSiblingHintRects([]);
19080
19976
  return;
19081
19977
  }
19082
19978
  {
@@ -19195,7 +20091,6 @@ function OhhwellsBridge() {
19195
20091
  hoveredNavContainerRef.current = null;
19196
20092
  setHoveredNavContainerRect(null);
19197
20093
  hoveredItemElRef.current = editable;
19198
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
19199
20094
  }
19200
20095
  }
19201
20096
  }
@@ -19492,7 +20387,7 @@ function OhhwellsBridge() {
19492
20387
  }
19493
20388
  };
19494
20389
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
19495
- if (linkPopoverOpenRef.current) {
20390
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
19496
20391
  if (hoveredImageRef.current) {
19497
20392
  hoveredImageRef.current = null;
19498
20393
  hoveredImageHasTextOverlapRef.current = false;
@@ -19581,6 +20476,15 @@ function OhhwellsBridge() {
19581
20476
  }
19582
20477
  return;
19583
20478
  }
20479
+ const cardHit = !isDragOver && Array.from(document.querySelectorAll(".card, [data-ohw-card]")).some((card) => {
20480
+ if (card.contains(imgEl)) return false;
20481
+ const r2 = card.getBoundingClientRect();
20482
+ return x2 >= r2.left && x2 <= r2.right && y2 >= r2.top && y2 <= r2.bottom;
20483
+ });
20484
+ if (cardHit) {
20485
+ dismissImageHover();
20486
+ return;
20487
+ }
19584
20488
  const topEl = document.elementFromPoint(x2, y2);
19585
20489
  if (topEl?.closest('[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"], [data-ohw-more-menu], [data-slot="dropdown-menu-content"], [data-slot="dropdown-menu-item"]')) {
19586
20490
  if (hoveredImageRef.current) {
@@ -19857,8 +20761,7 @@ function OhhwellsBridge() {
19857
20761
  };
19858
20762
  const handleMouseMove = (e) => {
19859
20763
  const { clientX, clientY } = e;
19860
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
19861
- if (isOverEditorChrome(clientX, clientY)) {
20764
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
19862
20765
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
19863
20766
  formHoverElRef.current = null;
19864
20767
  setFormHoverRect(null);
@@ -19866,6 +20769,12 @@ function OhhwellsBridge() {
19866
20769
  setHoveredItemRect(null);
19867
20770
  hoveredNavContainerRef.current = null;
19868
20771
  setHoveredNavContainerRect(null);
20772
+ siblingHintElRef.current = null;
20773
+ setSiblingHintRect(null);
20774
+ setSiblingHintRects([]);
20775
+ dismissImageHover();
20776
+ clearImageHover();
20777
+ setSectionGap(null);
19869
20778
  return;
19870
20779
  }
19871
20780
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -19877,7 +20786,11 @@ function OhhwellsBridge() {
19877
20786
  if (e.data?.type !== "ow:pointer-sync") return;
19878
20787
  const { clientX, clientY } = e.data;
19879
20788
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
19880
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
20789
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
20790
+ dismissImageHover();
20791
+ clearImageHover();
20792
+ return;
20793
+ }
19881
20794
  if (probeSocialsRowAt(clientX, clientY)) return;
19882
20795
  probeSectionGapAt(clientX, clientY);
19883
20796
  probeImageAt(clientX, clientY);
@@ -19991,7 +20904,11 @@ function OhhwellsBridge() {
19991
20904
  const slotFromItem = hrefKey ? findSocialByHrefKey(hrefKey)?.querySelector('[data-ohw-editable="icon"]') : null;
19992
20905
  const requestedIsIconSlot = Boolean(requestedIconKey) && Boolean(document.querySelector(`[data-ohw-key="${requestedIconKey}"][data-ohw-editable="icon"]`));
19993
20906
  const requestedIsFree = Boolean(requestedIconKey) && !document.querySelector(`[data-ohw-key="${requestedIconKey}"]`);
19994
- const iconKey = requestedIsIconSlot ? requestedIconKey : slotFromItem?.dataset.ohwKey ?? (requestedIsFree ? requestedIconKey : void 0);
20907
+ let iconKey = requestedIsIconSlot ? requestedIconKey : slotFromItem?.dataset.ohwKey ?? (requestedIsFree ? requestedIconKey : void 0);
20908
+ const item = hrefKey ? findSocialByHrefKey(hrefKey) : null;
20909
+ if (item && typeof iconMarkup === "string" && iconMarkup) {
20910
+ iconKey = ensureIconSlot(item) ?? iconKey;
20911
+ }
19995
20912
  if (iconKey && typeof iconMarkup === "string" && iconMarkup) {
19996
20913
  document.querySelectorAll(`[data-ohw-key="${iconKey}"][data-ohw-editable="icon"]`).forEach((el) => {
19997
20914
  applyIconMarkup(el, iconMarkup);
@@ -20001,7 +20918,7 @@ function OhhwellsBridge() {
20001
20918
  }
20002
20919
  if (iconKey && platformId) nodes.push({ key: socialPlatformKey(iconKey), text: platformId });
20003
20920
  if (iconKey && label) {
20004
- const labelKey = socialLabelKey(iconKey);
20921
+ const labelKey = (item ? socialLabelElement(item)?.getAttribute("data-ohw-key") : null) ?? socialLabelKey(iconKey);
20005
20922
  document.querySelectorAll(`[data-ohw-key="${labelKey}"]`).forEach((el) => {
20006
20923
  if (keepLabel && el.textContent?.trim()) return;
20007
20924
  el.textContent = label;
@@ -20156,6 +21073,44 @@ function OhhwellsBridge() {
20156
21073
  if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
20157
21074
  }, 400));
20158
21075
  };
21076
+ const reapCommittedAiSections = (excludeIds) => {
21077
+ const aiState = parseAiSectionsState(aiSectionsRef.current);
21078
+ if (aiState.sections.length === 0) return [];
21079
+ let orderEntries = [];
21080
+ try {
21081
+ const parsed = JSON.parse(editContentRef.current[SECTION_ORDER_KEY] ?? "[]");
21082
+ if (Array.isArray(parsed)) orderEntries = parsed;
21083
+ } catch {
21084
+ return [];
21085
+ }
21086
+ const removedIds = orderEntries.filter((entry) => entry && typeof entry.instanceId === "string" && entry.removed).map((entry) => entry.instanceId);
21087
+ if (removedIds.length === 0) return [];
21088
+ const result = reapRemovedAiSections(aiState, parseStyleStore(stylesRef.current), removedIds, excludeIds);
21089
+ if (!result.changed) return [];
21090
+ const nodes = [];
21091
+ aiSectionsRef.current = serializeAiSectionsState(result.state);
21092
+ nodes.push({ key: AI_SECTIONS_KEY, text: aiSectionsRef.current });
21093
+ const reaped = new Set(result.reapedIds);
21094
+ const nextOrderJson = JSON.stringify(orderEntries.filter((entry) => !reaped.has(entry.instanceId)));
21095
+ editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: nextOrderJson };
21096
+ setAiSectionOrder(nextOrderJson, window.location.pathname);
21097
+ nodes.push({ key: SECTION_ORDER_KEY, text: nextOrderJson });
21098
+ if (result.store) {
21099
+ stylesRef.current = JSON.stringify(result.store);
21100
+ nodes.push({ key: STYLE_STORE_KEY, text: stylesRef.current });
21101
+ }
21102
+ const nextContent = { ...editContentRef.current };
21103
+ for (const key of Object.keys(nextContent)) {
21104
+ if (key.startsWith(AI_SLOT_KEY_PREFIX) && result.slotPrefixes.some((prefix) => key.startsWith(prefix)) && nextContent[key] !== "") {
21105
+ nextContent[key] = "";
21106
+ nodes.push({ key, text: "" });
21107
+ }
21108
+ }
21109
+ editContentRef.current = nextContent;
21110
+ applyAiSectionsToDom(result.state);
21111
+ applyStylesToDom(parseStyleStore(stylesRef.current));
21112
+ return nodes;
21113
+ };
20159
21114
  const handleHydrate = (e) => {
20160
21115
  if (e.data?.type !== "ow:hydrate") return;
20161
21116
  const content = e.data.content;
@@ -20174,6 +21129,7 @@ function OhhwellsBridge() {
20174
21129
  }
20175
21130
  if (typeof content[STYLE_STORE_KEY] === "string") {
20176
21131
  stylesRef.current = content[STYLE_STORE_KEY];
21132
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
20177
21133
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
20178
21134
  }
20179
21135
  applyBrandChrome(content);
@@ -20185,11 +21141,11 @@ function OhhwellsBridge() {
20185
21141
  continue;
20186
21142
  }
20187
21143
  if (key === AI_SECTIONS_KEY) continue;
21144
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
21145
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
20188
21146
  if (key === BRAND_KIT_KEY) continue;
20189
21147
  if (key === STYLE_STORE_KEY) continue;
20190
21148
  if (BRAND_CHROME_KEYS.has(key)) continue;
20191
- if (key === LOGO_PLACEHOLDER_KEY) continue;
20192
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
20193
21149
  if (applyVideoSettingNode(key, val)) continue;
20194
21150
  if (applyCarouselNode(key, val)) continue;
20195
21151
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -20203,6 +21159,8 @@ function OhhwellsBridge() {
20203
21159
  if (video && video.src !== val) applyVideoSrc(video, val);
20204
21160
  } else if (el.dataset.ohwEditable === "link") {
20205
21161
  applyLinkHref(el, val);
21162
+ } else if (el.dataset.ohwEditable === "map") {
21163
+ applyMapQuery(el, val);
20206
21164
  } else if (el.dataset.ohwEditable === "icon") {
20207
21165
  applyIconMarkup(el, val);
20208
21166
  } else if (isIconMarkupValue(val)) {
@@ -20223,9 +21181,20 @@ function OhhwellsBridge() {
20223
21181
  reconcileNavbarItemsFromContent(editContentRef.current);
20224
21182
  reconcileFooterOrderFromContent(editContentRef.current);
20225
21183
  syncNavigationDragCursorAttrs();
21184
+ if (typeof content[STYLE_STORE_KEY] === "string") {
21185
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
21186
+ }
20226
21187
  enforceLinkHrefs();
21188
+ const hydrateReapExclude = /* @__PURE__ */ new Set();
21189
+ const hydratePendingUndo = pendingDeleteUndoRef.current;
21190
+ if (hydratePendingUndo?.sectionInstanceId) hydrateReapExclude.add(hydratePendingUndo.sectionInstanceId);
21191
+ const reapNodes = reapCommittedAiSections(hydrateReapExclude);
21192
+ if (reapNodes.length > 0) postToParentRef.current({ type: "ow:change", nodes: reapNodes });
20227
21193
  const hydratedHeight = document.body.scrollHeight;
20228
21194
  if (hydratedHeight > 50) postToParentRef.current({ type: "ow:height", height: hydratedHeight });
21195
+ if (parseAiSectionsState(aiSectionsRef.current).sections.length > 0) {
21196
+ postAiSectionsChanged();
21197
+ }
20229
21198
  postToParentRef.current({ type: "ow:hydrate-done" });
20230
21199
  };
20231
21200
  const handleUpdateLogoIdentity = (e) => {
@@ -20397,10 +21366,26 @@ function OhhwellsBridge() {
20397
21366
  const handleGetBrand = (e) => {
20398
21367
  if (e.data?.type !== "ow:get-brand") return;
20399
21368
  const template = deriveTemplateBrand();
20400
- const value = brandKitRef.current || (template ? JSON.stringify(template) : "");
21369
+ const fallback = template ?? (() => {
21370
+ const fonts = deriveTemplateFonts();
21371
+ return fonts ? { palette: AI_DEFAULT_BRAND.palette, fonts } : null;
21372
+ })();
21373
+ const value = brandKitRef.current || (fallback ? JSON.stringify(fallback) : "");
20401
21374
  postToParentRef.current({ type: "ow:brand-value", value });
20402
21375
  };
20403
21376
  window.addEventListener("message", handleGetBrand);
21377
+ const handleGetTemplateFonts = (e) => {
21378
+ if (e.data?.type !== "ow:get-template-fonts") return;
21379
+ const fonts = deriveTemplateFonts();
21380
+ postToParentRef.current({ type: "ow:template-fonts-value", value: fonts ? JSON.stringify(fonts) : "" });
21381
+ };
21382
+ window.addEventListener("message", handleGetTemplateFonts);
21383
+ const handleGetTemplateBrand = (e) => {
21384
+ if (e.data?.type !== "ow:get-template-brand") return;
21385
+ const brand = deriveTemplateBrand();
21386
+ postToParentRef.current({ type: "ow:template-brand-value", value: brand ? JSON.stringify(brand) : "" });
21387
+ };
21388
+ window.addEventListener("message", handleGetTemplateBrand);
20404
21389
  const handleMoveSection = (e) => {
20405
21390
  if (e.data?.type !== "ow:move-section") return;
20406
21391
  const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
@@ -20408,8 +21393,11 @@ function OhhwellsBridge() {
20408
21393
  if (!instanceId || !direction) return;
20409
21394
  const entries = moveSectionInstance(instanceId, direction, window.location.pathname);
20410
21395
  if (!entries) return;
20411
- const orderJson = JSON.stringify(entries);
21396
+ const orderJson = JSON.stringify(
21397
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
21398
+ );
20412
21399
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
21400
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
20413
21401
  setAiSectionOrder(orderJson, window.location.pathname);
20414
21402
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20415
21403
  window.dispatchEvent(new Event("resize"));
@@ -20455,8 +21443,11 @@ function OhhwellsBridge() {
20455
21443
  const currentEntries = getPageSectionOrderEntries(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname);
20456
21444
  const entries = deleteSectionInstance(instanceId, window.location.pathname, currentEntries);
20457
21445
  if (!entries) return;
20458
- const orderJson = JSON.stringify(entries);
21446
+ const orderJson = JSON.stringify(
21447
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
21448
+ );
20459
21449
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
21450
+ setAiSectionOrder(orderJson, window.location.pathname);
20460
21451
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20461
21452
  aiSectionApiRef.current?.clear();
20462
21453
  window.dispatchEvent(new Event("resize"));
@@ -20465,6 +21456,7 @@ function OhhwellsBridge() {
20465
21456
  const actionId = newInstanceId();
20466
21457
  pendingDeleteUndoRef.current = {
20467
21458
  actionId,
21459
+ sectionInstanceId: instanceId,
20468
21460
  restore: () => {
20469
21461
  const restoredEntries = getPageSectionOrderEntries(
20470
21462
  editContentRef.current[SECTION_ORDER_KEY],
@@ -20472,8 +21464,11 @@ function OhhwellsBridge() {
20472
21464
  );
20473
21465
  const restored = restoreSectionInstance(instanceId, window.location.pathname, restoredEntries);
20474
21466
  if (!restored) return;
20475
- const restoredJson = JSON.stringify(restored);
21467
+ const restoredJson = JSON.stringify(
21468
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, restored)
21469
+ );
20476
21470
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: restoredJson };
21471
+ setAiSectionOrder(restoredJson, window.location.pathname);
20477
21472
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: restoredJson }] });
20478
21473
  window.dispatchEvent(new Event("resize"));
20479
21474
  const restoreHeight = document.body.scrollHeight;
@@ -20499,7 +21494,9 @@ function OhhwellsBridge() {
20499
21494
  const result = duplicateSectionInstance(instanceId, newId, window.location.pathname, currentEntries);
20500
21495
  if (!result) return;
20501
21496
  const { entries, keyRekeys } = result;
20502
- const orderJson = JSON.stringify(entries);
21497
+ const orderJson = JSON.stringify(
21498
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
21499
+ );
20503
21500
  const nodes = [{ key: SECTION_ORDER_KEY, text: orderJson }];
20504
21501
  for (const { from, to } of keyRekeys) {
20505
21502
  const inherited = editContentRef.current[from];
@@ -20509,6 +21506,7 @@ function OhhwellsBridge() {
20509
21506
  ...editContentRef.current,
20510
21507
  ...Object.fromEntries(nodes.map(({ key, text }) => [key, text]))
20511
21508
  };
21509
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
20512
21510
  setAiSectionOrder(orderJson, window.location.pathname);
20513
21511
  postToParentRef.current({ type: "ow:change", nodes });
20514
21512
  window.dispatchEvent(new Event("resize"));
@@ -20527,6 +21525,12 @@ function OhhwellsBridge() {
20527
21525
  closeLinkPopoverRef.current();
20528
21526
  return;
20529
21527
  }
21528
+ if (floatingPanelOpenRef.current) {
21529
+ setFloatingPanelRef.current(null);
21530
+ deselectRef.current();
21531
+ deactivateRef.current();
21532
+ return;
21533
+ }
20530
21534
  deselectRef.current();
20531
21535
  deactivateRef.current();
20532
21536
  clearMediaSelectionRef.current();
@@ -20759,7 +21763,12 @@ function OhhwellsBridge() {
20759
21763
  }
20760
21764
  if (navDragRef.current) {
20761
21765
  const session = navDragRef.current;
20762
- const slot = hitTestNavDropSlot(session.lastClientX, session.lastClientY, session.hrefKey);
21766
+ const slot = hitTestNavDropSlot(
21767
+ session.lastClientX,
21768
+ session.lastClientY,
21769
+ session.hrefKey,
21770
+ session.draggedEl
21771
+ );
20763
21772
  refreshNavDragVisualsRef.current(session, slot, session.lastClientX, session.lastClientY);
20764
21773
  }
20765
21774
  if (hoveredImageRef.current) {
@@ -20772,6 +21781,10 @@ function OhhwellsBridge() {
20772
21781
  };
20773
21782
  const handleSave = (e) => {
20774
21783
  if (e.data?.type !== "ow:save") return;
21784
+ const pendingUndo = pendingDeleteUndoRef.current;
21785
+ const reapExclude = /* @__PURE__ */ new Set();
21786
+ if (pendingUndo?.sectionInstanceId) reapExclude.add(pendingUndo.sectionInstanceId);
21787
+ const reapNodes = reapCommittedAiSections(reapExclude);
20775
21788
  const nodes = collectEditableNodes(editContentRef.current);
20776
21789
  const tracker = document.querySelector("[data-ohw-sections-tracker]");
20777
21790
  if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
@@ -20791,6 +21804,11 @@ function OhhwellsBridge() {
20791
21804
  const text = specs.length ? JSON.stringify(specs) : editContentRef.current[fieldsKey(formKey)];
20792
21805
  if (text) nodes.push({ key: fieldsKey(formKey), type: "text", text });
20793
21806
  });
21807
+ for (const reapNode of reapNodes) {
21808
+ if (reapNode.key.startsWith(AI_SLOT_KEY_PREFIX) && !nodes.some((n) => n.key === reapNode.key)) {
21809
+ nodes.push({ key: reapNode.key, type: "text", text: reapNode.text });
21810
+ }
21811
+ }
20794
21812
  postToParentRef.current({ type: "ow:save-result", nodes });
20795
21813
  };
20796
21814
  const handleInsertSection = (e) => {
@@ -20801,8 +21819,12 @@ function OhhwellsBridge() {
20801
21819
  if (inserted) {
20802
21820
  const tracker = getSectionsTracker();
20803
21821
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
20804
- const h = document.body.scrollHeight;
20805
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
21822
+ const reportHeight = () => {
21823
+ const h = document.body.scrollHeight;
21824
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
21825
+ };
21826
+ reportHeight();
21827
+ setTimeout(reportHeight, 500);
20806
21828
  }
20807
21829
  };
20808
21830
  const handleSwitchSchedule = (e) => {
@@ -20952,18 +21974,21 @@ function OhhwellsBridge() {
20952
21974
  }
20953
21975
  return null;
20954
21976
  };
21977
+ const isPointInRect = (el, clientX, clientY) => {
21978
+ const r2 = el.getBoundingClientRect();
21979
+ return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
21980
+ };
20955
21981
  const isPointOverEditable = (scope, clientX, clientY) => {
20956
21982
  const editables = scope.querySelectorAll("[data-ohw-editable], [data-ohw-href-key]");
20957
21983
  for (const el of editables) {
20958
- const r2 = el.getBoundingClientRect();
20959
- if (clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom) return true;
21984
+ if (isPointInRect(el, clientX, clientY)) return true;
20960
21985
  }
20961
21986
  return false;
20962
21987
  };
20963
21988
  const handleCarouselHover = (e) => {
20964
21989
  const container = findCarouselAtPoint(e.clientX, e.clientY);
20965
21990
  const scope = container?.closest("[data-ohw-section]") ?? container?.parentElement ?? null;
20966
- if (!container || !scope || isPointOverEditable(scope, e.clientX, e.clientY)) {
21991
+ if (!container || !scope || isPointOverEditable(scope, e.clientX, e.clientY) || isPointOverNavigation(e.clientX, e.clientY) || isPointOverBridgeChrome(e.clientX, e.clientY)) {
20967
21992
  setCarouselHover((prev) => prev ? null : prev);
20968
21993
  return;
20969
21994
  }
@@ -21201,15 +22226,17 @@ function OhhwellsBridge() {
21201
22226
  window.removeEventListener("message", handleAiSetBrand);
21202
22227
  window.removeEventListener("message", handleAiSetStyles);
21203
22228
  window.removeEventListener("message", handleGetBrand);
22229
+ window.removeEventListener("message", handleGetTemplateFonts);
22230
+ window.removeEventListener("message", handleGetTemplateBrand);
21204
22231
  window.removeEventListener("message", handleMoveSection);
21205
22232
  window.removeEventListener("message", handlePanelDragging);
21206
22233
  window.removeEventListener("message", handleDeleteSection);
21207
22234
  window.removeEventListener("message", handleDuplicateSection);
21208
22235
  window.removeEventListener("message", handleDeactivate);
21209
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
21210
22236
  window.removeEventListener("message", handleToastAction);
21211
22237
  window.removeEventListener("message", handleFormCount);
21212
22238
  window.removeEventListener("message", handleUiEscape);
22239
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
21213
22240
  autoSaveTimers.current.forEach(clearTimeout);
21214
22241
  autoSaveTimers.current.clear();
21215
22242
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -21412,7 +22439,7 @@ function OhhwellsBridge() {
21412
22439
  postToParent2({
21413
22440
  type: "ow:ready",
21414
22441
  version: "1",
21415
- bridgeVersion: "0.1.91",
22442
+ bridgeVersion: "0.1.93",
21416
22443
  path: pathname,
21417
22444
  nodes: collectEditableNodes(editContentRef.current),
21418
22445
  sections
@@ -22028,7 +23055,7 @@ function OhhwellsBridge() {
22028
23055
  "span",
22029
23056
  {
22030
23057
  "data-ohw-form-count": "",
22031
- className: "ml-0.5 inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-primary px-1.5 text-[11px] font-semibold text-primary-foreground",
23058
+ className: "ml-0.5 inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-bridge-primary px-1.5 text-[11px] font-semibold text-primary-foreground",
22032
23059
  children: formPickCount
22033
23060
  }
22034
23061
  )
@@ -22265,11 +23292,11 @@ function OhhwellsBridge() {
22265
23292
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
22266
23293
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
22267
23294
  children: [
22268
- /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
23295
+ /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } }),
22269
23296
  /* @__PURE__ */ jsx33(
22270
23297
  Badge,
22271
23298
  {
22272
- className: "px-8 py-1 bg-primary hover:bg-primary text-primary-foreground text-xs font-medium shrink-0 rounded-full cursor-pointer pointer-events-auto",
23299
+ className: "px-8 py-1 bg-bridge-primary hover:bg-bridge-primary text-primary-foreground text-xs font-medium shrink-0 rounded-full cursor-pointer pointer-events-auto",
22273
23300
  onClick: () => {
22274
23301
  window.parent.postMessage(
22275
23302
  {
@@ -22283,7 +23310,7 @@ function OhhwellsBridge() {
22283
23310
  children: "Add Section"
22284
23311
  }
22285
23312
  ),
22286
- /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-primary", style: { height: 3 } })
23313
+ /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } })
22287
23314
  ]
22288
23315
  }
22289
23316
  ),
@@ -22331,6 +23358,59 @@ function OhhwellsBridge() {
22331
23358
  ) : null
22332
23359
  ] });
22333
23360
  }
23361
+
23362
+ // src/ui/EmptySection.tsx
23363
+ import Link3 from "next/link";
23364
+ import { Fragment as Fragment9, jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
23365
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
23366
+ return /* @__PURE__ */ jsxs21(Fragment9, { children: [
23367
+ /* @__PURE__ */ jsx34(
23368
+ "p",
23369
+ {
23370
+ style: {
23371
+ fontFamily: "var(--brand-font-body)",
23372
+ fontSize: "0.75rem",
23373
+ fontWeight: 500,
23374
+ letterSpacing: "0.15em",
23375
+ textTransform: "uppercase",
23376
+ color: "var(--brand-accent)",
23377
+ marginBottom: "1.5rem"
23378
+ },
23379
+ children: /* @__PURE__ */ jsx34(Link3, { href: homeHref, style: { color: "inherit" }, children: /* @__PURE__ */ jsx34("span", { ...eyebrowKey ? { "data-ohw-editable": "text", "data-ohw-key": eyebrowKey } : {}, children: "Home" }) })
23380
+ }
23381
+ ),
23382
+ /* @__PURE__ */ jsx34(
23383
+ "h1",
23384
+ {
23385
+ style: {
23386
+ fontFamily: "var(--brand-font-heading)",
23387
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
23388
+ lineHeight: 1.1,
23389
+ letterSpacing: "-0.025em",
23390
+ color: "var(--brand-text)",
23391
+ marginBottom: "1rem"
23392
+ },
23393
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
23394
+ children: title
23395
+ }
23396
+ ),
23397
+ /* @__PURE__ */ jsx34(
23398
+ "p",
23399
+ {
23400
+ style: {
23401
+ fontFamily: "var(--brand-font-body)",
23402
+ fontSize: "1rem",
23403
+ lineHeight: 1.7,
23404
+ fontWeight: 300,
23405
+ color: "var(--brand-text-muted)",
23406
+ maxWidth: "340px"
23407
+ },
23408
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
23409
+ children: "This page doesn't have any content yet."
23410
+ }
23411
+ )
23412
+ ] });
23413
+ }
22334
23414
  export {
22335
23415
  AI_DEFAULT_BRAND,
22336
23416
  AI_TREE_SCHEMA_VERSIONS,
@@ -22347,6 +23427,7 @@ export {
22347
23427
  DropdownMenuItem,
22348
23428
  DropdownMenuSeparator,
22349
23429
  DropdownMenuTrigger,
23430
+ EmptySection,
22350
23431
  ItemActionToolbar,
22351
23432
  ItemInteractionLayer,
22352
23433
  LinkEditorPanel,