@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.cjs CHANGED
@@ -46,6 +46,7 @@ __export(index_exports, {
46
46
  DropdownMenuItem: () => DropdownMenuItem,
47
47
  DropdownMenuSeparator: () => DropdownMenuSeparator,
48
48
  DropdownMenuTrigger: () => DropdownMenuTrigger,
49
+ EmptySection: () => EmptySection,
49
50
  ItemActionToolbar: () => ItemActionToolbar,
50
51
  ItemInteractionLayer: () => ItemInteractionLayer,
51
52
  LinkEditorPanel: () => LinkEditorPanel,
@@ -143,6 +144,9 @@ function isRenderableTree(value) {
143
144
  // src/lib/ai-sections-store.ts
144
145
  var AI_SECTIONS_KEY = "__ohw_ai_sections";
145
146
  var AI_SLOT_KEY_PREFIX = "ai.";
147
+ function aiSlotKeyPrefixFor(sectionId) {
148
+ return `${AI_SLOT_KEY_PREFIX}${sectionId}.`;
149
+ }
146
150
  var EMPTY_AI_SECTIONS = { v: 1, sections: [] };
147
151
  function parseAiSectionsState(raw) {
148
152
  if (!raw) return EMPTY_AI_SECTIONS;
@@ -254,6 +258,33 @@ function deleteSectionFromState(state, sectionId) {
254
258
  if (removed.includes(sectionId)) return state;
255
259
  return { ...state, v: 1, sections: state.sections, removed: [...removed, sectionId] };
256
260
  }
261
+ function reapRemovedAiSections(state, store, removedIds, excludeIds = /* @__PURE__ */ new Set()) {
262
+ const generated = new Set(state.sections.map((entry) => entry.id));
263
+ const reapedIds = [...new Set(removedIds)].filter((id) => generated.has(id) && !excludeIds.has(id));
264
+ if (reapedIds.length === 0) {
265
+ return { state, store, reapedIds: [], slotPrefixes: [], changed: false };
266
+ }
267
+ const reaped = new Set(reapedIds);
268
+ const slotPrefixes = reapedIds.map(aiSlotKeyPrefixFor);
269
+ const nextState = {
270
+ ...state,
271
+ v: 1,
272
+ sections: state.sections.filter((entry) => !reaped.has(entry.id))
273
+ };
274
+ let nextStore = store;
275
+ if (store) {
276
+ const sections = {};
277
+ for (const [key, override] of Object.entries(store.sections)) {
278
+ if (!reaped.has(key)) sections[key] = override;
279
+ }
280
+ const nodes = {};
281
+ for (const [key, override] of Object.entries(store.nodes)) {
282
+ if (!slotPrefixes.some((prefix) => key.startsWith(prefix))) nodes[key] = override;
283
+ }
284
+ nextStore = { v: 1, sections, nodes };
285
+ }
286
+ return { state: nextState, store: nextStore, reapedIds, slotPrefixes, changed: true };
287
+ }
257
288
 
258
289
  // src/lib/brand-chrome.ts
259
290
  var BRAND_NAME_KEY = "__ohw_brand_name";
@@ -322,16 +353,17 @@ var LEGACY_BRAND_VAR_NAMES = [
322
353
  "text-muted",
323
354
  "surface",
324
355
  "border",
325
- "on-primary",
326
- "navbar-background"
356
+ "on-primary"
327
357
  ].map((role) => `--brand-${role}`);
328
358
  var FONT_VARS = {
329
359
  heading: ["--font-heading", "--font-display", "--brand-font-heading"],
330
360
  body: ["--font-body", "--brand-font-body"]
331
361
  };
332
362
  var BRAND_FONT_LINK_ID = "ohw-brand-fonts";
333
- function brandColorVars(kit) {
334
- const { dark, primary, accent, light } = kit.palette;
363
+ var CUSTOM_FONT_STYLE_ID = "ohw-brand-custom-fonts";
364
+ var CUSTOM_FONT_FORMATS = /* @__PURE__ */ new Set(["woff2", "woff", "truetype", "opentype"]);
365
+ function brandColorVars(palette) {
366
+ const { dark, primary, accent, light } = palette;
335
367
  const mix = (a, aPct, b) => `color-mix(in srgb, ${a} ${aPct}%, ${b})`;
336
368
  const surface = mix(light, 95, dark);
337
369
  const border = mix(light, 85, dark);
@@ -354,22 +386,42 @@ function brandColorVars(kit) {
354
386
  "--brand-border": border,
355
387
  // Buttons/bands painted in the primary colour assume it's dark/saturated enough to need
356
388
  // light text on top — the same assumption LOGO_IMAGE's light-on-dark navbar mark makes.
357
- "--brand-on-primary": light,
358
- "--brand-navbar-background": dark
389
+ "--brand-on-primary": light
359
390
  };
360
391
  }
392
+ function parseCustomFontWeight(raw) {
393
+ if (typeof raw !== "object" || raw === null) return null;
394
+ const w = raw;
395
+ if (typeof w.weight !== "number" || !Number.isFinite(w.weight)) return null;
396
+ if (typeof w.url !== "string" || !w.url) return null;
397
+ if (typeof w.format !== "string" || !CUSTOM_FONT_FORMATS.has(w.format)) return null;
398
+ const weightEnd = typeof w.weightEnd === "number" && Number.isFinite(w.weightEnd) && w.weightEnd > w.weight ? w.weightEnd : void 0;
399
+ return { weight: w.weight, ...weightEnd !== void 0 ? { weightEnd } : {}, url: w.url, format: w.format };
400
+ }
401
+ function parseCustomFont(raw) {
402
+ if (typeof raw !== "object" || raw === null) return null;
403
+ const f = raw;
404
+ if (typeof f.family !== "string" || !f.family) return null;
405
+ if (typeof f.label !== "string" || !f.label) return null;
406
+ if (!Array.isArray(f.weights)) return null;
407
+ const weights = f.weights.map(parseCustomFontWeight).filter((w) => w !== null);
408
+ if (weights.length === 0) return null;
409
+ return { family: f.family, label: f.label, weights };
410
+ }
361
411
  function parseBrandKit(raw) {
362
412
  if (!raw) return null;
363
413
  try {
364
414
  const parsed = JSON.parse(raw);
365
415
  const p = parsed?.palette;
366
416
  const f = parsed?.fonts;
367
- 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") {
368
- return null;
369
- }
417
+ 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;
418
+ const fonts = f && typeof f.heading === "string" && typeof f.body === "string" ? { heading: f.heading, body: f.body } : void 0;
419
+ const customFonts = Array.isArray(parsed?.customFonts) ? parsed.customFonts.map(parseCustomFont).filter((c) => c !== null) : void 0;
420
+ if (!palette && !fonts && !customFonts) return null;
370
421
  return {
371
- palette: { dark: p.dark, primary: p.primary, accent: p.accent, light: p.light },
372
- fonts: { heading: f.heading, body: f.body }
422
+ ...palette ? { palette } : {},
423
+ ...fonts ? { fonts } : {},
424
+ ...customFonts ? { customFonts } : {}
373
425
  };
374
426
  } catch {
375
427
  return null;
@@ -379,11 +431,16 @@ function familyOf(stack) {
379
431
  const first = stack.split(",")[0]?.trim() ?? "";
380
432
  return first.replace(/^['"]|['"]$/g, "");
381
433
  }
434
+ function quoteFamily(family) {
435
+ return `'${family.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
436
+ }
437
+ var WEIGHT_SCALE = [100, 200, 300, 400, 500, 600, 700, 800, 900];
382
438
  function loadBrandFonts(families) {
383
439
  const unique = [...new Set(families.map((f) => f.trim()).filter(Boolean))];
384
440
  if (unique.length === 0) return;
385
- const spec = unique.map((f) => `${f.replace(/ /g, "+")}:400,500,600,700`).join("|");
386
- const href = `https://fonts.googleapis.com/css?family=${spec}&display=swap`;
441
+ const weights = WEIGHT_SCALE.join(";");
442
+ const spec = unique.map((f) => `family=${f.replace(/ /g, "+")}:wght@${weights}`).join("&");
443
+ const href = `https://fonts.googleapis.com/css2?${spec}&display=swap`;
387
444
  let link = document.getElementById(BRAND_FONT_LINK_ID);
388
445
  if (!link) {
389
446
  link = document.createElement("link");
@@ -393,18 +450,54 @@ function loadBrandFonts(families) {
393
450
  }
394
451
  if (link.href !== href) link.href = href;
395
452
  }
453
+ function escapeCssString(value) {
454
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
455
+ }
456
+ function loadCustomFonts(fonts) {
457
+ const list = fonts ?? [];
458
+ let style = document.getElementById(CUSTOM_FONT_STYLE_ID);
459
+ if (list.length === 0) {
460
+ style?.remove();
461
+ return;
462
+ }
463
+ if (!style) {
464
+ style = document.createElement("style");
465
+ style.id = CUSTOM_FONT_STYLE_ID;
466
+ document.head.appendChild(style);
467
+ }
468
+ style.textContent = list.flatMap(
469
+ (font) => font.weights.map((w) => {
470
+ const weightDescriptor = w.weightEnd ? `${w.weight} ${w.weightEnd}` : `${w.weight}`;
471
+ return `@font-face { font-family: '${escapeCssString(font.family)}'; src: url('${escapeCssString(w.url)}') format('${w.format}'); font-weight: ${weightDescriptor}; font-display: swap; }`;
472
+ })
473
+ ).join("\n");
474
+ }
396
475
  function applyBrandToDom(kit) {
397
476
  const root = document.documentElement;
398
477
  if (!kit) {
399
478
  for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES]) root.style.removeProperty(name);
400
479
  for (const name of [...FONT_VARS.heading, ...FONT_VARS.body]) root.style.removeProperty(name);
401
480
  document.getElementById(BRAND_FONT_LINK_ID)?.remove();
481
+ document.getElementById(CUSTOM_FONT_STYLE_ID)?.remove();
402
482
  return;
403
483
  }
404
- for (const [name, value] of Object.entries(brandColorVars(kit))) root.style.setProperty(name, value);
405
- for (const name of FONT_VARS.heading) root.style.setProperty(name, kit.fonts.heading);
406
- for (const name of FONT_VARS.body) root.style.setProperty(name, kit.fonts.body);
407
- loadBrandFonts([familyOf(kit.fonts.heading), familyOf(kit.fonts.body)]);
484
+ if (kit.palette) {
485
+ for (const [name, value] of Object.entries(brandColorVars(kit.palette))) root.style.setProperty(name, value);
486
+ }
487
+ if (kit.customFonts) {
488
+ loadCustomFonts(kit.customFonts);
489
+ }
490
+ if (kit.fonts) {
491
+ const heading = quoteFamily(kit.fonts.heading);
492
+ const body = quoteFamily(kit.fonts.body);
493
+ for (const name of FONT_VARS.heading) root.style.setProperty(name, heading);
494
+ for (const name of FONT_VARS.body) root.style.setProperty(name, body);
495
+ const customFamilies = new Set((kit.customFonts ?? []).map((f) => f.family));
496
+ const googleFamilies = [familyOf(kit.fonts.heading), familyOf(kit.fonts.body)].filter(
497
+ (f) => !customFamilies.has(f)
498
+ );
499
+ loadBrandFonts(googleFamilies);
500
+ }
408
501
  }
409
502
 
410
503
  // src/lib/section-styles.ts
@@ -461,6 +554,12 @@ function styleSheetCss() {
461
554
  `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px}px !important; padding-bottom: ${px}px !important; }`
462
555
  );
463
556
  }
557
+ rules.push(
558
+ `[data-ohw-style-corners="sharp"] :is(.card, [data-ohw-card], img, picture, figure, [data-ohw-editable="bg-image"]) { border-radius: 0 !important; }`,
559
+ `[data-ohw-style-corners="sharp"] :has(> img) { border-radius: 0 !important; }`,
560
+ `[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; }`,
561
+ `[data-ohw-style-corners="rounded"] :has(> img) { border-radius: max(0.75rem, var(--radius, 0px)) !important; }`
562
+ );
464
563
  for (const align of ["left", "center", "right"]) {
465
564
  rules.push(`[data-ohw-style-align="${align}"] { text-align: ${align} !important; }`);
466
565
  }
@@ -491,6 +590,7 @@ var SECTION_ATTRS = {
491
590
  headlineScale: "data-ohw-style-headline",
492
591
  imageAspect: "data-ohw-style-aspect",
493
592
  spacing: "data-ohw-style-spacing",
593
+ cornerStyle: "data-ohw-style-corners",
494
594
  align: "data-ohw-style-align"
495
595
  };
496
596
  var NODE_WROTE_ATTR = "data-ohw-style-node";
@@ -553,14 +653,39 @@ function buttonSurfaceOf(el) {
553
653
  }
554
654
  function alignSubjectOf(el) {
555
655
  const button = el.closest('[data-ohw-role="button"]');
556
- return button?.parentElement ?? el;
656
+ if (button?.parentElement) return button.parentElement;
657
+ let subject = el;
658
+ while (subject.parentElement && !subject.parentElement.hasAttribute("data-ohw-section") && /^inline/.test(getComputedStyle(subject).display)) {
659
+ subject = subject.parentElement;
660
+ }
661
+ const TEXT_ROW = /^(P|LI|H[1-6]|BLOCKQUOTE|FIGCAPTION|DT|DD)$/;
662
+ const parent = subject.parentElement;
663
+ if (parent && TEXT_ROW.test(parent.tagName) && /^(inline-)?flex$/.test(getComputedStyle(parent).display)) {
664
+ return parent;
665
+ }
666
+ if (parent && !parent.hasAttribute("data-ohw-section")) {
667
+ const pcs = getComputedStyle(parent);
668
+ const children = Array.from(parent.children);
669
+ const textRowChild = (c) => c.hasAttribute("data-ohw-editable") || (c.textContent ?? "").trim().length === 0 || c.getAttribute("aria-hidden") === "true";
670
+ if (/^(inline-)?flex$/.test(pcs.display) && pcs.flexDirection.startsWith("row") && children.length > 0 && children.some((c) => c.hasAttribute("data-ohw-editable")) && children.every(textRowChild)) {
671
+ return parent;
672
+ }
673
+ }
674
+ const row = subject.closest("li");
675
+ if (row && row !== subject && /^(inline-)?flex$/.test(getComputedStyle(row).display)) {
676
+ return row;
677
+ }
678
+ return subject;
557
679
  }
558
680
  function applyStylesToDom(store) {
559
681
  ensureStyleSheet();
560
682
  clearSectionAttrs(document);
561
683
  clearNodeProps(document);
562
684
  loadStyleFonts(
563
- store ? Object.values(store.nodes).flatMap((n) => n.fontFamily ? [n.fontFamily] : []) : []
685
+ store ? [
686
+ ...Object.values(store.nodes).flatMap((n) => n.fontFamily ? [n.fontFamily] : []),
687
+ ...Object.values(store.sections).flatMap((s) => s.fontFamily ? [s.fontFamily] : [])
688
+ ] : []
564
689
  );
565
690
  if (!store) return;
566
691
  for (const [sectionId, override] of Object.entries(store.sections)) {
@@ -580,6 +705,23 @@ function applyStylesToDom(store) {
580
705
  section.style.setProperty("background", override.sectionBackgroundColor, "important");
581
706
  section.setAttribute("data-ohw-style-bgcolor", "");
582
707
  }
708
+ if (override.textColor !== void 0 || override.fontFamily !== void 0) {
709
+ const textEls = marker.querySelectorAll(
710
+ '[data-ohw-editable="text"], [data-ohw-editable="plain"], [data-ohw-field-label]'
711
+ );
712
+ for (const el of Array.from(textEls)) {
713
+ if (override.textColor !== void 0) {
714
+ saveInline(el, "color");
715
+ el.style.setProperty("color", override.textColor, "important");
716
+ el.setAttribute(NODE_WROTE_ATTR, "");
717
+ }
718
+ if (override.fontFamily !== void 0) {
719
+ saveInline(el, "font-family");
720
+ el.style.setProperty("font-family", `'${override.fontFamily}'`, "important");
721
+ el.setAttribute(NODE_WROTE_ATTR, "");
722
+ }
723
+ }
724
+ }
583
725
  }
584
726
  }
585
727
  for (const [key, override] of Object.entries(store.nodes)) {
@@ -626,15 +768,557 @@ function applyStylesToDom(store) {
626
768
  }
627
769
  }
628
770
  }
771
+ for (const [sectionId, override] of Object.entries(store.sections)) {
772
+ if (override.headingColor === void 0) continue;
773
+ const escaped = CSS.escape(sectionId);
774
+ const byInstance = document.querySelectorAll(`[data-ohw-instance="${escaped}"]`);
775
+ const sections = byInstance.length ? byInstance : document.querySelectorAll(`[data-ohw-section="${escaped}"]`);
776
+ for (const marker of Array.from(sections)) {
777
+ const headings = marker.querySelectorAll(
778
+ ":is(h1, h2, h3, h4, h5, h6)[data-ohw-editable], :is(h1, h2, h3, h4, h5, h6) > [data-ohw-editable]"
779
+ );
780
+ for (const el of Array.from(headings)) {
781
+ saveInline(el, "color");
782
+ el.style.setProperty("color", override.headingColor, "important");
783
+ el.setAttribute(NODE_WROTE_ATTR, "");
784
+ }
785
+ }
786
+ }
629
787
  }
630
788
 
631
789
  // src/ui/ai-tree/aiSectionsManager.tsx
632
790
  var import_react_dom = require("react-dom");
633
791
  var import_client = require("react-dom/client");
634
792
 
793
+ // src/lib/sections.ts
794
+ var LINK_PICKER_EXCLUDED_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
795
+ function isChromeSection(el) {
796
+ return el.matches("header, nav, footer, aside");
797
+ }
798
+ function titleCaseSectionId(id) {
799
+ return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
800
+ }
801
+ function parseSectionsFromRoot(root) {
802
+ const seen = /* @__PURE__ */ new Set();
803
+ const sections = [];
804
+ for (const el of root.querySelectorAll("[data-ohw-section]")) {
805
+ const id = el.getAttribute("data-ohw-section") ?? "";
806
+ if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
807
+ if (el.parentElement?.closest("[data-ohw-section]")) continue;
808
+ if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
809
+ continue;
810
+ seen.add(id);
811
+ const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
812
+ sections.push({ id, label });
813
+ }
814
+ return sections;
815
+ }
816
+ function collectSectionsFromDom() {
817
+ if (typeof document === "undefined") return [];
818
+ return parseSectionsFromRoot(document);
819
+ }
820
+ function parseSectionsFromHtml(html) {
821
+ const doc = new DOMParser().parseFromString(html, "text/html");
822
+ return parseSectionsFromRoot(doc);
823
+ }
824
+
825
+ // src/lib/section-instances.ts
826
+ var SECTION_ORDER_KEY = "__ohw_section_order";
827
+ var REMOVED_ATTR = "data-ohw-section-removed";
828
+ function isRemovedSection(el) {
829
+ return el.hasAttribute(REMOVED_ATTR);
830
+ }
831
+ function movableUnit(el) {
832
+ return el.closest("[data-ohw-section-container]") ?? el;
833
+ }
834
+ function sectionTypeOf(el) {
835
+ return el.getAttribute("data-ohw-section") ?? el.querySelector("[data-ohw-section]")?.getAttribute("data-ohw-section") ?? "";
836
+ }
837
+ function sectionElementOf(el) {
838
+ return el.hasAttribute("data-ohw-section") ? el : el.querySelector("[data-ohw-section]") ?? el;
839
+ }
840
+ function collectTopLevelUnits(predicate) {
841
+ const seen = /* @__PURE__ */ new Set();
842
+ const result = [];
843
+ document.querySelectorAll("[data-ohw-section]").forEach((el) => {
844
+ if (!predicate(el)) return;
845
+ const unit = movableUnit(el);
846
+ if (unit.parentElement?.closest("[data-ohw-section],[data-ohw-section-container]")) return;
847
+ if (seen.has(unit)) return;
848
+ seen.add(unit);
849
+ result.push(unit);
850
+ });
851
+ return result;
852
+ }
853
+ function topLevelSections() {
854
+ return collectTopLevelUnits((el) => !isChromeSection(el) && !isRemovedSection(movableUnit(el)));
855
+ }
856
+ function instanceIdOf(el) {
857
+ return el.dataset.ohwInstance ?? el.dataset.ohwSection ?? "";
858
+ }
859
+ function findByInstanceId(instanceId) {
860
+ const escapedId = CSS.escape(instanceId);
861
+ const direct = document.querySelector(`[data-ohw-instance="${escapedId}"]`);
862
+ if (direct) return movableUnit(direct);
863
+ const bare = document.querySelector(`[data-ohw-section="${escapedId}"]:not([data-ohw-instance])`);
864
+ return bare ? movableUnit(bare) : null;
865
+ }
866
+ function planSectionMove(instanceId, targetIndex, currentPath) {
867
+ const sections = topLevelSections();
868
+ const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
869
+ if (index === -1) return null;
870
+ const dragged = sections[index];
871
+ const others = sections.filter((_, i) => i !== index);
872
+ const clamped = Math.max(0, Math.min(targetIndex, others.length));
873
+ const reordered = [...others.slice(0, clamped), dragged, ...others.slice(clamped)];
874
+ return reordered.map((el, order) => ({
875
+ instanceId: instanceIdOf(el),
876
+ type: sectionTypeOf(el),
877
+ order,
878
+ pagePath: currentPath
879
+ }));
880
+ }
881
+ function moveSectionInstance(instanceId, direction, currentPath) {
882
+ const sections = topLevelSections();
883
+ const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
884
+ if (index === -1) return null;
885
+ const siblingIndex = direction === "up" ? index - 1 : index + 1;
886
+ if (siblingIndex < 0 || siblingIndex >= sections.length) return null;
887
+ const entries = planSectionMove(instanceId, siblingIndex, currentPath);
888
+ if (!entries) return null;
889
+ applyPersistedOrder(entries);
890
+ return entries;
891
+ }
892
+ function syncRemovedFlags(entries) {
893
+ const removedIds = new Set(entries.filter((e) => e.removed).map((e) => e.instanceId));
894
+ document.querySelectorAll(`[${REMOVED_ATTR}]`).forEach((el) => {
895
+ if (!removedIds.has(instanceIdOf(el))) {
896
+ el.style.removeProperty("display");
897
+ el.removeAttribute(REMOVED_ATTR);
898
+ }
899
+ });
900
+ for (const id of removedIds) {
901
+ const el = findByInstanceId(id);
902
+ if (el) {
903
+ el.style.display = "none";
904
+ el.setAttribute(REMOVED_ATTR, "");
905
+ }
906
+ }
907
+ }
908
+ function applyPersistedOrder(entries) {
909
+ syncRemovedFlags(entries);
910
+ if (entries.length === 0) return;
911
+ const sections = topLevelSections();
912
+ if (sections.length === 0) return;
913
+ const orderIndex = new Map(entries.map((e) => [e.instanceId, e.order]));
914
+ const ordered = [...sections].sort((a, b) => {
915
+ const aOrder = orderIndex.get(instanceIdOf(a));
916
+ const bOrder = orderIndex.get(instanceIdOf(b));
917
+ if (aOrder === void 0 && bOrder === void 0) return 0;
918
+ if (aOrder === void 0) return 1;
919
+ if (bOrder === void 0) return -1;
920
+ return aOrder - bOrder;
921
+ });
922
+ let prev = null;
923
+ for (const el of ordered) {
924
+ if (prev) prev.after(el);
925
+ prev = el;
926
+ }
927
+ }
928
+ function setSectionRemoved(instanceId, currentPath, existingEntries, removed) {
929
+ if (!findByInstanceId(instanceId)) return null;
930
+ const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
931
+ const allSections = collectTopLevelUnits((el) => !isChromeSection(el));
932
+ allSections.forEach((el, order) => {
933
+ const id = instanceIdOf(el);
934
+ if (!byId.has(id)) {
935
+ byId.set(id, { instanceId: id, type: sectionTypeOf(el), order, pagePath: currentPath });
936
+ }
937
+ });
938
+ const target = byId.get(instanceId);
939
+ if (!target) return null;
940
+ byId.set(instanceId, { ...target, removed });
941
+ const entries = Array.from(byId.values());
942
+ applyPersistedOrder(entries);
943
+ return entries;
944
+ }
945
+ function deleteSectionInstance(instanceId, currentPath, existingEntries) {
946
+ return setSectionRemoved(instanceId, currentPath, existingEntries, true);
947
+ }
948
+ function restoreSectionInstance(instanceId, currentPath, existingEntries) {
949
+ return setSectionRemoved(instanceId, currentPath, existingEntries, false);
950
+ }
951
+ function duplicateSectionInstance(instanceId, newId, currentPath, existingEntries) {
952
+ const original = findByInstanceId(instanceId);
953
+ if (!original) return null;
954
+ const clone = original.cloneNode(true);
955
+ clone.setAttribute("data-ohw-instance", newId);
956
+ const keyRekeys = rekeySectionSubtree(clone, newId);
957
+ original.insertAdjacentElement("afterend", clone);
958
+ const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
959
+ const entries = topLevelSections().map((el, order) => {
960
+ const id = instanceIdOf(el);
961
+ return {
962
+ instanceId: id,
963
+ type: sectionTypeOf(el),
964
+ order,
965
+ pagePath: currentPath,
966
+ ...byId.get(id)?.removed ? { removed: true } : {}
967
+ };
968
+ });
969
+ applyPersistedOrder(entries);
970
+ return { entries, keyRekeys };
971
+ }
972
+ function newInstanceId() {
973
+ return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `instance-${Date.now()}-${Math.random().toString(36).slice(2)}`;
974
+ }
975
+ function getPageSectionOrderEntries(raw, currentPath) {
976
+ if (!raw) return [];
977
+ try {
978
+ const entries = JSON.parse(raw);
979
+ return entries.filter((e) => !e.pagePath || e.pagePath === currentPath);
980
+ } catch {
981
+ return [];
982
+ }
983
+ }
984
+ function mergePageSectionOrder(raw, currentPath, pageEntries) {
985
+ let all = [];
986
+ if (raw) {
987
+ try {
988
+ const parsed = JSON.parse(raw);
989
+ if (Array.isArray(parsed)) all = parsed;
990
+ } catch {
991
+ }
992
+ }
993
+ const otherPages = all.filter((e) => e && e.pagePath && e.pagePath !== currentPath);
994
+ const pageIds = new Set(pageEntries.map((e) => e.instanceId));
995
+ const removedHere = all.filter(
996
+ (e) => e && (!e.pagePath || e.pagePath === currentPath) && e.removed && !pageIds.has(e.instanceId)
997
+ );
998
+ return [...otherPages, ...removedHere, ...pageEntries];
999
+ }
1000
+ function rekeySectionSubtree(root, instanceId) {
1001
+ const suffix = `::${instanceId}`;
1002
+ const pairs = [];
1003
+ const rekey = (el, attr) => {
1004
+ const current = el.getAttribute(attr);
1005
+ if (!current) return;
1006
+ const base = current.includes("::") ? current.slice(0, current.indexOf("::")) : current;
1007
+ const next = `${base}${suffix}`;
1008
+ el.setAttribute(attr, next);
1009
+ pairs.push({ from: current, to: next });
1010
+ };
1011
+ if (root.hasAttribute("data-ohw-key")) rekey(root, "data-ohw-key");
1012
+ if (root.hasAttribute("data-ohw-href-key")) rekey(root, "data-ohw-href-key");
1013
+ root.querySelectorAll("[data-ohw-key]").forEach((el) => rekey(el, "data-ohw-key"));
1014
+ root.querySelectorAll("[data-ohw-href-key]").forEach((el) => rekey(el, "data-ohw-href-key"));
1015
+ return pairs;
1016
+ }
1017
+ function initSectionInstancesFromContent(content, currentPath) {
1018
+ document.querySelectorAll("[data-ohw-section]:not([data-ohw-instance])").forEach((el) => {
1019
+ el.setAttribute("data-ohw-instance", el.getAttribute("data-ohw-section") ?? "");
1020
+ });
1021
+ document.querySelectorAll("[data-ohw-section-container]:not([data-ohw-instance])").forEach((el) => {
1022
+ const type = sectionTypeOf(el);
1023
+ if (type) el.setAttribute("data-ohw-instance", type);
1024
+ });
1025
+ const entries = getPageSectionOrderEntries(content[SECTION_ORDER_KEY], currentPath);
1026
+ for (const entry of entries) {
1027
+ if (entry.instanceId === entry.type) continue;
1028
+ if (document.querySelector(`[data-ohw-instance="${CSS.escape(entry.instanceId)}"]`)) continue;
1029
+ const original = document.querySelector(
1030
+ `[data-ohw-section="${CSS.escape(entry.type)}"][data-ohw-instance="${CSS.escape(entry.type)}"]`
1031
+ );
1032
+ if (!original) continue;
1033
+ const clone = original.cloneNode(true);
1034
+ clone.setAttribute("data-ohw-instance", entry.instanceId);
1035
+ rekeySectionSubtree(clone, entry.instanceId);
1036
+ original.insertAdjacentElement("afterend", clone);
1037
+ }
1038
+ applyPersistedOrder(entries);
1039
+ }
1040
+
635
1041
  // src/ui/ai-tree/AiTreeRenderer.tsx
636
1042
  var import_react = __toESM(require("react"), 1);
637
1043
  var import_lucide_react = require("lucide-react");
1044
+
1045
+ // src/lib/placeholder-imagery.ts
1046
+ var U = (id) => `https://images.unsplash.com/photo-${id}?auto=format&fit=crop&w=1600&q=80`;
1047
+ var GENERIC = [
1048
+ U("1441986300917-64674bd600d8"),
1049
+ U("1486406146926-c627a92ad1ab"),
1050
+ U("1497032628192-86f99bcd76bc"),
1051
+ U("1521737604893-d14cc237f11d"),
1052
+ U("1522071820081-009f0129c71c"),
1053
+ U("1519389950473-47ba0277781c"),
1054
+ U("1460925895917-afdab827c52f"),
1055
+ U("1504384308090-c894fdcc538d")
1056
+ ];
1057
+ var PEOPLE = [
1058
+ U("1500648767791-00dcc994a43e"),
1059
+ U("1494790108377-be9c29b29330"),
1060
+ U("1507003211169-0a1dd7228f2d"),
1061
+ U("1438761681033-6461ffad8d80"),
1062
+ U("1544005313-94ddf0286df2"),
1063
+ U("1472099645785-5658abf4ff4e"),
1064
+ U("1519085360753-af0119f7cbe7"),
1065
+ U("1534528741775-53994a69daeb")
1066
+ ];
1067
+ var THEMED = [
1068
+ {
1069
+ keywords: ["portrait", "headshot", "person", "people", "team", "staff", "avatar", "founder", "face"],
1070
+ pool: PEOPLE
1071
+ },
1072
+ {
1073
+ keywords: ["pet", "dog", "cat", "puppy", "kitten", "vet", "animal"],
1074
+ pool: [
1075
+ U("1548199973-03cce0bbc87b"),
1076
+ U("1450778869180-41d0601e046e"),
1077
+ U("1583511655857-d19b40a7a54e"),
1078
+ U("1587300003388-59208cc962cb"),
1079
+ U("1517849845537-4d257902454a"),
1080
+ U("1601758228041-f3b2795255f1")
1081
+ ]
1082
+ },
1083
+ {
1084
+ keywords: [
1085
+ "baker",
1086
+ "bakery",
1087
+ "cafe",
1088
+ "coffee",
1089
+ "latte",
1090
+ "restaurant",
1091
+ "pastr",
1092
+ "bread",
1093
+ "cake",
1094
+ "cater",
1095
+ "chef",
1096
+ "kitchen",
1097
+ "food",
1098
+ "pizza",
1099
+ "dessert",
1100
+ "brunch",
1101
+ "bistro",
1102
+ "deli",
1103
+ "dish",
1104
+ "menu"
1105
+ ],
1106
+ pool: [
1107
+ U("1509440159596-0249088772ff"),
1108
+ U("1555507036-ab1f4038808a"),
1109
+ U("1517433670267-08bbd4be890f"),
1110
+ U("1486427944299-d1955d23e34d"),
1111
+ U("1504754524776-8f4f37790ca0"),
1112
+ U("1495474472287-4d71bcdd2085"),
1113
+ U("1521017432531-fbd92d768814"),
1114
+ U("1556909114-f6e7ad7d3136")
1115
+ ]
1116
+ },
1117
+ {
1118
+ keywords: [
1119
+ "shop",
1120
+ "store",
1121
+ "boutique",
1122
+ "retail",
1123
+ "clothing",
1124
+ "fashion",
1125
+ "jewel",
1126
+ "gift",
1127
+ "florist",
1128
+ "market",
1129
+ "grocer",
1130
+ "product",
1131
+ "storefront"
1132
+ ],
1133
+ pool: [
1134
+ U("1441984904996-e0b6ba687e04"),
1135
+ U("1472851294608-062f824d29cc"),
1136
+ U("1523381210434-271e8be1f52b"),
1137
+ U("1534452203293-494d7ddbf7e0"),
1138
+ U("1445205170230-053b83016050"),
1139
+ U("1560243563-062bfc001d68")
1140
+ ]
1141
+ },
1142
+ {
1143
+ keywords: [
1144
+ "yoga",
1145
+ "pilates",
1146
+ "fitness",
1147
+ "gym",
1148
+ "workout",
1149
+ "trainer",
1150
+ "wellness",
1151
+ "meditat",
1152
+ "massage",
1153
+ "therap",
1154
+ "physio",
1155
+ "chiro",
1156
+ "nutrition",
1157
+ "spa",
1158
+ "studio"
1159
+ ],
1160
+ pool: [
1161
+ U("1544367567-0f2fcb009e0b"),
1162
+ U("1506126613408-eca07ce68773"),
1163
+ U("1545205597-3d9d02c29597"),
1164
+ U("1552196563-55cd4e45efb3"),
1165
+ U("1518611012118-696072aa579a"),
1166
+ U("1571019613454-1cb2f99b2d8b"),
1167
+ U("1540555700478-4be289fbecef"),
1168
+ U("1519824145371-296894a0daa9")
1169
+ ]
1170
+ },
1171
+ {
1172
+ keywords: [
1173
+ "salon",
1174
+ "hairdress",
1175
+ "haircut",
1176
+ "barber",
1177
+ "manicure",
1178
+ "pedicure",
1179
+ "nails",
1180
+ "beauty",
1181
+ "makeup",
1182
+ "cosmetic",
1183
+ "eyelash",
1184
+ "eyebrow",
1185
+ "skincare",
1186
+ "esthetic",
1187
+ "waxing",
1188
+ "hair"
1189
+ ],
1190
+ pool: [
1191
+ U("1560066984-138dadb4c035"),
1192
+ U("1522337660859-02fbefca4702"),
1193
+ U("1562322140-8baeececf3df"),
1194
+ U("1521590832167-7bcbfaa6381f"),
1195
+ U("1487412947147-5cebf100ffc2"),
1196
+ U("1526045478516-99145907023c")
1197
+ ]
1198
+ },
1199
+ {
1200
+ keywords: [
1201
+ "cleaning",
1202
+ "plumb",
1203
+ "electric",
1204
+ "landscap",
1205
+ "contractor",
1206
+ "handyman",
1207
+ "renov",
1208
+ "hvac",
1209
+ "roofing",
1210
+ "painting",
1211
+ "carpentry",
1212
+ "flooring",
1213
+ "movers",
1214
+ "construction",
1215
+ "tools"
1216
+ ],
1217
+ pool: [
1218
+ U("1581578731548-c64695cc6952"),
1219
+ U("1504307651254-35680f356dfd"),
1220
+ U("1581092160562-40aa08e78837"),
1221
+ U("1621905251189-08b45d6a269e"),
1222
+ U("1558618666-fcd25c85cd64"),
1223
+ U("1585128792020-803d29415281")
1224
+ ]
1225
+ },
1226
+ {
1227
+ keywords: [
1228
+ "legal",
1229
+ "attorney",
1230
+ "lawyer",
1231
+ "account",
1232
+ "bookkeep",
1233
+ "consult",
1234
+ "coaching",
1235
+ "financ",
1236
+ "insurance",
1237
+ "realtor",
1238
+ "estate",
1239
+ "marketing",
1240
+ "agency",
1241
+ "office",
1242
+ "business",
1243
+ "desk"
1244
+ ],
1245
+ pool: [
1246
+ U("1497366216548-37526070297c"),
1247
+ U("1497366811353-6870744d04b2"),
1248
+ U("1454165804606-c3d57bc86b40"),
1249
+ U("1521791136064-7986c2920216"),
1250
+ U("1556761175-b413da4baf72"),
1251
+ U("1542744173-8e7e53415bb0")
1252
+ ]
1253
+ },
1254
+ {
1255
+ keywords: [
1256
+ "wedding",
1257
+ "event",
1258
+ "party",
1259
+ "celebrat",
1260
+ "venue",
1261
+ "community",
1262
+ "nonprofit",
1263
+ "charity",
1264
+ "workshop",
1265
+ "photograph",
1266
+ "concert"
1267
+ ],
1268
+ pool: [
1269
+ U("1511578314322-379afb476865"),
1270
+ U("1501281668745-f7f57925c3b4"),
1271
+ U("1523580494863-6f3031224c94"),
1272
+ U("1540575467063-178a50c2df87"),
1273
+ U("1505236858219-8359eb29e329"),
1274
+ U("1528605248644-14dd04022da1")
1275
+ ]
1276
+ }
1277
+ ];
1278
+ function poolForSubject(subject) {
1279
+ for (const theme of THEMED) {
1280
+ if (theme.keywords.some((k) => subject.includes(k))) {
1281
+ return theme.pool;
1282
+ }
1283
+ }
1284
+ return GENERIC;
1285
+ }
1286
+ function mixedHash(text) {
1287
+ let hash = 2166136261;
1288
+ for (let i = 0; i < text.length; i++) {
1289
+ hash ^= text.charCodeAt(i);
1290
+ hash = Math.imul(hash, 16777619);
1291
+ }
1292
+ return hash >>> 16 & 65535;
1293
+ }
1294
+ function resolvePlaceholderRef(ref) {
1295
+ const match = /^placeholder:([a-z0-9-]+)$/.exec(ref);
1296
+ if (!match) return null;
1297
+ const subject = match[1];
1298
+ const pool = poolForSubject(subject.replace(/-\d+$/, ""));
1299
+ return pool[mixedHash(ref) % pool.length];
1300
+ }
1301
+ function collectPlaceholderRefs(tree) {
1302
+ const seen = /* @__PURE__ */ new Set();
1303
+ for (const match of JSON.stringify(tree ?? null).matchAll(/"(placeholder:[a-z0-9-]+)"/gu)) {
1304
+ seen.add(match[1]);
1305
+ }
1306
+ return [...seen];
1307
+ }
1308
+ function buildPlaceholderMap(tree) {
1309
+ const map = {};
1310
+ const cursor = /* @__PURE__ */ new Map();
1311
+ for (const ref of collectPlaceholderRefs(tree)) {
1312
+ const subject = ref.slice("placeholder:".length).replace(/-\d+$/, "");
1313
+ const pool = poolForSubject(subject);
1314
+ const start = cursor.get(pool) ?? mixedHash(ref) % pool.length;
1315
+ map[ref] = pool[start % pool.length];
1316
+ cursor.set(pool, start + 1);
1317
+ }
1318
+ return map;
1319
+ }
1320
+
1321
+ // src/ui/ai-tree/AiTreeRenderer.tsx
638
1322
  var import_jsx_runtime = require("react/jsx-runtime");
639
1323
  function lucideByName(name) {
640
1324
  const pascal = name.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
@@ -648,6 +1332,7 @@ var typeStyle = (spec, font) => ({
648
1332
  fontWeight: spec.weight
649
1333
  });
650
1334
  var str = (value) => typeof value === "string" ? value : "";
1335
+ var cardRadius = (slots) => slots.cornerStyle === "sharp" ? 0 : AI_TREE_TOKENS.radiusCard;
651
1336
  var featureLines = (value) => value.split(/\n|<br\s*\/?>/i).map((line) => line.trim()).filter(Boolean);
652
1337
  var CHECK_MASK = `data:image/svg+xml,${encodeURIComponent(
653
1338
  '<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>'
@@ -674,6 +1359,25 @@ var FEATURE_LINE_CSS = [
674
1359
  `background-color:currentColor;-webkit-mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px;`,
675
1360
  `mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px}`
676
1361
  ].join("");
1362
+ function buttonShellStyle(ctx, fullWidth) {
1363
+ const bs = ctx.buttonStyle;
1364
+ if (bs) {
1365
+ return {
1366
+ borderRadius: bs.radius,
1367
+ ...bs.padding ? { padding: bs.padding } : {},
1368
+ ...bs.fontFamily ? { fontFamily: bs.fontFamily } : { fontFamily: ctx.brand.fonts.body },
1369
+ ...bs.fontSize ? { fontSize: bs.fontSize } : {},
1370
+ ...bs.fontWeight ? { fontWeight: bs.fontWeight } : {},
1371
+ ...bs.letterSpacing && bs.letterSpacing !== "normal" ? { letterSpacing: bs.letterSpacing } : {},
1372
+ ...bs.textTransform && bs.textTransform !== "none" ? { textTransform: bs.textTransform } : {}
1373
+ };
1374
+ }
1375
+ return {
1376
+ borderRadius: AI_TREE_TOKENS.radiusButton,
1377
+ padding: fullWidth ? `${AI_TREE_TOKENS.spacing2}px ${AI_TREE_TOKENS.spacing4}px` : `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`,
1378
+ ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
1379
+ };
1380
+ }
677
1381
  function hexLuminance(color) {
678
1382
  const m = /^#([0-9a-f]{6})$/i.exec(color.trim());
679
1383
  if (!m) return null;
@@ -690,6 +1394,12 @@ function hexContrast(a, b) {
690
1394
  const [hi, lo] = la > lb ? [la, lb] : [lb, la];
691
1395
  return (hi + 0.05) / (lo + 0.05);
692
1396
  }
1397
+ function primaryButtonLabel(brand) {
1398
+ const darkC = hexContrast(brand.palette.primary, brand.palette.dark);
1399
+ const lightC = hexContrast(brand.palette.primary, AI_TREE_TOKENS.textPrimaryForeground);
1400
+ if (darkC === null || lightC === null) return AI_TREE_TOKENS.textPrimaryForeground;
1401
+ return darkC > lightC ? brand.palette.dark : AI_TREE_TOKENS.textPrimaryForeground;
1402
+ }
693
1403
  function accentBandContext(brand) {
694
1404
  const p = brand.palette;
695
1405
  const lightWins = (hexContrast(p.light, p.primary) ?? 99) >= (hexContrast(p.dark, p.primary) ?? 0);
@@ -707,6 +1417,22 @@ function accentBandContext(brand) {
707
1417
  function textAttrs(ctx, path) {
708
1418
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
709
1419
  }
1420
+ var AI_RESPONSIVE_CSS = [
1421
+ "@media (max-width: 960px) {",
1422
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
1423
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
1424
+ "}",
1425
+ "@media (max-width: 640px) {",
1426
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
1427
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
1428
+ // Group containers flatten to a column on phones; span placements come along for free.
1429
+ " [data-ai-responsive] [data-ai-group] { display: flex !important; flex-direction: column !important; }",
1430
+ " [data-ai-responsive] [data-ai-group] > * { grid-column: auto !important; }",
1431
+ " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
1432
+ " [data-ai-responsive] { overflow-x: hidden; }",
1433
+ " [data-ai-responsive] img { max-width: 100%; }",
1434
+ "}"
1435
+ ].join("\n");
710
1436
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
711
1437
  function MediaBox({
712
1438
  refValue,
@@ -719,13 +1445,17 @@ function MediaBox({
719
1445
  const url = refValue ? ctx.resolveMedia(refValue) : null;
720
1446
  const isIcon = /^(lucide|simple):/.test(refValue);
721
1447
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
722
- const editAttrs = ctx.keyFor && editPath && !isIcon ? { "data-ohw-key": ctx.keyFor(editPath), "data-ohw-editable": "image" } : {};
1448
+ const editAttrs = ctx.keyFor && editPath ? {
1449
+ "data-ohw-key": ctx.keyFor(editPath),
1450
+ "data-ohw-editable": isIcon ? "icon" : "image"
1451
+ } : {};
723
1452
  if (isIcon) {
724
1453
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
725
1454
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
726
1455
  "span",
727
1456
  {
728
1457
  "data-ai-icon": refValue,
1458
+ ...editAttrs,
729
1459
  style: {
730
1460
  display: "inline-flex",
731
1461
  width: 48,
@@ -789,12 +1519,15 @@ function ButtonEl({
789
1519
  width: fullWidth ? "100%" : void 0,
790
1520
  alignItems: "center",
791
1521
  justifyContent: "center",
792
- padding: fullWidth ? `${AI_TREE_TOKENS.spacing2}px ${AI_TREE_TOKENS.spacing4}px` : `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`,
793
- borderRadius: AI_TREE_TOKENS.radiusButton,
794
1522
  textDecoration: "none",
795
1523
  cursor: "pointer",
796
- ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body),
797
- ...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 }
1524
+ ...buttonShellStyle(ctx, fullWidth),
1525
+ ...secondary ? { border: `1px solid ${ctx.brand.palette.dark}`, color: ctx.brand.palette.dark } : (
1526
+ // Off-band, prefer the template button's measured fill/label pair — a template may
1527
+ // fill its CTAs with any token (hvac: accent bg, primary text). On an accent band
1528
+ // the flipped palette keeps contrast, so the brand-driven colours stay.
1529
+ !ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : { background: ctx.brand.palette.primary, color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand) }
1530
+ )
798
1531
  },
799
1532
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
800
1533
  }
@@ -970,10 +1703,11 @@ function PricingCard({ node, ctx, path }) {
970
1703
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
971
1704
  "div",
972
1705
  {
1706
+ "data-ohw-card": "",
973
1707
  style: {
974
1708
  background: hasBg ? ctx.brand.palette.light : "transparent",
975
1709
  border: `1px solid ${dark}`,
976
- borderRadius: AI_TREE_TOKENS.radiusCard,
1710
+ borderRadius: cardRadius(slots),
977
1711
  padding: AI_TREE_TOKENS.paddingBlock,
978
1712
  display: "flex",
979
1713
  flexDirection: "column",
@@ -1078,10 +1812,11 @@ function TestimonialCard({ node, ctx, path }) {
1078
1812
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1079
1813
  "div",
1080
1814
  {
1815
+ "data-ohw-card": "",
1081
1816
  "data-ai-avatar-pos": avatarPos ?? void 0,
1082
1817
  style: {
1083
1818
  background: hasBg ? ctx.cardSurface : "transparent",
1084
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1819
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1085
1820
  overflow: "hidden",
1086
1821
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1087
1822
  minWidth: 0
@@ -1116,10 +1851,11 @@ function TeamCard({ node, ctx, path }) {
1116
1851
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1117
1852
  "div",
1118
1853
  {
1854
+ "data-ohw-card": "",
1119
1855
  "data-ai-avatar-pos": avatarPos ?? void 0,
1120
1856
  style: {
1121
1857
  background: hasBg ? ctx.cardSurface : "transparent",
1122
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1858
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1123
1859
  overflow: "hidden",
1124
1860
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1125
1861
  minWidth: 0,
@@ -1290,9 +2026,10 @@ function CardBlock({ node, ctx, path }) {
1290
2026
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1291
2027
  "div",
1292
2028
  {
2029
+ "data-ohw-card": "",
1293
2030
  style: {
1294
2031
  background: hasBg ? ctx.cardSurface : "transparent",
1295
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
2032
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1296
2033
  overflow: "hidden",
1297
2034
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1298
2035
  display: horizontal ? "flex" : "block",
@@ -1320,7 +2057,7 @@ function CardBlock({ node, ctx, path }) {
1320
2057
  ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1321
2058
  "div",
1322
2059
  {
1323
- 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" },
2060
+ 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" },
1324
2061
  children: media
1325
2062
  }
1326
2063
  )),
@@ -1611,7 +2348,7 @@ function CollectionBlock({ node, ctx, path }) {
1611
2348
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1612
2349
  "div",
1613
2350
  {
1614
- "data-ai-grid": "",
2351
+ "data-ai-grid": String(itemsPerRow),
1615
2352
  style: {
1616
2353
  display: "grid",
1617
2354
  gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
@@ -1731,6 +2468,32 @@ function renderNode(node, ctx, path) {
1731
2468
  if (child) {
1732
2469
  return renderNode(child, ctx, `${path}.c0`);
1733
2470
  }
2471
+ if (str(slots.provider) === "map" && str(slots.query)) {
2472
+ const query = str(slots.query);
2473
+ const mapAttrs = ctx.keyFor ? {
2474
+ "data-ohw-key": ctx.keyFor(`${path}.query`),
2475
+ "data-ohw-editable": "map",
2476
+ "data-ohw-map-query": query
2477
+ } : {};
2478
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2479
+ "iframe",
2480
+ {
2481
+ ...mapAttrs,
2482
+ "data-ai-embed": "map",
2483
+ title: str(slots.title) || "Map",
2484
+ src: `https://www.google.com/maps?q=${encodeURIComponent(query)}&output=embed`,
2485
+ loading: "lazy",
2486
+ referrerPolicy: "no-referrer-when-downgrade",
2487
+ style: {
2488
+ width: "100%",
2489
+ minHeight: 320,
2490
+ border: 0,
2491
+ borderRadius: AI_TREE_TOKENS.radiusCard,
2492
+ display: "block"
2493
+ }
2494
+ }
2495
+ );
2496
+ }
1734
2497
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1735
2498
  "div",
1736
2499
  {
@@ -1834,15 +2597,14 @@ function renderNode(node, ctx, path) {
1834
2597
  alignSelf: submitAlign,
1835
2598
  border: "none",
1836
2599
  cursor: "pointer",
1837
- padding: "12px 24px",
1838
- // Corner radius follows the host template's own buttons (measured from a template
1839
- // CTA); 8px only when the page has no template button to match.
1840
- borderRadius: ctx.buttonRadius ?? 8,
1841
- // Brand-styled: primary fill, brand-derived label colour (not a fixed token) so it
1842
- // reads correctly on custom palettes.
1843
- background: ctx.brand.palette.primary,
1844
- color: ctx.buttonLabel ?? ctx.brand.palette.light,
1845
- ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
2600
+ // Shape/padding/typography follow the host template's own buttons.
2601
+ ...buttonShellStyle(ctx),
2602
+ // Brand-styled: the template button's measured fill/label pair off-band, else
2603
+ // primary fill with a brand-derived label so it reads on custom palettes.
2604
+ ...!ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : {
2605
+ background: ctx.brand.palette.primary,
2606
+ color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand)
2607
+ }
1846
2608
  },
1847
2609
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
1848
2610
  },
@@ -1877,7 +2639,7 @@ function renderNode(node, ctx, path) {
1877
2639
  function AiTreeRenderer({
1878
2640
  tree,
1879
2641
  brand,
1880
- buttonRadius,
2642
+ buttonStyle,
1881
2643
  resolveMedia,
1882
2644
  editKeyPrefix
1883
2645
  }) {
@@ -1887,13 +2649,18 @@ function AiTreeRenderer({
1887
2649
  const resolvedBrand = brand ?? AI_DEFAULT_BRAND;
1888
2650
  const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
1889
2651
  const blockBrand = band?.brand ?? resolvedBrand;
2652
+ const placeholderMap = buildPlaceholderMap(tree);
1890
2653
  const ctx = {
1891
2654
  brand: blockBrand,
1892
- resolveMedia: resolveMedia ?? (() => null),
2655
+ // An owner/library ref resolves through the host resolver; a `placeholder:<subject>` ref the
2656
+ // host cannot resolve falls back to real stock photography (the per-section map first, then a
2657
+ // standalone resolve), so generated galleries, image rows, and overlay backgrounds arrive with
2658
+ // photos instead of grey boxes.
2659
+ resolveMedia: (ref) => resolveMedia?.(ref) ?? placeholderMap[ref] ?? resolvePlaceholderRef(ref),
1893
2660
  cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
1894
2661
  keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
1895
2662
  sectionAlignment: (tree.settings ?? {}).alignment === "center" ? "center" : "left",
1896
- buttonRadius,
2663
+ buttonStyle,
1897
2664
  ...band ? { buttonLabel: band.buttonLabel } : {}
1898
2665
  };
1899
2666
  const settings = tree.settings ?? {};
@@ -1934,6 +2701,7 @@ function AiTreeRenderer({
1934
2701
  {
1935
2702
  "data-ai-section": tree.tag ?? "",
1936
2703
  ...bgAttrs,
2704
+ "data-ai-responsive": "",
1937
2705
  style: {
1938
2706
  position: "relative",
1939
2707
  padding: `${pad}px 0`,
@@ -1944,12 +2712,13 @@ function AiTreeRenderer({
1944
2712
  color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
1945
2713
  },
1946
2714
  children: [
1947
- isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
2715
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
1948
2716
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
2717
+ isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
1949
2718
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1950
2719
  "div",
1951
2720
  {
1952
- "data-ai-container": "",
2721
+ "data-ai-section-inner": "",
1953
2722
  style: {
1954
2723
  position: "relative",
1955
2724
  maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
@@ -1960,7 +2729,7 @@ function AiTreeRenderer({
1960
2729
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1961
2730
  "div",
1962
2731
  {
1963
- "data-ai-row": "",
2732
+ "data-ai-columns": "",
1964
2733
  style: {
1965
2734
  display: "grid",
1966
2735
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -1999,21 +2768,63 @@ function AiTreeRenderer({
1999
2768
  var import_jsx_runtime2 = require("react/jsx-runtime");
2000
2769
  var CONTAINER_ATTR = "data-ohw-ai-generated";
2001
2770
  var REPLACED_ATTR = "data-ohw-ai-replaced-by";
2002
- var REMOVED_ATTR = "data-ohw-ai-removed";
2771
+ var REMOVED_ATTR2 = "data-ohw-ai-removed";
2003
2772
  var TEMPLATE_HIDDEN_ATTR = "data-ohw-ai-template-hidden";
2004
2773
  var CHROME_SECTION_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
2774
+ function isChromeSection2(el) {
2775
+ if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) return true;
2776
+ return el.tagName === "HEADER" || el.tagName === "FOOTER";
2777
+ }
2005
2778
  function readRootVar(name) {
2006
2779
  if (typeof document === "undefined") return "";
2007
2780
  return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
2008
2781
  }
2782
+ function normalizeColorToHex(value) {
2783
+ if (!value || typeof document === "undefined") return value;
2784
+ const canvas = document.createElement("canvas");
2785
+ canvas.width = 1;
2786
+ canvas.height = 1;
2787
+ const ctx = canvas.getContext("2d");
2788
+ if (!ctx) return value;
2789
+ ctx.fillStyle = value;
2790
+ ctx.fillRect(0, 0, 1, 1);
2791
+ const [r2, g, b] = ctx.getImageData(0, 0, 1, 1).data;
2792
+ const toHex = (n) => n.toString(16).padStart(2, "0");
2793
+ return `#${toHex(r2)}${toHex(g)}${toHex(b)}`;
2794
+ }
2795
+ var GENERIC_FONT_KEYWORDS = /* @__PURE__ */ new Set([
2796
+ "serif",
2797
+ "sans-serif",
2798
+ "monospace",
2799
+ "cursive",
2800
+ "fantasy",
2801
+ "system-ui",
2802
+ "ui-serif",
2803
+ "ui-sans-serif",
2804
+ "ui-monospace",
2805
+ "ui-rounded",
2806
+ "math",
2807
+ "emoji",
2808
+ "fangsong"
2809
+ ]);
2810
+ function primaryFontFamily(stack) {
2811
+ const first = stack.split(",").map((part) => part.trim().replace(/^["']|["']$/g, "")).find((part) => part && !GENERIC_FONT_KEYWORDS.has(part.toLowerCase()));
2812
+ return first ?? "";
2813
+ }
2814
+ function humanizeFontFamily(name) {
2815
+ 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());
2816
+ }
2817
+ function readFontVar(name) {
2818
+ return humanizeFontFamily(primaryFontFamily(readRootVar(name)));
2819
+ }
2009
2820
  function deriveBrandOverride() {
2010
2821
  const dark = readRootVar("--ohw-brand-dark");
2011
2822
  const primary = readRootVar("--ohw-brand-primary");
2012
2823
  const light = readRootVar("--ohw-brand-light");
2013
2824
  if (!dark || !primary || !light) return null;
2014
2825
  const accent = readRootVar("--ohw-brand-accent");
2015
- const heading = readRootVar("--font-heading") || readRootVar("--font-display");
2016
- const body = readRootVar("--font-body");
2826
+ const heading = readFontVar("--font-heading") || readFontVar("--font-display");
2827
+ const body = readFontVar("--font-body");
2017
2828
  return {
2018
2829
  palette: { dark, primary, accent: accent || dark, light },
2019
2830
  fonts: {
@@ -2022,28 +2833,98 @@ function deriveBrandOverride() {
2022
2833
  }
2023
2834
  };
2024
2835
  }
2025
- function deriveTemplateBrand() {
2026
- const dark = readRootVar("--color-dark");
2027
- const primary = readRootVar("--color-primary");
2028
- const light = readRootVar("--color-light");
2836
+ function deriveTemplateBrandLive() {
2837
+ const dark = readRootVar("--color-dark") || readRootVar("--brand-text");
2838
+ const primary = readRootVar("--color-primary") || readRootVar("--brand-primary");
2839
+ const light = readRootVar("--color-light") || readRootVar("--brand-background");
2029
2840
  if (!dark || !primary || !light) return null;
2030
- const accent = readRootVar("--color-accent");
2031
- const heading = readRootVar("--font-heading") || readRootVar("--font-display");
2032
- const body = readRootVar("--font-body");
2841
+ const accent = readRootVar("--color-accent") || readRootVar("--brand-accent");
2842
+ const heading = readFontVar("--brand-font-heading") || readFontVar("--font-heading") || readFontVar("--font-display");
2843
+ const body = readFontVar("--brand-font-body") || readFontVar("--font-body");
2033
2844
  return {
2034
- palette: { dark, primary, accent: accent || dark, light },
2845
+ palette: {
2846
+ dark: normalizeColorToHex(dark),
2847
+ primary: normalizeColorToHex(primary),
2848
+ accent: normalizeColorToHex(accent || dark),
2849
+ light: normalizeColorToHex(light)
2850
+ },
2035
2851
  fonts: {
2036
2852
  heading: heading || AI_DEFAULT_BRAND.fonts.heading,
2037
2853
  body: body || AI_DEFAULT_BRAND.fonts.body
2038
2854
  }
2039
2855
  };
2040
2856
  }
2041
- function deriveTemplateButtonRadius() {
2857
+ function deriveTemplateFontsLive() {
2858
+ const heading = readFontVar("--brand-font-heading") || readFontVar("--font-heading") || readFontVar("--font-display");
2859
+ const body = readFontVar("--brand-font-body") || readFontVar("--font-body");
2860
+ if (!heading && !body) return null;
2861
+ return {
2862
+ heading: heading || AI_DEFAULT_BRAND.fonts.heading,
2863
+ body: body || AI_DEFAULT_BRAND.fonts.body
2864
+ };
2865
+ }
2866
+ var OVERRIDE_VAR_NAMES = [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES, ...FONT_VARS.heading, ...FONT_VARS.body];
2867
+ function withOverrideStripped(read) {
2868
+ const root = document.documentElement;
2869
+ const restore = OVERRIDE_VAR_NAMES.map((name) => [name, root.style.getPropertyValue(name)]);
2870
+ for (const name of OVERRIDE_VAR_NAMES) root.style.removeProperty(name);
2871
+ try {
2872
+ return read();
2873
+ } finally {
2874
+ for (const [name, value] of restore) if (value) root.style.setProperty(name, value);
2875
+ }
2876
+ }
2877
+ var TEMPLATE_BRAND_SNAPSHOT = typeof document === "undefined" ? null : withOverrideStripped(deriveTemplateBrandLive);
2878
+ var TEMPLATE_FONTS_SNAPSHOT = typeof document === "undefined" ? null : withOverrideStripped(deriveTemplateFontsLive);
2879
+ function deriveTemplateBrand() {
2880
+ return TEMPLATE_BRAND_SNAPSHOT;
2881
+ }
2882
+ function deriveTemplateFonts() {
2883
+ return TEMPLATE_FONTS_SNAPSHOT;
2884
+ }
2885
+ function deriveTemplateButtonStyle() {
2042
2886
  if (typeof document === "undefined") return null;
2043
- const btn = document.querySelector('[data-ohw-role="button"]');
2887
+ const candidates = Array.from(
2888
+ document.querySelectorAll('[data-ohw-role="button"]')
2889
+ ).filter(
2890
+ (el) => !el.closest(`[${CONTAINER_ATTR}]`) && // Whole-card links legitimately carry the button role (serene's clickable treatment
2891
+ // cards) but must never donate the "button look" — a real button holds at most its
2892
+ // one editable label, a card holds a headline, copy and prices.
2893
+ el.querySelectorAll("[data-ohw-editable]").length <= 1
2894
+ );
2895
+ const isFilled = (el) => {
2896
+ const bg = getComputedStyle(el).backgroundColor;
2897
+ if (!bg || bg === "transparent") return false;
2898
+ const alpha = bg.match(/rgba?\([^)]*,\s*([\d.]+)\)$/);
2899
+ return !alpha || parseFloat(alpha[1]) > 0;
2900
+ };
2901
+ const btn = candidates.find(isFilled) ?? candidates[0];
2044
2902
  if (!btn) return null;
2045
- const radius = getComputedStyle(btn).borderTopLeftRadius;
2046
- return radius || null;
2903
+ const cs = getComputedStyle(btn);
2904
+ const filled = isFilled(btn);
2905
+ const corners = [
2906
+ cs.borderTopLeftRadius,
2907
+ cs.borderTopRightRadius,
2908
+ cs.borderBottomRightRadius,
2909
+ cs.borderBottomLeftRadius
2910
+ ].map((v) => v || "0px");
2911
+ const radius = corners.every((v) => v === corners[0]) ? corners[0] : corners.join(" ");
2912
+ const px = (v) => parseFloat(v) || 0;
2913
+ const lineHeight = px(cs.lineHeight) || px(cs.fontSize) * 1.2;
2914
+ const contentH = btn.getBoundingClientRect().height - px(cs.borderTopWidth) - px(cs.borderBottomWidth);
2915
+ const impliedY = Math.round(Math.max(0, (contentH - lineHeight) / 2));
2916
+ const padY = Math.max(px(cs.paddingTop), px(cs.paddingBottom), impliedY);
2917
+ const padX = Math.max(px(cs.paddingLeft), px(cs.paddingRight));
2918
+ return {
2919
+ radius: radius || "10px",
2920
+ padding: `${padY}px ${padX}px`,
2921
+ ...filled ? { background: cs.backgroundColor, color: cs.color } : {},
2922
+ fontFamily: cs.fontFamily || "",
2923
+ fontSize: cs.fontSize || "",
2924
+ fontWeight: cs.fontWeight || "",
2925
+ letterSpacing: cs.letterSpacing || "",
2926
+ textTransform: cs.textTransform || ""
2927
+ };
2047
2928
  }
2048
2929
  var mounted = /* @__PURE__ */ new Map();
2049
2930
  function findTemplateSection(id) {
@@ -2059,6 +2940,19 @@ function findPlacementAnchor(id, exclude) {
2059
2940
  for (const el of document.querySelectorAll(`[data-ohw-section="${CSS.escape(id)}"]`)) {
2060
2941
  if (el === exclude) continue;
2061
2942
  if (el.parentElement?.closest("[data-ohw-section]")) continue;
2943
+ if (isChromeSection2(el)) return null;
2944
+ return el;
2945
+ }
2946
+ return null;
2947
+ }
2948
+ function findFooterSection() {
2949
+ const byId = findTemplateSection("footer");
2950
+ if (byId) return byId;
2951
+ for (const el of Array.from(
2952
+ document.querySelectorAll("footer[data-ohw-section]")
2953
+ ).reverse()) {
2954
+ if (el.hasAttribute(CONTAINER_ATTR)) continue;
2955
+ if (el.parentElement?.closest("[data-ohw-section]")) continue;
2062
2956
  return el;
2063
2957
  }
2064
2958
  return null;
@@ -2085,7 +2979,7 @@ function placeContainer(container, entry) {
2085
2979
  return;
2086
2980
  }
2087
2981
  }
2088
- const footer = findTemplateSection("footer");
2982
+ const footer = findFooterSection();
2089
2983
  if (footer) {
2090
2984
  footer.insertAdjacentElement("beforebegin", container);
2091
2985
  } else {
@@ -2094,18 +2988,18 @@ function placeContainer(container, entry) {
2094
2988
  }
2095
2989
  function syncRemovedSections(state) {
2096
2990
  const removed = new Set(state.removed ?? []);
2097
- for (const el of document.querySelectorAll(`[${REMOVED_ATTR}]`)) {
2991
+ for (const el of document.querySelectorAll(`[${REMOVED_ATTR2}]`)) {
2098
2992
  const id = el.getAttribute("data-ohw-section") ?? "";
2099
2993
  if (!removed.has(id)) {
2100
2994
  el.style.removeProperty("display");
2101
- el.removeAttribute(REMOVED_ATTR);
2995
+ el.removeAttribute(REMOVED_ATTR2);
2102
2996
  }
2103
2997
  }
2104
2998
  for (const id of removed) {
2105
2999
  const section = findTemplateSection(id);
2106
3000
  if (section && !section.hasAttribute(REPLACED_ATTR)) {
2107
3001
  section.style.display = "none";
2108
- section.setAttribute(REMOVED_ATTR, "");
3002
+ section.setAttribute(REMOVED_ATTR2, "");
2109
3003
  }
2110
3004
  }
2111
3005
  }
@@ -2120,9 +3014,9 @@ function syncTemplateHidden(state, pageHasSections) {
2120
3014
  if (!hide) return;
2121
3015
  for (const el of Array.from(document.querySelectorAll("[data-ohw-section]"))) {
2122
3016
  if (el.hasAttribute(CONTAINER_ATTR)) continue;
2123
- if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) continue;
3017
+ if (isChromeSection2(el)) continue;
2124
3018
  if (el.parentElement?.closest("[data-ohw-section]")) continue;
2125
- if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR)) continue;
3019
+ if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR2)) continue;
2126
3020
  el.style.display = "none";
2127
3021
  el.setAttribute(TEMPLATE_HIDDEN_ATTR, "");
2128
3022
  }
@@ -2146,18 +3040,23 @@ function syncReplacedOriginals(state) {
2146
3040
  }
2147
3041
  }
2148
3042
  var sectionOrderIndex = /* @__PURE__ */ new Map();
3043
+ var removedSectionIds = /* @__PURE__ */ new Set();
2149
3044
  function setAiSectionOrder(raw, currentPath) {
2150
3045
  const next = /* @__PURE__ */ new Map();
3046
+ const removed = /* @__PURE__ */ new Set();
2151
3047
  if (raw) {
2152
3048
  try {
2153
3049
  const entries = JSON.parse(raw);
2154
3050
  for (const entry of entries) {
2155
- if (!entry.pagePath || entry.pagePath === currentPath) next.set(entry.instanceId, entry.order);
3051
+ if (entry.pagePath && entry.pagePath !== currentPath) continue;
3052
+ next.set(entry.instanceId, entry.order);
3053
+ if (entry.removed) removed.add(entry.instanceId);
2156
3054
  }
2157
3055
  } catch {
2158
3056
  }
2159
3057
  }
2160
3058
  sectionOrderIndex = next;
3059
+ removedSectionIds = removed;
2161
3060
  }
2162
3061
  function applyExplicitOrder(entries) {
2163
3062
  if (sectionOrderIndex.size === 0) return entries;
@@ -2193,11 +3092,23 @@ function orderByChain(sections) {
2193
3092
  for (const root of roots) visit(root);
2194
3093
  return out.length === sections.length ? out : sections;
2195
3094
  }
3095
+ function syncSoftRemovedGenerated() {
3096
+ for (const [id, section] of mounted) {
3097
+ const el = section.container;
3098
+ if (removedSectionIds.has(id)) {
3099
+ el.style.display = "none";
3100
+ el.setAttribute(REMOVED_ATTR, "");
3101
+ } else if (el.hasAttribute(REMOVED_ATTR)) {
3102
+ el.style.removeProperty("display");
3103
+ el.removeAttribute(REMOVED_ATTR);
3104
+ }
3105
+ }
3106
+ }
2196
3107
  function applyAiSectionsToDom(state, options) {
2197
3108
  if (typeof document === "undefined") return;
2198
3109
  const brandOverride = deriveBrandOverride();
2199
3110
  const templateBrand = deriveTemplateBrand();
2200
- const templateButtonRadius = deriveTemplateButtonRadius();
3111
+ const templateButtonStyle = deriveTemplateButtonStyle();
2201
3112
  const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
2202
3113
  const pagePath = window.location.pathname;
2203
3114
  const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
@@ -2237,7 +3148,7 @@ function applyAiSectionsToDom(state, options) {
2237
3148
  {
2238
3149
  tree: entry.tree,
2239
3150
  brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
2240
- buttonRadius: templateButtonRadius,
3151
+ buttonStyle: templateButtonStyle,
2241
3152
  resolveMedia,
2242
3153
  editKeyPrefix: `ai.${entry.id}`
2243
3154
  }
@@ -2260,6 +3171,7 @@ function applyAiSectionsToDom(state, options) {
2260
3171
  syncReplacedOriginals(state);
2261
3172
  syncRemovedSections(state);
2262
3173
  syncTemplateHidden(state, pageSections.length > 0);
3174
+ syncSoftRemovedGenerated();
2263
3175
  }
2264
3176
 
2265
3177
  // src/useLinkHrefGuardian.ts
@@ -6451,12 +7363,12 @@ var cva = (base, config) => (props) => {
6451
7363
  var import_radix_ui2 = require("radix-ui");
6452
7364
  var import_jsx_runtime5 = require("react/jsx-runtime");
6453
7365
  var toggleVariants = cva(
6454
- "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",
7366
+ "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",
6455
7367
  {
6456
7368
  variants: {
6457
7369
  variant: {
6458
7370
  default: "bg-transparent border-0",
6459
- outline: "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground"
7371
+ outline: "border border-input bg-transparent shadow-xs hover:bg-bridge-accent hover:text-accent-foreground"
6460
7372
  },
6461
7373
  size: {
6462
7374
  default: "px-2 py-1",
@@ -6549,10 +7461,10 @@ var DragHandle = React4.forwardRef(
6549
7461
  type,
6550
7462
  "data-slot": "drag-handle",
6551
7463
  className: cn(
6552
- "inline-flex h-7 w-4 shrink-0 items-center justify-center rounded-md transition-all duration-200 max-h-[30px]",
7464
+ "inline-flex h-7 w-4 shrink-0 items-center justify-center rounded-md transition-all duration-200 max-h-7.5",
6553
7465
  "bg-white border border-transparent text-stone-500 shadow-md cursor-grab",
6554
7466
  "enabled:hover:border enabled:hover:border-stone-200 enabled:hover:text-stone-950",
6555
- "enabled:active:border enabled:active:border-primary enabled:active:bg-primary-50 enabled:active:text-stone-950 enabled:active:shadow enabled:active:cursor-grabbing",
7467
+ "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",
6556
7468
  "disabled:cursor-not-allowed disabled:opacity-40 disabled:text-stone-950 disabled:pointer-events-none",
6557
7469
  className
6558
7470
  ),
@@ -6574,7 +7486,7 @@ var CustomToolbar = React5.forwardRef(({ className, onMouseDown, ...props }, ref
6574
7486
  "data-ohw-toolbar": "",
6575
7487
  className: cn(
6576
7488
  // Figma: bg background, radius 8, gap-1, p-0.5, shadow-md — no border
6577
- "inline-flex h-8 items-center gap-1 rounded-[var(--radius,0.5rem)] bg-background p-0.5 font-sans whitespace-nowrap shadow-md",
7489
+ "inline-flex h-8 items-center gap-1 rounded-(--radius,0.5rem) bg-background p-0.5 font-sans whitespace-nowrap shadow-md",
6578
7490
  className
6579
7491
  ),
6580
7492
  onMouseDown: (e) => {
@@ -6603,7 +7515,7 @@ var CustomToolbarButton = React5.forwardRef(
6603
7515
  type,
6604
7516
  className: cn(
6605
7517
  "inline-flex size-7 shrink-0 items-center justify-center rounded-[calc(var(--radius,0.5rem)-2px)] text-foreground transition-colors",
6606
- active ? "bg-primary text-primary-foreground" : "bg-transparent hover:bg-muted disabled:cursor-not-allowed disabled:text-muted-foreground disabled:opacity-60",
7518
+ active ? "bg-bridge-primary text-primary-foreground" : "bg-transparent hover:bg-muted disabled:cursor-not-allowed disabled:text-muted-foreground disabled:opacity-60",
6607
7519
  className
6608
7520
  ),
6609
7521
  ...props
@@ -6808,9 +7720,7 @@ function ItemActionToolbar({
6808
7720
  ToggleGroup,
6809
7721
  {
6810
7722
  value: dropdownOpen ? "open" : "closed",
6811
- onValueChange: (value) => {
6812
- if (value !== "open" && value !== "closed") return;
6813
- onDropdownOpenChange?.(value === "open");
7723
+ onValueChange: () => {
6814
7724
  },
6815
7725
  className: "h-7 gap-0.5 rounded-[calc(var(--radius,0.5rem)-2px)] bg-muted p-0.5",
6816
7726
  onMouseDown: (e) => {
@@ -6825,6 +7735,11 @@ function ItemActionToolbar({
6825
7735
  size: "sm",
6826
7736
  "aria-label": "Closed",
6827
7737
  className: "h-6 min-w-0 px-2 text-xs font-medium",
7738
+ onMouseDown: (e) => {
7739
+ e.preventDefault();
7740
+ e.stopPropagation();
7741
+ if (dropdownOpen !== false) onDropdownOpenChange?.(false);
7742
+ },
6828
7743
  children: "Closed"
6829
7744
  }
6830
7745
  ),
@@ -6835,6 +7750,11 @@ function ItemActionToolbar({
6835
7750
  size: "sm",
6836
7751
  "aria-label": "Open",
6837
7752
  className: "h-6 min-w-0 px-2 text-xs font-medium",
7753
+ onMouseDown: (e) => {
7754
+ e.preventDefault();
7755
+ e.stopPropagation();
7756
+ if (dropdownOpen !== true) onDropdownOpenChange?.(true);
7757
+ },
6838
7758
  children: "Open"
6839
7759
  }
6840
7760
  )
@@ -7797,13 +8717,13 @@ function FormFieldToolbar({
7797
8717
  ]
7798
8718
  }
7799
8719
  ) }),
7800
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-[190px] p-1", children: FIELD_TYPES.map((entry) => {
8720
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-47.5 p-1", children: FIELD_TYPES.map((entry) => {
7801
8721
  const Icon = TYPE_ICONS[entry.type];
7802
8722
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
7803
8723
  DropdownMenuItem,
7804
8724
  {
7805
8725
  onSelect: () => onTypeChange(entry.type),
7806
- className: "rounded-md py-2 text-[13px] " + (entry.type === type ? "bg-primary/10" : ""),
8726
+ className: "rounded-md py-2 text-[13px] " + (entry.type === type ? "bg-bridge-primary/10" : ""),
7807
8727
  children: [
7808
8728
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Icon, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
7809
8729
  entry.label
@@ -7820,7 +8740,7 @@ function FormFieldToolbar({
7820
8740
  type: "button",
7821
8741
  "aria-pressed": required,
7822
8742
  onClick: onRequiredToggle,
7823
- 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"),
8743
+ 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"),
7824
8744
  "data-ohw-field-required-toggle": "",
7825
8745
  children: [
7826
8746
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.Asterisk, { size: 14, strokeWidth: 2, "aria-hidden": true }),
@@ -7840,7 +8760,7 @@ function FormFieldToolbar({
7840
8760
  children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.MoreHorizontal, { size: 15, "aria-hidden": true })
7841
8761
  }
7842
8762
  ) }),
7843
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-[170px] p-1", children: [
8763
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-42.5 p-1", children: [
7844
8764
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DropdownMenuItem, { onSelect: onDuplicate, className: "rounded-md py-2 text-[13px]", children: [
7845
8765
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.Copy, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
7846
8766
  "Duplicate"
@@ -7860,7 +8780,7 @@ function FieldTypePicker({ onPick }) {
7860
8780
  "div",
7861
8781
  {
7862
8782
  "data-ohw-field-type-picker": "",
7863
- className: "pointer-events-auto grid w-[420px] grid-cols-3 gap-3 rounded-xl border border-border bg-background p-4 shadow-lg",
8783
+ className: "pointer-events-auto grid w-105 grid-cols-3 gap-3 rounded-xl border border-border bg-background p-4 shadow-lg",
7864
8784
  children: FIELD_TYPES.map((entry) => {
7865
8785
  const Icon = TYPE_ICONS[entry.type];
7866
8786
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
@@ -7868,7 +8788,7 @@ function FieldTypePicker({ onPick }) {
7868
8788
  {
7869
8789
  type: "button",
7870
8790
  onClick: () => onPick(entry.type),
7871
- 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",
8791
+ 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",
7872
8792
  children: [
7873
8793
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Icon, { size: 26, strokeWidth: 1.5, "aria-hidden": true }),
7874
8794
  entry.label
@@ -7894,7 +8814,7 @@ var buttonVariants = cva(
7894
8814
  {
7895
8815
  variants: {
7896
8816
  variant: {
7897
- default: "bg-primary text-primary-foreground hover:opacity-90",
8817
+ default: "bg-bridge-primary text-primary-foreground hover:opacity-90",
7898
8818
  outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
7899
8819
  ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
7900
8820
  },
@@ -7989,6 +8909,7 @@ function MediaOverlay({
7989
8909
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
7990
8910
  );
7991
8911
  }, [isVideo]);
8912
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
7992
8913
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
7993
8914
  const box = {
7994
8915
  position: "fixed",
@@ -8094,8 +9015,8 @@ function MediaOverlay({
8094
9015
  pointerEvents: hover.hasTextOverlap ? "none" : "auto",
8095
9016
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
8096
9017
  // than hovered. Hover keeps the existing tinted preview.
8097
- boxShadow: selected ? "inset 0 0 0 2px var(--color-primary)" : "inset 0 0 0 1.5px var(--color-primary)",
8098
- background: selected ? "transparent" : "color-mix(in srgb, var(--color-primary) 20%, transparent)"
9018
+ boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
9019
+ background: selected ? "transparent" : "color-mix(in srgb, var(--color-bridge-primary) 20%, transparent)"
8099
9020
  },
8100
9021
  onClick: () => onSelect && !selected ? onSelect(hover.key) : onReplace(hover.key),
8101
9022
  children: [
@@ -8118,17 +9039,17 @@ function MediaOverlay({
8118
9039
  },
8119
9040
  children: [
8120
9041
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
8121
- isVideo ? "Replace video" : "Replace image"
9042
+ replaceLabel
8122
9043
  ]
8123
9044
  }
8124
9045
  ),
8125
- replaceMode === "none" ? null : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
9046
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8126
9047
  Button,
8127
9048
  {
8128
9049
  "data-ohw-media-overlay": "",
8129
9050
  variant: "outline",
8130
9051
  size: "sm",
8131
- "aria-label": isVideo ? "Replace video" : "Replace image",
9052
+ "aria-label": replaceLabel,
8132
9053
  className: "gap-1.5 cursor-pointer hover:bg-background",
8133
9054
  style: {
8134
9055
  ...OVERLAY_BUTTON_STYLE,
@@ -8151,7 +9072,7 @@ function MediaOverlay({
8151
9072
  },
8152
9073
  children: [
8153
9074
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
8154
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
9075
+ replaceMode === "full" ? replaceLabel : null
8155
9076
  ]
8156
9077
  }
8157
9078
  )
@@ -8189,8 +9110,8 @@ function CarouselOverlay({
8189
9110
  height: rect.height,
8190
9111
  zIndex: 2147483646,
8191
9112
  pointerEvents: "auto",
8192
- boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
8193
- background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
9113
+ boxShadow: "inset 0 0 0 1.5px var(--color-bridge-primary)",
9114
+ background: "color-mix(in srgb, var(--color-bridge-primary) 20%, transparent)"
8194
9115
  },
8195
9116
  onClick: () => onEdit(hover.key),
8196
9117
  children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
@@ -8210,229 +9131,17 @@ function CarouselOverlay({
8210
9131
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react6.GalleryHorizontal, { size: 14 }),
8211
9132
  "Edit gallery"
8212
9133
  ]
8213
- }
8214
- )
8215
- }
8216
- );
8217
- }
8218
-
8219
- // src/ui/ai-section/AiSectionOverlay.tsx
8220
- var import_react8 = require("react");
8221
- var import_lucide_react7 = require("lucide-react");
8222
-
8223
- // src/lib/sections.ts
8224
- var LINK_PICKER_EXCLUDED_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
8225
- function isChromeSection(el) {
8226
- return el.matches("header, nav, footer, aside");
8227
- }
8228
- function titleCaseSectionId(id) {
8229
- return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
8230
- }
8231
- function parseSectionsFromRoot(root) {
8232
- const seen = /* @__PURE__ */ new Set();
8233
- const sections = [];
8234
- for (const el of root.querySelectorAll("[data-ohw-section]")) {
8235
- const id = el.getAttribute("data-ohw-section") ?? "";
8236
- if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
8237
- if (el.parentElement?.closest("[data-ohw-section]")) continue;
8238
- if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
8239
- continue;
8240
- seen.add(id);
8241
- const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
8242
- sections.push({ id, label });
8243
- }
8244
- return sections;
8245
- }
8246
- function collectSectionsFromDom() {
8247
- if (typeof document === "undefined") return [];
8248
- return parseSectionsFromRoot(document);
8249
- }
8250
- function parseSectionsFromHtml(html) {
8251
- const doc = new DOMParser().parseFromString(html, "text/html");
8252
- return parseSectionsFromRoot(doc);
8253
- }
8254
-
8255
- // src/lib/section-instances.ts
8256
- var SECTION_ORDER_KEY = "__ohw_section_order";
8257
- var REMOVED_ATTR2 = "data-ohw-section-removed";
8258
- function isRemovedSection(el) {
8259
- return el.hasAttribute(REMOVED_ATTR2);
8260
- }
8261
- function topLevelSections() {
8262
- return Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
8263
- (el) => !el.parentElement?.closest("[data-ohw-section]") && !isChromeSection(el) && !isRemovedSection(el)
8264
- );
8265
- }
8266
- function instanceIdOf(el) {
8267
- return el.dataset.ohwInstance ?? el.dataset.ohwSection ?? "";
8268
- }
8269
- function findByInstanceId(instanceId) {
8270
- const escapedId = CSS.escape(instanceId);
8271
- return document.querySelector(`[data-ohw-instance="${escapedId}"]`) ?? document.querySelector(`[data-ohw-section="${escapedId}"]:not([data-ohw-instance])`);
8272
- }
8273
- function planSectionMove(instanceId, targetIndex, currentPath) {
8274
- const sections = topLevelSections();
8275
- const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
8276
- if (index === -1) return null;
8277
- const dragged = sections[index];
8278
- const others = sections.filter((_, i) => i !== index);
8279
- const clamped = Math.max(0, Math.min(targetIndex, others.length));
8280
- const reordered = [...others.slice(0, clamped), dragged, ...others.slice(clamped)];
8281
- return reordered.map((el, order) => ({
8282
- instanceId: instanceIdOf(el),
8283
- type: el.getAttribute("data-ohw-section") ?? "",
8284
- order,
8285
- pagePath: currentPath
8286
- }));
8287
- }
8288
- function moveSectionInstance(instanceId, direction, currentPath) {
8289
- const sections = topLevelSections();
8290
- const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
8291
- if (index === -1) return null;
8292
- const siblingIndex = direction === "up" ? index - 1 : index + 1;
8293
- if (siblingIndex < 0 || siblingIndex >= sections.length) return null;
8294
- const entries = planSectionMove(instanceId, siblingIndex, currentPath);
8295
- if (!entries) return null;
8296
- applyPersistedOrder(entries);
8297
- return entries;
8298
- }
8299
- function syncRemovedFlags(entries) {
8300
- const removedIds = new Set(entries.filter((e) => e.removed).map((e) => e.instanceId));
8301
- document.querySelectorAll(`[${REMOVED_ATTR2}]`).forEach((el) => {
8302
- if (!removedIds.has(instanceIdOf(el))) {
8303
- el.style.removeProperty("display");
8304
- el.removeAttribute(REMOVED_ATTR2);
8305
- }
8306
- });
8307
- for (const id of removedIds) {
8308
- const el = findByInstanceId(id);
8309
- if (el) {
8310
- el.style.display = "none";
8311
- el.setAttribute(REMOVED_ATTR2, "");
8312
- }
8313
- }
8314
- }
8315
- function applyPersistedOrder(entries) {
8316
- syncRemovedFlags(entries);
8317
- if (entries.length === 0) return;
8318
- const sections = topLevelSections();
8319
- if (sections.length === 0) return;
8320
- const orderIndex = new Map(entries.map((e) => [e.instanceId, e.order]));
8321
- const ordered = [...sections].sort((a, b) => {
8322
- const aOrder = orderIndex.get(instanceIdOf(a));
8323
- const bOrder = orderIndex.get(instanceIdOf(b));
8324
- if (aOrder === void 0 && bOrder === void 0) return 0;
8325
- if (aOrder === void 0) return 1;
8326
- if (bOrder === void 0) return -1;
8327
- return aOrder - bOrder;
8328
- });
8329
- let prev = null;
8330
- for (const el of ordered) {
8331
- if (prev) prev.after(el);
8332
- prev = el;
8333
- }
8334
- }
8335
- function setSectionRemoved(instanceId, currentPath, existingEntries, removed) {
8336
- if (!findByInstanceId(instanceId)) return null;
8337
- const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
8338
- const allSections = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
8339
- (el) => !el.parentElement?.closest("[data-ohw-section]") && !isChromeSection(el)
8340
- );
8341
- allSections.forEach((el, order) => {
8342
- const id = instanceIdOf(el);
8343
- if (!byId.has(id)) {
8344
- byId.set(id, { instanceId: id, type: el.getAttribute("data-ohw-section") ?? "", order, pagePath: currentPath });
8345
- }
8346
- });
8347
- const target = byId.get(instanceId);
8348
- if (!target) return null;
8349
- byId.set(instanceId, { ...target, removed });
8350
- const entries = Array.from(byId.values());
8351
- applyPersistedOrder(entries);
8352
- return entries;
8353
- }
8354
- function deleteSectionInstance(instanceId, currentPath, existingEntries) {
8355
- return setSectionRemoved(instanceId, currentPath, existingEntries, true);
8356
- }
8357
- function restoreSectionInstance(instanceId, currentPath, existingEntries) {
8358
- return setSectionRemoved(instanceId, currentPath, existingEntries, false);
8359
- }
8360
- function duplicateSectionInstance(instanceId, newId, currentPath, existingEntries) {
8361
- const original = findByInstanceId(instanceId);
8362
- if (!original) return null;
8363
- const clone = original.cloneNode(true);
8364
- clone.setAttribute("data-ohw-instance", newId);
8365
- const keyRekeys = rekeySectionSubtree(clone, newId);
8366
- original.insertAdjacentElement("afterend", clone);
8367
- const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
8368
- const entries = topLevelSections().map((el, order) => {
8369
- const id = instanceIdOf(el);
8370
- return {
8371
- instanceId: id,
8372
- type: el.getAttribute("data-ohw-section") ?? "",
8373
- order,
8374
- pagePath: currentPath,
8375
- ...byId.get(id)?.removed ? { removed: true } : {}
8376
- };
8377
- });
8378
- applyPersistedOrder(entries);
8379
- return { entries, keyRekeys };
8380
- }
8381
- function newInstanceId() {
8382
- return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `instance-${Date.now()}-${Math.random().toString(36).slice(2)}`;
8383
- }
8384
- function getPageSectionOrderEntries(raw, currentPath) {
8385
- if (!raw) return [];
8386
- try {
8387
- const entries = JSON.parse(raw);
8388
- return entries.filter((e) => !e.pagePath || e.pagePath === currentPath);
8389
- } catch {
8390
- return [];
8391
- }
8392
- }
8393
- function rekeySectionSubtree(root, instanceId) {
8394
- const suffix = `::${instanceId}`;
8395
- const pairs = [];
8396
- const rekey = (el, attr) => {
8397
- const current = el.getAttribute(attr);
8398
- if (!current) return;
8399
- const base = current.includes("::") ? current.slice(0, current.indexOf("::")) : current;
8400
- const next = `${base}${suffix}`;
8401
- el.setAttribute(attr, next);
8402
- pairs.push({ from: current, to: next });
8403
- };
8404
- if (root.hasAttribute("data-ohw-key")) rekey(root, "data-ohw-key");
8405
- if (root.hasAttribute("data-ohw-href-key")) rekey(root, "data-ohw-href-key");
8406
- root.querySelectorAll("[data-ohw-key]").forEach((el) => rekey(el, "data-ohw-key"));
8407
- root.querySelectorAll("[data-ohw-href-key]").forEach((el) => rekey(el, "data-ohw-href-key"));
8408
- return pairs;
8409
- }
8410
- function initSectionInstancesFromContent(content, currentPath) {
8411
- document.querySelectorAll("[data-ohw-section]:not([data-ohw-instance])").forEach((el) => {
8412
- el.setAttribute("data-ohw-instance", el.getAttribute("data-ohw-section") ?? "");
8413
- });
8414
- const entries = getPageSectionOrderEntries(content[SECTION_ORDER_KEY], currentPath);
8415
- for (const entry of entries) {
8416
- if (entry.instanceId === entry.type) continue;
8417
- if (document.querySelector(`[data-ohw-instance="${CSS.escape(entry.instanceId)}"]`)) continue;
8418
- const original = document.querySelector(
8419
- `[data-ohw-section="${CSS.escape(entry.type)}"][data-ohw-instance="${CSS.escape(entry.type)}"]`
8420
- );
8421
- if (!original) continue;
8422
- const clone = original.cloneNode(true);
8423
- clone.setAttribute("data-ohw-instance", entry.instanceId);
8424
- rekeySectionSubtree(clone, entry.instanceId);
8425
- original.insertAdjacentElement("afterend", clone);
8426
- }
8427
- applyPersistedOrder(entries);
9134
+ }
9135
+ )
9136
+ }
9137
+ );
8428
9138
  }
8429
9139
 
8430
9140
  // src/ui/ai-section/AiSectionOverlay.tsx
9141
+ var import_react8 = require("react");
9142
+ var import_lucide_react7 = require("lucide-react");
8431
9143
  var import_jsx_runtime17 = require("react/jsx-runtime");
8432
- function findSectionElement(instanceId) {
8433
- const escaped = CSS.escape(instanceId);
8434
- return document.querySelector(`[data-ohw-instance="${escaped}"]`) ?? document.querySelector(`[data-ohw-section="${escaped}"]:not([data-ohw-instance])`);
8435
- }
9144
+ var findSectionElement = findByInstanceId;
8436
9145
  function readRect(instanceId) {
8437
9146
  const el = findSectionElement(instanceId);
8438
9147
  if (!el) return null;
@@ -8472,7 +9181,7 @@ function useLiveSectionRect(sectionId) {
8472
9181
  }
8473
9182
  function computeSectionBoundaryFlags(instanceId) {
8474
9183
  const topLevel = topLevelSections();
8475
- const index = topLevel.findIndex((el) => (el.dataset.ohwInstance ?? el.dataset.ohwSection) === instanceId);
9184
+ const index = topLevel.findIndex((el) => instanceIdOf(el) === instanceId);
8476
9185
  if (index === -1) return { isFirst: true, isLast: true };
8477
9186
  return { isFirst: index === 0, isLast: index === topLevel.length - 1 };
8478
9187
  }
@@ -8556,18 +9265,20 @@ function AiSectionOverlay({
8556
9265
  selectedIdRef.current = selectedId;
8557
9266
  const report = (0, import_react8.useCallback)(
8558
9267
  (el) => {
9268
+ const labelSrc = el ? sectionElementOf(el) : null;
8559
9269
  postToParent2({
8560
9270
  type: "ow:section-selected",
8561
- sectionId: el ? el.dataset.ohwInstance ?? el.dataset.ohwSection ?? null : null,
8562
- sectionLabel: el ? el.dataset.ohwSectionLabel ?? titleCaseSectionId(el.dataset.ohwSection ?? "") : null
9271
+ sectionId: el ? instanceIdOf(el) || null : null,
9272
+ sectionLabel: labelSrc ? labelSrc.dataset.ohwSectionLabel ?? titleCaseSectionId(labelSrc.dataset.ohwSection ?? "") : null
8563
9273
  });
8564
9274
  },
8565
9275
  [postToParent2]
8566
9276
  );
8567
9277
  const selectFromElement = (0, import_react8.useCallback)(
8568
9278
  (el, options) => {
8569
- const sectionEl = el?.closest("[data-ohw-section]") ?? null;
8570
- const id = sectionEl ? sectionEl.dataset.ohwInstance ?? sectionEl.dataset.ohwSection ?? null : null;
9279
+ const inner = el?.closest("[data-ohw-section]") ?? null;
9280
+ const sectionEl = inner ? movableUnit(inner) : null;
9281
+ const id = sectionEl ? instanceIdOf(sectionEl) || null : null;
8571
9282
  if (id === selectedIdRef.current) return;
8572
9283
  setSelectedId(id);
8573
9284
  if (options?.report !== false) report(sectionEl);
@@ -8633,7 +9344,8 @@ function AiSectionOverlay({
8633
9344
  return;
8634
9345
  }
8635
9346
  const sec = t.closest("[data-ohw-section]");
8636
- setHoveredId(sec ? sec.dataset.ohwInstance ?? sec.dataset.ohwSection ?? null : null);
9347
+ const unit = sec ? movableUnit(sec) : null;
9348
+ setHoveredId(unit ? instanceIdOf(unit) || null : null);
8637
9349
  };
8638
9350
  const onLeave = () => setHoveredId(null);
8639
9351
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -9237,7 +9949,7 @@ function SectionTreeItem({
9237
9949
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
9238
9950
  "div",
9239
9951
  {
9240
- className: "mr-[-1px] h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
9952
+ className: "-mr-px h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
9241
9953
  "aria-hidden": true
9242
9954
  }
9243
9955
  ),
@@ -9256,7 +9968,7 @@ function SectionTreeItem({
9256
9968
  className: cn(
9257
9969
  "flex h-9 min-w-0 flex-1 items-center gap-2 rounded-md border border-border bg-background p-3",
9258
9970
  interactive && "cursor-pointer hover:bg-muted/30",
9259
- interactive && selected && "border-primary"
9971
+ interactive && selected && "border-bridge-primary"
9260
9972
  ),
9261
9973
  children: [
9262
9974
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
@@ -9386,7 +10098,7 @@ function UrlOrPageInput({
9386
10098
  };
9387
10099
  const fieldClassName = cn(
9388
10100
  "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]",
9389
- urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
10101
+ 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"
9390
10102
  );
9391
10103
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex w-full flex-col gap-2 p-0", children: [
9392
10104
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
@@ -10430,6 +11142,32 @@ function getNavbarDesktopContainer() {
10430
11142
  function getNavbarDrawerContainer() {
10431
11143
  return document.querySelector("[data-ohw-nav-drawer]");
10432
11144
  }
11145
+ function isNavbarListContainerVisible(el) {
11146
+ if (el.hasAttribute("hidden")) return false;
11147
+ if (el.getClientRects().length === 0) return false;
11148
+ const style = window.getComputedStyle(el);
11149
+ return style.display !== "none" && style.visibility !== "hidden";
11150
+ }
11151
+ function getActiveNavbarListContainer(draggedEl) {
11152
+ const desktop = getNavbarDesktopContainer();
11153
+ const drawer = getNavbarDrawerContainer();
11154
+ if (draggedEl) {
11155
+ if (drawer?.contains(draggedEl)) return drawer;
11156
+ if (desktop?.contains(draggedEl)) return desktop;
11157
+ }
11158
+ if (desktop && isNavbarListContainerVisible(desktop)) return desktop;
11159
+ if (drawer && isNavbarListContainerVisible(drawer)) return drawer;
11160
+ return desktop ?? drawer;
11161
+ }
11162
+ function listActiveNavbarItems(draggedEl) {
11163
+ const container = getActiveNavbarListContainer(draggedEl);
11164
+ if (container) {
11165
+ return Array.from(container.querySelectorAll("[data-ohw-href-key]")).filter(
11166
+ isNavbarLinkItem
11167
+ );
11168
+ }
11169
+ return listNavbarItems();
11170
+ }
10433
11171
  function listNavbarItems() {
10434
11172
  const desktop = getNavbarDesktopContainer();
10435
11173
  if (desktop) {
@@ -11542,20 +12280,23 @@ function isEmptyLabelValue(value) {
11542
12280
  function applyStoredValues(item, content) {
11543
12281
  const hrefKey = socialHrefKey(item);
11544
12282
  const iconKey = socialIconKey(item);
12283
+ const baseKey = hrefKey?.replace(/-href$/, "");
11545
12284
  if (hrefKey && content[hrefKey] !== void 0) item.setAttribute("href", content[hrefKey]);
11546
- if (iconKey) {
12285
+ const glyphKey = iconKey ?? baseKey ?? null;
12286
+ const glyphStored = glyphKey ? content[glyphKey]?.trim() : void 0;
12287
+ if (glyphStored) {
12288
+ ensureIconSlot(item);
11547
12289
  const glyph = item.querySelector(ICON_SELECTOR);
11548
- if (glyph && content[iconKey]) {
11549
- applyIconMarkup(glyph, content[iconKey]);
12290
+ if (glyph) {
12291
+ applyIconMarkup(glyph, glyphStored);
11550
12292
  glyph.removeAttribute(SOCIALS_ICON_PLACEHOLDER_ATTR);
11551
12293
  }
11552
- const label = item.querySelector(`[${SOCIALS_LABEL_ATTR}]`);
11553
- label?.setAttribute("data-ohw-key", socialLabelKey(iconKey));
11554
- const stored = content[socialLabelKey(iconKey)];
11555
- if (label && stored !== void 0) {
11556
- const words = isEmptyLabelValue(stored) ? "" : stored;
11557
- if (label.innerHTML !== words) label.innerHTML = words;
11558
- }
12294
+ }
12295
+ const label = socialLabelElement(item);
12296
+ const labelKey = label?.getAttribute("data-ohw-key");
12297
+ if (label && labelKey && content[labelKey] !== void 0) {
12298
+ const words = isEmptyLabelValue(content[labelKey]) ? "" : content[labelKey];
12299
+ if (label.innerHTML !== words) label.innerHTML = words;
11559
12300
  }
11560
12301
  }
11561
12302
  function socialPlatformKey(iconKey) {
@@ -11644,6 +12385,9 @@ function insertSocialItem(row, after, content = {}, { placeholder = true, keys }
11644
12385
  if (placeholder) {
11645
12386
  const label = socialLabelElement(item);
11646
12387
  if (label) label.textContent = PLACEHOLDER_SOCIAL_LABEL;
12388
+ } else if (keys) {
12389
+ const label = socialLabelElement(item);
12390
+ if (label) label.textContent = "";
11647
12391
  }
11648
12392
  applySocialsDisplayToRow(row, display);
11649
12393
  return { item, hrefKey, iconKey, order: getSocialsOrderFromDom(row.ownerDocument) };
@@ -13144,6 +13888,7 @@ function readLogoSizeState(content, placement) {
13144
13888
  function getLogoElement(el) {
13145
13889
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
13146
13890
  if (marked) return marked;
13891
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
13147
13892
  const root = el.closest("nav, [data-ohw-nav-root], footer");
13148
13893
  if (!root) return null;
13149
13894
  const anchor = el.closest("a");
@@ -13431,7 +14176,7 @@ function DisplaySwitch({
13431
14176
  onClick: () => onChange(!checked),
13432
14177
  className: cn(
13433
14178
  "relative h-5 w-9 shrink-0 rounded-full transition-colors",
13434
- checked ? "bg-primary" : "bg-primary-50",
14179
+ checked ? "bg-bridge-primary" : "bg-primary-50",
13435
14180
  disabled ? "cursor-default opacity-50" : "cursor-pointer"
13436
14181
  ),
13437
14182
  children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
@@ -13439,7 +14184,7 @@ function DisplaySwitch({
13439
14184
  {
13440
14185
  className: cn(
13441
14186
  "absolute top-0.5 h-4 w-4 rounded-full bg-white transition-all",
13442
- checked ? "left-[1.125rem]" : "left-0.5"
14187
+ checked ? "left-4.5" : "left-0.5"
13443
14188
  )
13444
14189
  }
13445
14190
  )
@@ -13500,8 +14245,8 @@ var lockFooterDuringDrag = lockItemDuringDrag;
13500
14245
  var unlockFooterDragInteraction = unlockItemDragInteraction;
13501
14246
 
13502
14247
  // src/lib/nav-dnd.ts
13503
- function listReorderableNavItems() {
13504
- return listNavbarItems().filter((el) => {
14248
+ function listReorderableNavItems(draggedEl) {
14249
+ return listActiveNavbarItems(draggedEl).filter((el) => {
13505
14250
  const key = el.getAttribute("data-ohw-href-key");
13506
14251
  return isNavbarHrefKey(key) && !isNestedNavChild(el);
13507
14252
  });
@@ -13516,8 +14261,8 @@ function resolveParentNavHrefKey(child) {
13516
14261
  const key = trigger?.getAttribute("data-ohw-href-key");
13517
14262
  return key && isNavbarHrefKey(key) ? key : null;
13518
14263
  }
13519
- function listNavChildren(parentHrefKey) {
13520
- const items = listNavbarItems();
14264
+ function listNavChildren(parentHrefKey, draggedEl) {
14265
+ const items = listActiveNavbarItems(draggedEl);
13521
14266
  const parent = items.find((el) => el.getAttribute("data-ohw-href-key") === parentHrefKey);
13522
14267
  if (!parent) return [];
13523
14268
  const group = parent.closest("[data-ohw-nav-group]");
@@ -13527,20 +14272,83 @@ function listNavChildren(parentHrefKey) {
13527
14272
  (el) => isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))
13528
14273
  );
13529
14274
  }
13530
- function activeNavListContainer() {
13531
- const desktop = getNavbarDesktopContainer();
13532
- if (desktop && desktop.getClientRects().length > 0) {
13533
- const style = window.getComputedStyle(desktop);
13534
- if (style.display !== "none" && style.visibility !== "hidden") return desktop;
14275
+ function isVerticalNavLayout(items, draggedEl) {
14276
+ const visible = items.filter((el) => el.getClientRects().length > 0);
14277
+ if (visible.length >= 2) {
14278
+ const a = visible[0].getBoundingClientRect();
14279
+ const b = visible[1].getBoundingClientRect();
14280
+ const verticalGap = b.top - a.bottom;
14281
+ const horizontalGap = b.left - a.right;
14282
+ return verticalGap > horizontalGap;
14283
+ }
14284
+ const container = getActiveNavbarListContainer(draggedEl);
14285
+ if (!container) return false;
14286
+ const style = window.getComputedStyle(container);
14287
+ return style.flexDirection === "column" || style.flexDirection === "column-reverse";
14288
+ }
14289
+ function buildVerticalRootNavDropSlots(items, draggedEl) {
14290
+ const slots = [];
14291
+ const barThickness = 3;
14292
+ if (items.length === 0) {
14293
+ const container = getActiveNavbarListContainer(draggedEl);
14294
+ if (!container) return slots;
14295
+ const rect = container.getBoundingClientRect();
14296
+ slots.push({
14297
+ insertIndex: 0,
14298
+ parentId: null,
14299
+ left: rect.left,
14300
+ top: rect.top + rect.height / 2 - barThickness / 2,
14301
+ width: Math.max(rect.width, 40),
14302
+ height: barThickness,
14303
+ direction: "horizontal"
14304
+ });
14305
+ return slots;
14306
+ }
14307
+ const edgeGap = (() => {
14308
+ if (items.length < 2) return 16;
14309
+ const a = items[0].getBoundingClientRect();
14310
+ const b = items[1].getBoundingClientRect();
14311
+ return Math.max(0, b.top - a.bottom);
14312
+ })();
14313
+ for (let i = 0; i <= items.length; i++) {
14314
+ let top;
14315
+ let width;
14316
+ let left;
14317
+ if (i === 0) {
14318
+ const first = items[0].getBoundingClientRect();
14319
+ top = first.top - edgeGap / 2 - barThickness / 2;
14320
+ left = first.left;
14321
+ width = first.width;
14322
+ } else if (i === items.length) {
14323
+ const last = items[items.length - 1].getBoundingClientRect();
14324
+ const lastGap = items.length >= 2 ? Math.max(0, last.top - items[items.length - 2].getBoundingClientRect().bottom) : edgeGap;
14325
+ top = last.bottom + lastGap / 2 - barThickness / 2;
14326
+ left = last.left;
14327
+ width = last.width;
14328
+ } else {
14329
+ const prev = items[i - 1].getBoundingClientRect();
14330
+ const next = items[i].getBoundingClientRect();
14331
+ top = (prev.bottom + next.top) / 2 - barThickness / 2;
14332
+ left = Math.min(prev.left, next.left);
14333
+ width = Math.max(prev.right, next.right) - left;
14334
+ }
14335
+ slots.push({
14336
+ insertIndex: i,
14337
+ parentId: null,
14338
+ left,
14339
+ top,
14340
+ width: Math.max(width, 40),
14341
+ height: barThickness,
14342
+ direction: "horizontal"
14343
+ });
13535
14344
  }
13536
- return getNavbarDrawerContainer();
14345
+ return slots;
13537
14346
  }
13538
- function buildRootNavDropSlots() {
13539
- const items = listReorderableNavItems();
14347
+ function buildHorizontalRootNavDropSlots(items, draggedEl) {
13540
14348
  const slots = [];
13541
14349
  const barThickness = 3;
13542
14350
  if (items.length === 0) {
13543
- const container = activeNavListContainer();
14351
+ const container = getActiveNavbarListContainer(draggedEl);
13544
14352
  if (!container) return slots;
13545
14353
  const rect = container.getBoundingClientRect();
13546
14354
  slots.push({
@@ -13571,10 +14379,7 @@ function buildRootNavDropSlots() {
13571
14379
  height = first.height;
13572
14380
  } else if (i === items.length) {
13573
14381
  const last = items[items.length - 1].getBoundingClientRect();
13574
- const lastGap = items.length >= 2 ? Math.max(
13575
- 0,
13576
- last.left - items[items.length - 2].getBoundingClientRect().right
13577
- ) : edgeGap;
14382
+ const lastGap = items.length >= 2 ? Math.max(0, last.left - items[items.length - 2].getBoundingClientRect().right) : edgeGap;
13578
14383
  left = last.right + lastGap / 2 - barThickness / 2;
13579
14384
  top = last.top;
13580
14385
  height = last.height;
@@ -13597,8 +14402,15 @@ function buildRootNavDropSlots() {
13597
14402
  }
13598
14403
  return slots;
13599
14404
  }
13600
- function buildChildNavDropSlots(parentHrefKey) {
13601
- const children = listNavChildren(parentHrefKey);
14405
+ function buildRootNavDropSlots(draggedEl) {
14406
+ const items = listReorderableNavItems(draggedEl);
14407
+ if (isVerticalNavLayout(items, draggedEl)) {
14408
+ return buildVerticalRootNavDropSlots(items, draggedEl);
14409
+ }
14410
+ return buildHorizontalRootNavDropSlots(items, draggedEl);
14411
+ }
14412
+ function buildChildNavDropSlots(parentHrefKey, draggedEl) {
14413
+ const children = listNavChildren(parentHrefKey, draggedEl);
13602
14414
  const slots = [];
13603
14415
  const barThickness = 3;
13604
14416
  if (children.length === 0) return slots;
@@ -13619,10 +14431,7 @@ function buildChildNavDropSlots(parentHrefKey) {
13619
14431
  width = first.width;
13620
14432
  } else if (i === children.length) {
13621
14433
  const last = children[children.length - 1].getBoundingClientRect();
13622
- const lastGap = children.length >= 2 ? Math.max(
13623
- 0,
13624
- last.top - children[children.length - 2].getBoundingClientRect().bottom
13625
- ) : edgeGap;
14434
+ const lastGap = children.length >= 2 ? Math.max(0, last.top - children[children.length - 2].getBoundingClientRect().bottom) : edgeGap;
13626
14435
  top = last.bottom + lastGap / 2 - barThickness / 2;
13627
14436
  left = last.left;
13628
14437
  width = last.width;
@@ -13645,18 +14454,19 @@ function buildChildNavDropSlots(parentHrefKey) {
13645
14454
  }
13646
14455
  return slots;
13647
14456
  }
13648
- function buildAllNavDropSlots(draggedHrefKey) {
13649
- const dragged = listNavbarItems().find((el) => el.getAttribute("data-ohw-href-key") === draggedHrefKey);
13650
- if (!dragged) return buildRootNavDropSlots();
14457
+ function buildAllNavDropSlots(draggedHrefKey, draggedEl) {
14458
+ const activeItems = listActiveNavbarItems(draggedEl);
14459
+ 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);
14460
+ if (!dragged) return buildRootNavDropSlots(draggedEl);
13651
14461
  if (isNestedNavChild(dragged)) {
13652
14462
  const parentKey = resolveParentNavHrefKey(dragged);
13653
14463
  if (!parentKey) return [];
13654
- return buildChildNavDropSlots(parentKey);
14464
+ return buildChildNavDropSlots(parentKey, draggedEl);
13655
14465
  }
13656
- return buildRootNavDropSlots();
14466
+ return buildRootNavDropSlots(draggedEl);
13657
14467
  }
13658
- function hitTestNavDropSlot(clientX, clientY, draggedHrefKey) {
13659
- const slots = buildAllNavDropSlots(draggedHrefKey);
14468
+ function hitTestNavDropSlot(clientX, clientY, draggedHrefKey, draggedEl) {
14469
+ const slots = buildAllNavDropSlots(draggedHrefKey, draggedEl);
13660
14470
  let best = null;
13661
14471
  for (const slot of slots) {
13662
14472
  const cx2 = slot.left + slot.width / 2;
@@ -13707,6 +14517,12 @@ function useNavItemDrag({
13707
14517
  setIsItemDragging(false);
13708
14518
  if (keepOpenEl?.isConnected) {
13709
14519
  setNavGroupForceOpen(keepOpenEl, true);
14520
+ requestAnimationFrame(() => {
14521
+ if (keepOpenEl.isConnected) setNavGroupForceOpen(keepOpenEl, true);
14522
+ requestAnimationFrame(() => {
14523
+ if (keepOpenEl.isConnected) setNavGroupForceOpen(keepOpenEl, true);
14524
+ });
14525
+ });
13710
14526
  } else {
13711
14527
  setNavGroupForceOpen(null, false);
13712
14528
  }
@@ -13722,7 +14538,7 @@ function useNavItemDrag({
13722
14538
  }
13723
14539
  session.activeSlot = activeSlot;
13724
14540
  setSiblingHintRects([]);
13725
- const slots = buildAllNavDropSlots(session.hrefKey);
14541
+ const slots = buildAllNavDropSlots(session.hrefKey, session.draggedEl);
13726
14542
  setNavDropSlots(slots);
13727
14543
  const activeIdx = activeSlot ? slots.findIndex(
13728
14544
  (s) => s.parentId === activeSlot.parentId && s.insertIndex === activeSlot.insertIndex
@@ -13758,7 +14574,12 @@ function useNavItemDrag({
13758
14574
  if (session.wasSelected && selectedElRef.current === session.draggedEl) {
13759
14575
  setToolbarRect(rect);
13760
14576
  }
13761
- const initialSlot = hitTestNavDropSlot(session.lastClientX, session.lastClientY, session.hrefKey);
14577
+ const initialSlot = hitTestNavDropSlot(
14578
+ session.lastClientX,
14579
+ session.lastClientY,
14580
+ session.hrefKey,
14581
+ session.draggedEl
14582
+ );
13762
14583
  refreshNavDragVisuals(session, initialSlot, session.lastClientX, session.lastClientY);
13763
14584
  },
13764
14585
  [
@@ -13780,10 +14601,11 @@ function useNavItemDrag({
13780
14601
  }
13781
14602
  const x = typeof clientX === "number" && (clientX !== 0 || clientY !== 0) ? clientX : session.lastClientX;
13782
14603
  const y = typeof clientY === "number" && (clientX !== 0 || clientY !== 0) ? clientY : session.lastClientY;
13783
- const slot = session.activeSlot ?? hitTestNavDropSlot(x, y, session.hrefKey);
14604
+ const slot = session.activeSlot ?? hitTestNavDropSlot(x, y, session.hrefKey, session.draggedEl);
13784
14605
  const planned = slot != null ? planNavItemMove(session.hrefKey, slot.parentId, slot.insertIndex) : null;
13785
14606
  const wasSelected = session.wasSelected;
13786
14607
  const hrefKey = session.hrefKey;
14608
+ const keepDropdownOpen = Boolean(session.draggedEl.closest("[data-ohw-nav-children]"));
13787
14609
  const applySelectionAfterDrop = () => {
13788
14610
  if (!wasSelected) {
13789
14611
  deselectRef.current();
@@ -13817,6 +14639,14 @@ function useNavItemDrag({
13817
14639
  });
13818
14640
  applySelectionAfterDrop();
13819
14641
  clearNavDragVisuals();
14642
+ if (keepDropdownOpen) {
14643
+ requestAnimationFrame(() => {
14644
+ const desktop = document.querySelector("[data-ohw-nav-container]");
14645
+ const drawer = document.querySelector("[data-ohw-nav-drawer]");
14646
+ 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)}"]`);
14647
+ if (link) setNavGroupForceOpen(link, true);
14648
+ });
14649
+ }
13820
14650
  requestAnimationFrame(() => {
13821
14651
  if (editContentRef.current[NAV_ORDER_KEY] === planned.orderJson) {
13822
14652
  applyNavForest(planned.forest);
@@ -13856,7 +14686,7 @@ function useNavItemDrag({
13856
14686
  if (!session) return false;
13857
14687
  e.preventDefault();
13858
14688
  if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
13859
- const slot = hitTestNavDropSlot(e.clientX, e.clientY, session.hrefKey);
14689
+ const slot = hitTestNavDropSlot(e.clientX, e.clientY, session.hrefKey, session.draggedEl);
13860
14690
  refreshNavDragVisualsRef.current(session, slot, e.clientX, e.clientY);
13861
14691
  return true;
13862
14692
  },
@@ -13902,7 +14732,7 @@ function useNavItemDrag({
13902
14732
  clearTextSelection();
13903
14733
  const session = navDragRef.current;
13904
14734
  if (!session) return;
13905
- const slot = hitTestNavDropSlot(e.clientX, e.clientY, session.hrefKey);
14735
+ const slot = hitTestNavDropSlot(e.clientX, e.clientY, session.hrefKey, session.draggedEl);
13906
14736
  refreshNavDragVisualsRef.current(session, slot, e.clientX, e.clientY);
13907
14737
  return;
13908
14738
  }
@@ -13918,7 +14748,8 @@ function useNavItemDrag({
13918
14748
  } catch {
13919
14749
  }
13920
14750
  if (linkPopoverOpenRef.current) setLinkPopover(null);
13921
- if (activeElRef.current) deactivateRef.current();
14751
+ const isNestedDrag = Boolean(pending.el.closest("[data-ohw-nav-children]"));
14752
+ if (activeElRef.current && !isNestedDrag) deactivateRef.current();
13922
14753
  const key = pending.el.getAttribute("data-ohw-href-key");
13923
14754
  if (!key) return;
13924
14755
  beginNavDragRef.current({
@@ -13929,6 +14760,9 @@ function useNavItemDrag({
13929
14760
  lastClientY: e.clientY,
13930
14761
  activeSlot: null
13931
14762
  });
14763
+ if (activeElRef.current && isNestedDrag) {
14764
+ deactivateRef.current();
14765
+ }
13932
14766
  };
13933
14767
  const endPointerDrag = (e) => {
13934
14768
  const pending = navPointerDragRef.current;
@@ -14006,6 +14840,7 @@ function useNavItemDrag({
14006
14840
  );
14007
14841
  return {
14008
14842
  navDragRef,
14843
+ navPointerDragRef,
14009
14844
  navDropSlots,
14010
14845
  activeNavDropIndex,
14011
14846
  startNavLinkDrag,
@@ -14210,15 +15045,17 @@ function useSectionDrag({
14210
15045
  clearSectionDragVisuals();
14211
15046
  return;
14212
15047
  }
14213
- const orderJson = JSON.stringify(entries);
15048
+ const orderJson = JSON.stringify(
15049
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
15050
+ );
14214
15051
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
14215
15052
  setAiSectionOrder(orderJson, window.location.pathname);
14216
15053
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
14217
- applyPersistedOrder(entries);
15054
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
14218
15055
  clearSectionDragVisuals();
14219
15056
  requestAnimationFrame(() => {
14220
15057
  if (editContentRef.current[SECTION_ORDER_KEY] === orderJson) {
14221
- applyPersistedOrder(entries);
15058
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
14222
15059
  }
14223
15060
  requestAnimationFrame(() => {
14224
15061
  window.dispatchEvent(new Event("resize"));
@@ -14251,8 +15088,9 @@ function useSectionDrag({
14251
15088
  const target = e.target;
14252
15089
  if (!(target instanceof HTMLElement)) return;
14253
15090
  if (target.closest(SECTION_DRAG_EXCLUDED_SELECTOR)) return;
14254
- const sectionEl = target.closest("[data-ohw-section]");
14255
- if (!sectionEl || isChromeSection(sectionEl) || sectionEl.dataset.ohwSection === "footer") return;
15091
+ const inner = target.closest("[data-ohw-section]");
15092
+ if (!inner || isChromeSection(inner) || inner.dataset.ohwSection === "footer") return;
15093
+ const sectionEl = movableUnit(inner);
14256
15094
  if (!topLevelSections().includes(sectionEl)) return;
14257
15095
  startSectionPressDrag(sectionEl, e.clientX, e.clientY, e.pointerId);
14258
15096
  };
@@ -14519,10 +15357,15 @@ function collectEditableNodes(extraContent, root = document) {
14519
15357
  if (el.dataset.ohwEditable === "link") {
14520
15358
  return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref3(el) };
14521
15359
  }
15360
+ if (el.dataset.ohwEditable === "map") {
15361
+ return { key: el.dataset.ohwKey ?? "", type: "map", text: el.dataset.ohwMapQuery ?? "" };
15362
+ }
14522
15363
  return {
14523
15364
  key: el.dataset.ohwKey ?? "",
14524
15365
  type: el.dataset.ohwEditable ?? "text",
14525
- text: el.dataset.ohwEditable === "plain" ? el.innerText ?? "" : el.innerHTML
15366
+ // innerText does not exist on SVG elements (a circular-badge <textPath> is a legitimate
15367
+ // plain editable) — textContent is the value there.
15368
+ text: el.dataset.ohwEditable === "plain" ? el.innerText ?? el.textContent ?? "" : el.innerHTML
14526
15369
  };
14527
15370
  });
14528
15371
  const hrefEls = Array.from(root.querySelectorAll("[data-ohw-href-key]"));
@@ -14959,7 +15802,7 @@ var badgeVariants = cva(
14959
15802
  {
14960
15803
  variants: {
14961
15804
  variant: {
14962
- default: "border-transparent bg-primary text-primary-foreground",
15805
+ default: "border-transparent bg-bridge-primary text-primary-foreground",
14963
15806
  secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
14964
15807
  destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
14965
15808
  outline: "text-foreground"
@@ -15081,21 +15924,10 @@ function parseSchedulingInsertAfter(insertAfter) {
15081
15924
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
15082
15925
  };
15083
15926
  }
15084
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
15085
- const parsed = parseSchedulingInsertAfter(insertAfter);
15086
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
15087
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
15088
- return { effectiveInsertAfter, insertBefore };
15089
- }
15090
- function getSchedulingMountPoint(insertAfter) {
15091
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
15092
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
15093
- if (!anchorEl && anchor === "scheduling") {
15094
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
15095
- anchorEl = widgets.at(-1) ?? null;
15096
- }
15097
- if (!anchorEl) return null;
15098
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
15927
+ function resolveEntryAnchor(entry) {
15928
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
15929
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
15930
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
15099
15931
  }
15100
15932
  function schedulingMountDepth(insertAfter) {
15101
15933
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -15112,8 +15944,7 @@ function getPageSchedulingEntries(raw) {
15112
15944
  }
15113
15945
  }
15114
15946
  function isSchedulingWidgetMissing(entry) {
15115
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
15116
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
15947
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
15117
15948
  }
15118
15949
  function hasMissingSchedulingWidgets(entries) {
15119
15950
  return entries.some(isSchedulingWidgetMissing);
@@ -15151,18 +15982,18 @@ function initSectionsFromContent(content, removeExisting = false, currentPath =
15151
15982
  } catch {
15152
15983
  }
15153
15984
  }
15154
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
15155
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
15156
- const sectionId = schedulingSectionId(effectiveInsertAfter);
15985
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
15986
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
15987
+ const sectionId = schedulingSectionId(widgetId);
15157
15988
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
15158
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
15159
- if (!mountPoint) return false;
15989
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
15990
+ if (!anchorEl) return false;
15991
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
15160
15992
  const container = document.createElement("div");
15161
15993
  container.dataset.ohwSectionContainer = "scheduling";
15162
- container.dataset.ohwSection = sectionId;
15163
15994
  container.dataset.ohwInstance = sectionId;
15164
- if (insertBefore) {
15165
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
15995
+ if (beforeId) {
15996
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
15166
15997
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
15167
15998
  if (!beforePoint) return false;
15168
15999
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -15173,20 +16004,26 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
15173
16004
  }
15174
16005
  tail.insertAdjacentElement("afterend", container);
15175
16006
  }
15176
- const root = (0, import_client2.createRoot)(container);
15177
- schedulingRoots.set(container, root);
15178
- (0, import_react_dom3.flushSync)(() => {
15179
- root.render(
15180
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
15181
- SchedulingWidget,
15182
- {
15183
- notifyOnConnect,
15184
- initialScheduleId: scheduleId,
15185
- insertAfter: effectiveInsertAfter
15186
- }
15187
- )
15188
- );
15189
- });
16007
+ try {
16008
+ const root = (0, import_client2.createRoot)(container);
16009
+ schedulingRoots.set(container, root);
16010
+ (0, import_react_dom3.flushSync)(() => {
16011
+ root.render(
16012
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
16013
+ SchedulingWidget,
16014
+ {
16015
+ notifyOnConnect,
16016
+ initialScheduleId: scheduleId,
16017
+ insertAfter: widgetId
16018
+ }
16019
+ )
16020
+ );
16021
+ });
16022
+ } catch (err) {
16023
+ console.error("[ow:scheduling] render threw", err);
16024
+ container.remove();
16025
+ return false;
16026
+ }
15190
16027
  const tracker = getSectionsTracker();
15191
16028
  let sections = [];
15192
16029
  try {
@@ -15194,10 +16031,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
15194
16031
  } catch {
15195
16032
  }
15196
16033
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
15197
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
16034
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
15198
16035
  sections.push({
15199
16036
  type: "scheduling",
15200
- insertAfter: effectiveInsertAfter,
16037
+ insertAfter: widgetId,
16038
+ anchorId,
16039
+ beforeId: beforeId ?? null,
15201
16040
  pagePath: window.location.pathname,
15202
16041
  ...scheduleId ? { scheduleId } : {}
15203
16042
  });
@@ -15211,7 +16050,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
15211
16050
  for (let i = pending.length - 1; i >= 0; i--) {
15212
16051
  const entry = pending[i];
15213
16052
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
15214
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId ?? null)) {
16053
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
16054
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
15215
16055
  pending.splice(i, 1);
15216
16056
  }
15217
16057
  }
@@ -15303,7 +16143,7 @@ var EDITOR_CHROME_SELECTOR = '[data-ohw-toolbar], [data-ohw-form-toolbar], [data
15303
16143
  function isOverEditorChrome(x, y) {
15304
16144
  return document.elementsFromPoint(x, y).some((el) => el instanceof HTMLElement && el.matches(EDITOR_CHROME_SELECTOR));
15305
16145
  }
15306
- 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"])';
16146
+ 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"])';
15307
16147
  function getVideoEl2(el) {
15308
16148
  return el instanceof HTMLVideoElement ? el : el.querySelector("video");
15309
16149
  }
@@ -15359,6 +16199,12 @@ function applyVideoSettingNode(key, val) {
15359
16199
  });
15360
16200
  return true;
15361
16201
  }
16202
+ function applyMapQuery(el, val) {
16203
+ if (!(el instanceof HTMLIFrameElement)) return;
16204
+ const nextSrc = `https://www.google.com/maps?q=${encodeURIComponent(val)}&output=embed`;
16205
+ if (el.src !== nextSrc) el.src = nextSrc;
16206
+ el.setAttribute("data-ohw-map-query", val);
16207
+ }
15362
16208
  function applyLinkByKey(key, val) {
15363
16209
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
15364
16210
  if (el.dataset.ohwEditable === "link") applyLinkHref(el, val);
@@ -15369,6 +16215,11 @@ function applyLinkByKey(key, val) {
15369
16215
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
15370
16216
  }
15371
16217
  }
16218
+ function isInsideLinkEditor(target) {
16219
+ return Boolean(
16220
+ 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"]')
16221
+ );
16222
+ }
15372
16223
  function isInsideFloatingPanel(target) {
15373
16224
  return Boolean(target.closest("[data-ohw-floating-panel]"));
15374
16225
  }
@@ -15376,11 +16227,6 @@ function isPointOverFloatingPanel(clientX, clientY) {
15376
16227
  const el = document.elementFromPoint(clientX, clientY);
15377
16228
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
15378
16229
  }
15379
- function isInsideLinkEditor(target) {
15380
- return Boolean(
15381
- 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"]')
15382
- );
15383
- }
15384
16230
  function getHrefKeyFromElement(el) {
15385
16231
  if (!el) return null;
15386
16232
  const anchor = el.closest("[data-ohw-href-key]");
@@ -15393,6 +16239,7 @@ function isNavDropdownPanelOpen(childrenRoot) {
15393
16239
  if (childrenRoot.closest("[data-ohw-nav-drawer]")) return true;
15394
16240
  const group = childrenRoot.closest("[data-ohw-nav-group]");
15395
16241
  if (group?.hasAttribute("data-ohw-nav-force-open")) return true;
16242
+ if (navDropdownsOpenOnClick()) return false;
15396
16243
  if (childrenRoot.clientHeight < 2 || childrenRoot.clientWidth < 2) return false;
15397
16244
  const style = window.getComputedStyle(childrenRoot);
15398
16245
  if (style.display === "none" || style.visibility === "hidden") return false;
@@ -15436,6 +16283,11 @@ function getNavigationItemAnchor(el) {
15436
16283
  function isNavigationItem2(el) {
15437
16284
  return getNavigationItemAnchor(el) !== null;
15438
16285
  }
16286
+ function isNavFooterScopedNavigationItem(el) {
16287
+ return Boolean(
16288
+ el.closest("nav, footer, [data-ohw-nav-container], [data-ohw-nav-drawer]")
16289
+ );
16290
+ }
15439
16291
  function socialWordsClicked(target, item) {
15440
16292
  const text = target.closest('[data-ohw-editable="text"], [data-ohw-editable="plain"]');
15441
16293
  return Boolean(text && item.contains(text));
@@ -15639,13 +16491,14 @@ function getNavigationSelectionParent(el) {
15639
16491
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
15640
16492
  return getFooterLinksContainer();
15641
16493
  }
15642
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
16494
+ 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)) {
15643
16495
  return getNavigationRoot(el);
15644
16496
  }
15645
16497
  return null;
15646
16498
  }
15647
16499
  function collectNavigationItemSiblingHintRects(selected) {
15648
16500
  if (!isNavigationItem2(selected)) return [];
16501
+ if (!isNavFooterScopedNavigationItem(selected)) return [];
15649
16502
  const socialsRow = findSocialsRow(selected);
15650
16503
  if (socialsRow) {
15651
16504
  return listSocialItems(socialsRow).filter((item) => item !== selected).map((item) => item.getBoundingClientRect());
@@ -15854,7 +16707,6 @@ var ICONS = {
15854
16707
  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"/>',
15855
16708
  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"/>'
15856
16709
  };
15857
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
15858
16710
  var SELECTION_CHROME_GAP2 = 4;
15859
16711
  var TOOLBAR_STROKE_GAP2 = 4;
15860
16712
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -16234,6 +17086,7 @@ function StateToggle({
16234
17086
  );
16235
17087
  }
16236
17088
  var contentCache = /* @__PURE__ */ new Map();
17089
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
16237
17090
  var brandingCache = /* @__PURE__ */ new Map();
16238
17091
  var OHW_LOADER_STYLE = {
16239
17092
  position: "fixed",
@@ -16763,13 +17616,6 @@ function OhhwellsBridge() {
16763
17616
  const [isItemDragging, setIsItemDragging] = (0, import_react17.useState)(false);
16764
17617
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react17.useState)(false);
16765
17618
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
16766
- const [floatingPanel, setFloatingPanel] = (0, import_react17.useState)(null);
16767
- const floatingPanelOpenRef = (0, import_react17.useRef)(false);
16768
- floatingPanelOpenRef.current = floatingPanel !== null;
16769
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react17.useState)(null);
16770
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react17.useState)(null);
16771
- const [editorViewport, setEditorViewport] = (0, import_react17.useState)("desktop");
16772
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react17.useState)(null);
16773
17619
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react17.useState)(null);
16774
17620
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react17.useState)(null);
16775
17621
  const footerDragRef = (0, import_react17.useRef)(null);
@@ -16787,6 +17633,13 @@ function OhhwellsBridge() {
16787
17633
  const brandKitRef = (0, import_react17.useRef)("");
16788
17634
  const stylesRef = (0, import_react17.useRef)("");
16789
17635
  const pendingDeleteUndoRef = (0, import_react17.useRef)(null);
17636
+ const [floatingPanel, setFloatingPanel] = (0, import_react17.useState)(null);
17637
+ const floatingPanelOpenRef = (0, import_react17.useRef)(false);
17638
+ const setFloatingPanelRef = (0, import_react17.useRef)(setFloatingPanel);
17639
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react17.useState)(null);
17640
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react17.useState)(null);
17641
+ const [editorViewport, setEditorViewport] = (0, import_react17.useState)("desktop");
17642
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react17.useState)(null);
16790
17643
  const [sitePages, setSitePages] = (0, import_react17.useState)([]);
16791
17644
  const [sectionsByPath, setSectionsByPath] = (0, import_react17.useState)({});
16792
17645
  const sectionsPrefetchGenRef = (0, import_react17.useRef)(0);
@@ -16795,9 +17648,21 @@ function OhhwellsBridge() {
16795
17648
  const linkPopoverOpenRef = (0, import_react17.useRef)(false);
16796
17649
  const linkPopoverGraceUntilRef = (0, import_react17.useRef)(0);
16797
17650
  setLinkPopoverRef.current = setLinkPopover;
17651
+ setFloatingPanelRef.current = setFloatingPanel;
16798
17652
  linkPopoverSessionRef.current = linkPopover;
17653
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
17654
+ (0, import_react17.useEffect)(() => {
17655
+ const syncViewport = () => {
17656
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
17657
+ setEditorViewport((prev) => prev === next ? prev : next);
17658
+ };
17659
+ syncViewport();
17660
+ window.addEventListener("resize", syncViewport);
17661
+ return () => window.removeEventListener("resize", syncViewport);
17662
+ }, []);
16799
17663
  const {
16800
17664
  navDragRef,
17665
+ navPointerDragRef,
16801
17666
  navDropSlots,
16802
17667
  activeNavDropIndex,
16803
17668
  startNavLinkDrag,
@@ -17000,7 +17865,12 @@ function OhhwellsBridge() {
17000
17865
  if (owner) persistFieldsRef.current(owner);
17001
17866
  }
17002
17867
  activeElRef.current = null;
17003
- setNavGroupForceOpen(null, false);
17868
+ const preserveNavDropdown = Boolean(navPointerDragRef.current?.el?.closest("[data-ohw-nav-children]")) || Boolean(navDragRef.current?.draggedEl?.closest("[data-ohw-nav-children]")) || Boolean(
17869
+ selectedElRef.current?.closest("[data-ohw-nav-children]") && selectedElRef.current?.closest("[data-ohw-nav-group]")?.hasAttribute("data-ohw-nav-force-open")
17870
+ );
17871
+ if (!preserveNavDropdown) {
17872
+ setNavGroupForceOpen(null, false);
17873
+ }
17004
17874
  setReorderHrefKey(null);
17005
17875
  setReorderDragDisabled(false);
17006
17876
  if (!selectedElRef.current) {
@@ -17122,7 +17992,10 @@ function OhhwellsBridge() {
17122
17992
  setSelectedIsSocialsRow(false);
17123
17993
  const isDropdownTrigger = !isNestedNavChild(navAnchor) && (navItemHasDropdownChildren(navAnchor) || navItemOwnsDropdownPanel(navAnchor));
17124
17994
  if (isNestedNavChild(navAnchor)) {
17125
- setNavGroupForceOpen(navAnchor, true);
17995
+ const group = navAnchor.closest("[data-ohw-nav-group]");
17996
+ if (group?.hasAttribute("data-ohw-nav-force-open")) {
17997
+ setNavGroupForceOpen(navAnchor, true);
17998
+ }
17126
17999
  setNavDropdownPreviewOpen(null);
17127
18000
  } else if (isDropdownTrigger) {
17128
18001
  const group = navAnchor.closest("[data-ohw-nav-group]");
@@ -17204,6 +18077,10 @@ function OhhwellsBridge() {
17204
18077
  const handleNavDropdownOpenChange = (0, import_react17.useCallback)((open) => {
17205
18078
  const selected = selectedElRef.current;
17206
18079
  if (!selected || !isNavigationItem2(selected)) return;
18080
+ if (isNestedNavChild(selected)) return;
18081
+ const group = selected.closest("[data-ohw-nav-group]");
18082
+ const domOpen = Boolean(group?.hasAttribute("data-ohw-nav-force-open"));
18083
+ if (open === domOpen) return;
17207
18084
  setNavGroupForceOpen(selected, open);
17208
18085
  setNavDropdownPreviewOpen(open);
17209
18086
  requestAnimationFrame(() => {
@@ -17733,7 +18610,10 @@ function OhhwellsBridge() {
17733
18610
  clearHrefKeyHover(anchor);
17734
18611
  const isDropdownTrigger = !isNestedNavChild(anchor) && (navItemHasDropdownChildren(anchor) || navItemOwnsDropdownPanel(anchor));
17735
18612
  if (isNestedNavChild(anchor)) {
17736
- setNavGroupForceOpen(anchor, true);
18613
+ const group = anchor.closest("[data-ohw-nav-group]");
18614
+ if (group?.hasAttribute("data-ohw-nav-force-open")) {
18615
+ setNavGroupForceOpen(anchor, true);
18616
+ }
17737
18617
  setNavDropdownPreviewOpen(null);
17738
18618
  } else if (isDropdownTrigger) {
17739
18619
  setNavGroupForceOpen(null, false);
@@ -18120,6 +19000,7 @@ function OhhwellsBridge() {
18120
19000
  }
18121
19001
  if (typeof content[STYLE_STORE_KEY] === "string") {
18122
19002
  stylesRef.current = content[STYLE_STORE_KEY];
19003
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
18123
19004
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
18124
19005
  }
18125
19006
  applyBrandChrome(content);
@@ -18127,11 +19008,11 @@ function OhhwellsBridge() {
18127
19008
  for (const [key, val] of Object.entries(content)) {
18128
19009
  if (key === "__ohw_sections") continue;
18129
19010
  if (key === AI_SECTIONS_KEY) continue;
19011
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
19012
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
18130
19013
  if (key === BRAND_KIT_KEY) continue;
18131
19014
  if (key === STYLE_STORE_KEY) continue;
18132
19015
  if (BRAND_CHROME_KEYS.has(key)) continue;
18133
- if (key === LOGO_PLACEHOLDER_KEY) continue;
18134
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
18135
19016
  if (applyVideoSettingNode(key, val)) continue;
18136
19017
  if (applyCarouselNode(key, val)) continue;
18137
19018
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -18159,6 +19040,8 @@ function OhhwellsBridge() {
18159
19040
  }
18160
19041
  } else if (el.dataset.ohwEditable === "link") {
18161
19042
  applyLinkHref(el, val);
19043
+ } else if (el.dataset.ohwEditable === "map") {
19044
+ applyMapQuery(el, val);
18162
19045
  } else if (el.dataset.ohwEditable === "icon") {
18163
19046
  applyIconMarkup(el, val);
18164
19047
  } else if (el.dataset.ohwEditable === "form") {
@@ -18177,6 +19060,9 @@ function OhhwellsBridge() {
18177
19060
  applySocialsDisplayFromContent(content);
18178
19061
  applySocialsLabelsFromContent(content, document, { fillEmptyOnly: isEditModeRef.current });
18179
19062
  if (isEditModeRef.current) requestMissingSocialIconsRef.current();
19063
+ if (typeof content[STYLE_STORE_KEY] === "string") {
19064
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
19065
+ }
18180
19066
  enforceLinkHrefs();
18181
19067
  initSectionsFromContent(content, true);
18182
19068
  sectionsLoadedRef.current = true;
@@ -18197,7 +19083,9 @@ function OhhwellsBridge() {
18197
19083
  let cancelled = false;
18198
19084
  setFetchState("loading");
18199
19085
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
18200
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
19086
+ const initialPath = pathname;
19087
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
19088
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18201
19089
  if (cancelled) return;
18202
19090
  const content = data?.content ?? {};
18203
19091
  const branding = Boolean(data?.showBranding);
@@ -18306,7 +19194,9 @@ function OhhwellsBridge() {
18306
19194
  }, [isEditMode]);
18307
19195
  (0, import_react17.useEffect)(() => {
18308
19196
  if (isEditMode || fetchState !== "done") return;
19197
+ console.log("env", process.env.NEXT_PUBLIC_FLOWOPS_API_URL);
18309
19198
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19199
+ console.log({ apiUrl, subdomain });
18310
19200
  bindPublishedForms(apiUrl, subdomain, contentCache.get(subdomain) ?? {});
18311
19201
  }, [isEditMode, fetchState, subdomain]);
18312
19202
  (0, import_react17.useEffect)(() => {
@@ -18316,10 +19206,10 @@ function OhhwellsBridge() {
18316
19206
  const applyFromCache = () => {
18317
19207
  const content = contentCache.get(subdomain);
18318
19208
  if (!content) return;
18319
- retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
18320
- initSectionInstancesFromContent(content, window.location.pathname);
18321
19209
  observer?.disconnect();
18322
19210
  try {
19211
+ retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
19212
+ initSectionInstancesFromContent(content, window.location.pathname);
18323
19213
  applyBrandChrome(content);
18324
19214
  if (typeof content[BRAND_KIT_KEY] === "string") {
18325
19215
  applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
@@ -18331,16 +19221,17 @@ function OhhwellsBridge() {
18331
19221
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
18332
19222
  }
18333
19223
  if (typeof content[STYLE_STORE_KEY] === "string") {
19224
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
18334
19225
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
18335
19226
  }
18336
19227
  for (const [key, val] of Object.entries(content)) {
18337
19228
  if (key === "__ohw_sections") continue;
18338
19229
  if (key === AI_SECTIONS_KEY) continue;
19230
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
19231
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
18339
19232
  if (key === BRAND_KIT_KEY) continue;
18340
19233
  if (key === STYLE_STORE_KEY) continue;
18341
19234
  if (BRAND_CHROME_KEYS.has(key)) continue;
18342
- if (key === LOGO_PLACEHOLDER_KEY) continue;
18343
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
18344
19235
  if (applyVideoSettingNode(key, val)) continue;
18345
19236
  if (applyCarouselNode(key, val)) continue;
18346
19237
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -18355,6 +19246,8 @@ function OhhwellsBridge() {
18355
19246
  if (video && video.src !== val) applyVideoSrc(video, val);
18356
19247
  } else if (el.dataset.ohwEditable === "link") {
18357
19248
  applyLinkHref(el, val);
19249
+ } else if (el.dataset.ohwEditable === "map") {
19250
+ applyMapQuery(el, val);
18358
19251
  } else if (el.dataset.ohwEditable === "form") {
18359
19252
  } else if (isIconMarkupValue(val)) {
18360
19253
  } else if (el.innerHTML !== val) {
@@ -18370,6 +19263,9 @@ function OhhwellsBridge() {
18370
19263
  applySocialsDisplayFromContent(content);
18371
19264
  applySocialsLabelsFromContent(content, document, { fillEmptyOnly: isEditModeRef.current });
18372
19265
  if (isEditModeRef.current) requestMissingSocialIconsRef.current();
19266
+ if (typeof content[STYLE_STORE_KEY] === "string") {
19267
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
19268
+ }
18373
19269
  document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
18374
19270
  if (!form.querySelector("[data-ohw-form-success]")) {
18375
19271
  reconcileFieldsFromContent(form, content);
@@ -18386,6 +19282,17 @@ function OhhwellsBridge() {
18386
19282
  debounceTimer = setTimeout(applyFromCache, 150);
18387
19283
  };
18388
19284
  applyFromCache();
19285
+ const pathCacheKey = `${subdomain}::${pathname}`;
19286
+ if (!fetchedContentPaths.has(pathCacheKey)) {
19287
+ fetchedContentPaths.add(pathCacheKey);
19288
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19289
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
19290
+ if (!data?.content) return;
19291
+ contentCache.set(subdomain, data.content);
19292
+ applyFromCache();
19293
+ }).catch(() => {
19294
+ });
19295
+ }
18389
19296
  observer = new MutationObserver(scheduleApply);
18390
19297
  observer.observe(document.body, { childList: true, subtree: true });
18391
19298
  return () => {
@@ -18410,6 +19317,10 @@ function OhhwellsBridge() {
18410
19317
  deselectRef.current();
18411
19318
  deactivateRef.current();
18412
19319
  }, [pathname, isEditMode]);
19320
+ (0, import_react17.useEffect)(() => {
19321
+ if (!isEditMode) return;
19322
+ initSectionInstancesFromContent(editContentRef.current, pathname);
19323
+ }, [pathname, isEditMode]);
18413
19324
  (0, import_react17.useEffect)(() => {
18414
19325
  const contentForNav = () => {
18415
19326
  if (isEditMode) return editContentRef.current;
@@ -18501,26 +19412,11 @@ function OhhwellsBridge() {
18501
19412
  const t2 = setTimeout(measure, 500);
18502
19413
  const ro = new ResizeObserver(schedule);
18503
19414
  ro.observe(document.body);
18504
- let lastWidth = window.innerWidth;
18505
- let resizeTimers = [];
18506
- const clearResizeTimers = () => {
18507
- resizeTimers.forEach(clearTimeout);
18508
- resizeTimers = [];
18509
- };
18510
- const handleResize = () => {
18511
- if (window.innerWidth === lastWidth) return;
18512
- lastWidth = window.innerWidth;
18513
- clearResizeTimers();
18514
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
18515
- };
18516
- window.addEventListener("resize", handleResize);
18517
19415
  return () => {
18518
19416
  clearTimeout(t1);
18519
19417
  clearTimeout(t2);
18520
19418
  if (raf != null) cancelAnimationFrame(raf);
18521
19419
  ro.disconnect();
18522
- clearResizeTimers();
18523
- window.removeEventListener("resize", handleResize);
18524
19420
  };
18525
19421
  }, [pathname, isEditMode, postToParent2]);
18526
19422
  (0, import_react17.useEffect)(() => {
@@ -18551,28 +19447,19 @@ function OhhwellsBridge() {
18551
19447
  return;
18552
19448
  }
18553
19449
  const existing = editStylesRef.current;
18554
- let initialVh = window.innerHeight;
18555
- if (existing?.base.textContent) {
18556
- const match = existing.base.textContent.match(/\.min-h-screen[^{]*\{[^}]*min-height:\s*(\d+)px/);
18557
- if (match) initialVh = parseInt(match[1], 10);
18558
- }
19450
+ const canvasHeight = "var(--ohw-canvas-h, 852px)";
18559
19451
  const baseCss = `
18560
19452
  html { height: auto !important; }
18561
19453
  body { height: auto !important; min-height: 0 !important; overflow: hidden !important; }
18562
- .min-h-screen, .min-h-svh, .min-h-dvh { min-height: ${initialVh}px !important; }
18563
- .h-screen, .h-svh, .h-dvh { height: ${initialVh}px !important; }
18564
- [style*="100vh"] { min-height: ${initialVh}px !important; height: ${initialVh}px !important; }
18565
- [style*="100svh"] { min-height: ${initialVh}px !important; height: ${initialVh}px !important; }
18566
- [style*="100dvh"] { min-height: ${initialVh}px !important; height: ${initialVh}px !important; }
18567
- /* A section written as min-height: var(--ohw-canvas-h, 100svh) \u2014 the documented way to
18568
- build a full-viewport section that still grows for its own content \u2014 matches the three
18569
- rules above on substring alone, since the fallback text contains "100svh" too. Forcing
18570
- a literal height on top of that turns "at least one screen" into "exactly one screen",
18571
- so content taller than one screen (a long mobile hero, say) overflows a centered flex
18572
- column upward, under whatever sits above it. Only the min-height half belongs to it. */
18573
- [style*="min-height"][style*="100vh"],
18574
- [style*="min-height"][style*="100svh"],
18575
- [style*="min-height"][style*="100dvh"] { height: auto !important; }
19454
+ .min-h-screen, .min-h-svh, .min-h-dvh { min-height: ${canvasHeight} !important; }
19455
+ .h-screen, .h-svh, .h-dvh { height: ${canvasHeight} !important; }
19456
+ /* Literal inline viewport units only \u2014 skip R11 sections that already reference --ohw-canvas-h
19457
+ (their style attribute contains "100svh" only as a var() fallback string). */
19458
+ [style*="100vh"]:not([style*="--ohw-canvas-h"]) { min-height: ${canvasHeight} !important; height: ${canvasHeight} !important; }
19459
+ [style*="100svh"]:not([style*="--ohw-canvas-h"]) { min-height: ${canvasHeight} !important; height: ${canvasHeight} !important; }
19460
+ [style*="100dvh"]:not([style*="--ohw-canvas-h"]) { min-height: ${canvasHeight} !important; height: ${canvasHeight} !important; }
19461
+ /* R11: min-height via --ohw-canvas-h; height must grow with content taller than one screen. */
19462
+ [style*="min-height"][style*="--ohw-canvas-h"] { height: auto !important; }
18576
19463
  /* Emptied text keeps somewhere to click. A label typed down to nothing collapses to a
18577
19464
  couple of pixels, and getting back into it meant hunting for the caret with the mouse.
18578
19465
  Edit mode only \u2014 the published page shows nothing where there is nothing (OHH-736). */
@@ -18719,6 +19606,10 @@ function OhhwellsBridge() {
18719
19606
  outline-offset: 0 !important;
18720
19607
  box-shadow: none !important;
18721
19608
  }
19609
+ /* Open nav dropdown panels paint above AI section selection chrome (2147483000). */
19610
+ [data-ohw-nav-group][data-ohw-nav-force-open] > [data-ohw-nav-children] {
19611
+ z-index: 2147483100 !important;
19612
+ }
18722
19613
  /* Text edit wins over grab/default (must beat [data-ohw-can-drag] *). */
18723
19614
  [data-ohw-editing],
18724
19615
  [data-ohw-editing] *,
@@ -18737,6 +19628,10 @@ function OhhwellsBridge() {
18737
19628
  [data-ohw-editable-state], [data-ohw-editable-state] * { pointer-events: none !important; }
18738
19629
  [data-ohw-editable-state][data-ohw-active-state] [data-ohw-editable] { pointer-events: auto !important; }
18739
19630
  [data-ohw-editable-state][data-ohw-active-state][data-ohw-editable] { pointer-events: auto !important; }
19631
+ /* SVG hit-tests painted glyphs only, so an editable curved-text badge answered clicks
19632
+ on its letter strokes alone (and a spinning one barely at all). In edit mode the
19633
+ whole box takes the click; the handler resolves it to the editable inside. */
19634
+ svg:has([data-ohw-editable]) { pointer-events: bounding-box !important; }
18740
19635
  `;
18741
19636
  if (!existing) {
18742
19637
  const base = document.createElement("style");
@@ -18775,9 +19670,6 @@ function OhhwellsBridge() {
18775
19670
  if (target.closest("[data-ohw-state-toggle]")) return;
18776
19671
  if (target.closest("[data-ohw-max-badge]")) return;
18777
19672
  if (isInsideLinkEditor(target)) return;
18778
- if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
18779
- clearMediaSelectionRef.current();
18780
- }
18781
19673
  if (isInsideFloatingPanel(target)) return;
18782
19674
  if (target.closest("[data-ohw-form-toolbar]")) return;
18783
19675
  if (target.closest(
@@ -18785,6 +19677,9 @@ function OhhwellsBridge() {
18785
19677
  )) {
18786
19678
  return;
18787
19679
  }
19680
+ if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
19681
+ clearMediaSelectionRef.current();
19682
+ }
18788
19683
  {
18789
19684
  const formEl = getFormElement(target);
18790
19685
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -18907,7 +19802,19 @@ function OhhwellsBridge() {
18907
19802
  openLogoSizePanelRef.current(logoEl);
18908
19803
  return;
18909
19804
  }
18910
- const editable = target.closest("[data-ohw-editable]");
19805
+ let editable = target.closest("[data-ohw-editable]");
19806
+ if (!editable && target instanceof SVGElement) {
19807
+ const inner = target.closest("svg")?.querySelectorAll("[data-ohw-editable]");
19808
+ if (inner && inner.length === 1) editable = inner[0];
19809
+ }
19810
+ if (editable && !(editable instanceof HTMLElement)) {
19811
+ e.preventDefault();
19812
+ e.stopPropagation();
19813
+ deselectRef.current();
19814
+ deactivateRef.current();
19815
+ aiSectionApiRef.current?.selectFromElement(editable);
19816
+ return;
19817
+ }
18911
19818
  if (editable) {
18912
19819
  if (editable.dataset.ohwEditable === "link") {
18913
19820
  e.preventDefault();
@@ -18936,14 +19843,6 @@ function OhhwellsBridge() {
18936
19843
  }
18937
19844
  const clickedButton = findClosestButtonLike(target);
18938
19845
  const buttonOnMedia = Boolean(clickedButton && isMediaEditable(editable) && editable.contains(clickedButton));
18939
- console.log("[click-debug]", {
18940
- editableType: editable.dataset.ohwEditable,
18941
- editableTag: editable.tagName,
18942
- targetTag: target.tagName,
18943
- clickedButtonTag: clickedButton?.tagName ?? null,
18944
- buttonOnMedia,
18945
- isMediaEditableEditable: isMediaEditable(editable)
18946
- });
18947
19846
  if (isMediaEditable(editable) && !buttonOnMedia) {
18948
19847
  e.preventDefault();
18949
19848
  e.stopPropagation();
@@ -18970,11 +19869,6 @@ function OhhwellsBridge() {
18970
19869
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
18971
19870
  const hrefCtx = getHrefKeyFromElement(hrefLookupTarget);
18972
19871
  const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
18973
- console.log("[click-debug 2]", {
18974
- hrefLookupTargetTag: hrefLookupTarget.tagName,
18975
- hrefCtx: hrefCtx ? { key: hrefCtx.key } : null,
18976
- navAnchorTag: navAnchor?.tagName ?? null
18977
- });
18978
19872
  if (navAnchor) {
18979
19873
  e.preventDefault();
18980
19874
  e.stopPropagation();
@@ -19144,6 +20038,9 @@ function OhhwellsBridge() {
19144
20038
  setHoveredItemRect(null);
19145
20039
  hoveredNavContainerRef.current = null;
19146
20040
  setHoveredNavContainerRect(null);
20041
+ siblingHintElRef.current = null;
20042
+ setSiblingHintRect(null);
20043
+ setSiblingHintRects([]);
19147
20044
  return;
19148
20045
  }
19149
20046
  {
@@ -19262,7 +20159,6 @@ function OhhwellsBridge() {
19262
20159
  hoveredNavContainerRef.current = null;
19263
20160
  setHoveredNavContainerRect(null);
19264
20161
  hoveredItemElRef.current = editable;
19265
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
19266
20162
  }
19267
20163
  }
19268
20164
  }
@@ -19559,7 +20455,7 @@ function OhhwellsBridge() {
19559
20455
  }
19560
20456
  };
19561
20457
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
19562
- if (linkPopoverOpenRef.current) {
20458
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
19563
20459
  if (hoveredImageRef.current) {
19564
20460
  hoveredImageRef.current = null;
19565
20461
  hoveredImageHasTextOverlapRef.current = false;
@@ -19648,6 +20544,15 @@ function OhhwellsBridge() {
19648
20544
  }
19649
20545
  return;
19650
20546
  }
20547
+ const cardHit = !isDragOver && Array.from(document.querySelectorAll(".card, [data-ohw-card]")).some((card) => {
20548
+ if (card.contains(imgEl)) return false;
20549
+ const r2 = card.getBoundingClientRect();
20550
+ return x2 >= r2.left && x2 <= r2.right && y2 >= r2.top && y2 <= r2.bottom;
20551
+ });
20552
+ if (cardHit) {
20553
+ dismissImageHover();
20554
+ return;
20555
+ }
19651
20556
  const topEl = document.elementFromPoint(x2, y2);
19652
20557
  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"]')) {
19653
20558
  if (hoveredImageRef.current) {
@@ -19924,8 +20829,7 @@ function OhhwellsBridge() {
19924
20829
  };
19925
20830
  const handleMouseMove = (e) => {
19926
20831
  const { clientX, clientY } = e;
19927
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
19928
- if (isOverEditorChrome(clientX, clientY)) {
20832
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
19929
20833
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
19930
20834
  formHoverElRef.current = null;
19931
20835
  setFormHoverRect(null);
@@ -19933,6 +20837,12 @@ function OhhwellsBridge() {
19933
20837
  setHoveredItemRect(null);
19934
20838
  hoveredNavContainerRef.current = null;
19935
20839
  setHoveredNavContainerRect(null);
20840
+ siblingHintElRef.current = null;
20841
+ setSiblingHintRect(null);
20842
+ setSiblingHintRects([]);
20843
+ dismissImageHover();
20844
+ clearImageHover();
20845
+ setSectionGap(null);
19936
20846
  return;
19937
20847
  }
19938
20848
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -19944,7 +20854,11 @@ function OhhwellsBridge() {
19944
20854
  if (e.data?.type !== "ow:pointer-sync") return;
19945
20855
  const { clientX, clientY } = e.data;
19946
20856
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
19947
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
20857
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
20858
+ dismissImageHover();
20859
+ clearImageHover();
20860
+ return;
20861
+ }
19948
20862
  if (probeSocialsRowAt(clientX, clientY)) return;
19949
20863
  probeSectionGapAt(clientX, clientY);
19950
20864
  probeImageAt(clientX, clientY);
@@ -20058,7 +20972,11 @@ function OhhwellsBridge() {
20058
20972
  const slotFromItem = hrefKey ? findSocialByHrefKey(hrefKey)?.querySelector('[data-ohw-editable="icon"]') : null;
20059
20973
  const requestedIsIconSlot = Boolean(requestedIconKey) && Boolean(document.querySelector(`[data-ohw-key="${requestedIconKey}"][data-ohw-editable="icon"]`));
20060
20974
  const requestedIsFree = Boolean(requestedIconKey) && !document.querySelector(`[data-ohw-key="${requestedIconKey}"]`);
20061
- const iconKey = requestedIsIconSlot ? requestedIconKey : slotFromItem?.dataset.ohwKey ?? (requestedIsFree ? requestedIconKey : void 0);
20975
+ let iconKey = requestedIsIconSlot ? requestedIconKey : slotFromItem?.dataset.ohwKey ?? (requestedIsFree ? requestedIconKey : void 0);
20976
+ const item = hrefKey ? findSocialByHrefKey(hrefKey) : null;
20977
+ if (item && typeof iconMarkup === "string" && iconMarkup) {
20978
+ iconKey = ensureIconSlot(item) ?? iconKey;
20979
+ }
20062
20980
  if (iconKey && typeof iconMarkup === "string" && iconMarkup) {
20063
20981
  document.querySelectorAll(`[data-ohw-key="${iconKey}"][data-ohw-editable="icon"]`).forEach((el) => {
20064
20982
  applyIconMarkup(el, iconMarkup);
@@ -20068,7 +20986,7 @@ function OhhwellsBridge() {
20068
20986
  }
20069
20987
  if (iconKey && platformId) nodes.push({ key: socialPlatformKey(iconKey), text: platformId });
20070
20988
  if (iconKey && label) {
20071
- const labelKey = socialLabelKey(iconKey);
20989
+ const labelKey = (item ? socialLabelElement(item)?.getAttribute("data-ohw-key") : null) ?? socialLabelKey(iconKey);
20072
20990
  document.querySelectorAll(`[data-ohw-key="${labelKey}"]`).forEach((el) => {
20073
20991
  if (keepLabel && el.textContent?.trim()) return;
20074
20992
  el.textContent = label;
@@ -20223,6 +21141,44 @@ function OhhwellsBridge() {
20223
21141
  if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
20224
21142
  }, 400));
20225
21143
  };
21144
+ const reapCommittedAiSections = (excludeIds) => {
21145
+ const aiState = parseAiSectionsState(aiSectionsRef.current);
21146
+ if (aiState.sections.length === 0) return [];
21147
+ let orderEntries = [];
21148
+ try {
21149
+ const parsed = JSON.parse(editContentRef.current[SECTION_ORDER_KEY] ?? "[]");
21150
+ if (Array.isArray(parsed)) orderEntries = parsed;
21151
+ } catch {
21152
+ return [];
21153
+ }
21154
+ const removedIds = orderEntries.filter((entry) => entry && typeof entry.instanceId === "string" && entry.removed).map((entry) => entry.instanceId);
21155
+ if (removedIds.length === 0) return [];
21156
+ const result = reapRemovedAiSections(aiState, parseStyleStore(stylesRef.current), removedIds, excludeIds);
21157
+ if (!result.changed) return [];
21158
+ const nodes = [];
21159
+ aiSectionsRef.current = serializeAiSectionsState(result.state);
21160
+ nodes.push({ key: AI_SECTIONS_KEY, text: aiSectionsRef.current });
21161
+ const reaped = new Set(result.reapedIds);
21162
+ const nextOrderJson = JSON.stringify(orderEntries.filter((entry) => !reaped.has(entry.instanceId)));
21163
+ editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: nextOrderJson };
21164
+ setAiSectionOrder(nextOrderJson, window.location.pathname);
21165
+ nodes.push({ key: SECTION_ORDER_KEY, text: nextOrderJson });
21166
+ if (result.store) {
21167
+ stylesRef.current = JSON.stringify(result.store);
21168
+ nodes.push({ key: STYLE_STORE_KEY, text: stylesRef.current });
21169
+ }
21170
+ const nextContent = { ...editContentRef.current };
21171
+ for (const key of Object.keys(nextContent)) {
21172
+ if (key.startsWith(AI_SLOT_KEY_PREFIX) && result.slotPrefixes.some((prefix) => key.startsWith(prefix)) && nextContent[key] !== "") {
21173
+ nextContent[key] = "";
21174
+ nodes.push({ key, text: "" });
21175
+ }
21176
+ }
21177
+ editContentRef.current = nextContent;
21178
+ applyAiSectionsToDom(result.state);
21179
+ applyStylesToDom(parseStyleStore(stylesRef.current));
21180
+ return nodes;
21181
+ };
20226
21182
  const handleHydrate = (e) => {
20227
21183
  if (e.data?.type !== "ow:hydrate") return;
20228
21184
  const content = e.data.content;
@@ -20241,6 +21197,7 @@ function OhhwellsBridge() {
20241
21197
  }
20242
21198
  if (typeof content[STYLE_STORE_KEY] === "string") {
20243
21199
  stylesRef.current = content[STYLE_STORE_KEY];
21200
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
20244
21201
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
20245
21202
  }
20246
21203
  applyBrandChrome(content);
@@ -20252,11 +21209,11 @@ function OhhwellsBridge() {
20252
21209
  continue;
20253
21210
  }
20254
21211
  if (key === AI_SECTIONS_KEY) continue;
21212
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
21213
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
20255
21214
  if (key === BRAND_KIT_KEY) continue;
20256
21215
  if (key === STYLE_STORE_KEY) continue;
20257
21216
  if (BRAND_CHROME_KEYS.has(key)) continue;
20258
- if (key === LOGO_PLACEHOLDER_KEY) continue;
20259
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
20260
21217
  if (applyVideoSettingNode(key, val)) continue;
20261
21218
  if (applyCarouselNode(key, val)) continue;
20262
21219
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -20270,6 +21227,8 @@ function OhhwellsBridge() {
20270
21227
  if (video && video.src !== val) applyVideoSrc(video, val);
20271
21228
  } else if (el.dataset.ohwEditable === "link") {
20272
21229
  applyLinkHref(el, val);
21230
+ } else if (el.dataset.ohwEditable === "map") {
21231
+ applyMapQuery(el, val);
20273
21232
  } else if (el.dataset.ohwEditable === "icon") {
20274
21233
  applyIconMarkup(el, val);
20275
21234
  } else if (isIconMarkupValue(val)) {
@@ -20290,9 +21249,20 @@ function OhhwellsBridge() {
20290
21249
  reconcileNavbarItemsFromContent(editContentRef.current);
20291
21250
  reconcileFooterOrderFromContent(editContentRef.current);
20292
21251
  syncNavigationDragCursorAttrs();
21252
+ if (typeof content[STYLE_STORE_KEY] === "string") {
21253
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
21254
+ }
20293
21255
  enforceLinkHrefs();
21256
+ const hydrateReapExclude = /* @__PURE__ */ new Set();
21257
+ const hydratePendingUndo = pendingDeleteUndoRef.current;
21258
+ if (hydratePendingUndo?.sectionInstanceId) hydrateReapExclude.add(hydratePendingUndo.sectionInstanceId);
21259
+ const reapNodes = reapCommittedAiSections(hydrateReapExclude);
21260
+ if (reapNodes.length > 0) postToParentRef.current({ type: "ow:change", nodes: reapNodes });
20294
21261
  const hydratedHeight = document.body.scrollHeight;
20295
21262
  if (hydratedHeight > 50) postToParentRef.current({ type: "ow:height", height: hydratedHeight });
21263
+ if (parseAiSectionsState(aiSectionsRef.current).sections.length > 0) {
21264
+ postAiSectionsChanged();
21265
+ }
20296
21266
  postToParentRef.current({ type: "ow:hydrate-done" });
20297
21267
  };
20298
21268
  const handleUpdateLogoIdentity = (e) => {
@@ -20464,10 +21434,26 @@ function OhhwellsBridge() {
20464
21434
  const handleGetBrand = (e) => {
20465
21435
  if (e.data?.type !== "ow:get-brand") return;
20466
21436
  const template = deriveTemplateBrand();
20467
- const value = brandKitRef.current || (template ? JSON.stringify(template) : "");
21437
+ const fallback = template ?? (() => {
21438
+ const fonts = deriveTemplateFonts();
21439
+ return fonts ? { palette: AI_DEFAULT_BRAND.palette, fonts } : null;
21440
+ })();
21441
+ const value = brandKitRef.current || (fallback ? JSON.stringify(fallback) : "");
20468
21442
  postToParentRef.current({ type: "ow:brand-value", value });
20469
21443
  };
20470
21444
  window.addEventListener("message", handleGetBrand);
21445
+ const handleGetTemplateFonts = (e) => {
21446
+ if (e.data?.type !== "ow:get-template-fonts") return;
21447
+ const fonts = deriveTemplateFonts();
21448
+ postToParentRef.current({ type: "ow:template-fonts-value", value: fonts ? JSON.stringify(fonts) : "" });
21449
+ };
21450
+ window.addEventListener("message", handleGetTemplateFonts);
21451
+ const handleGetTemplateBrand = (e) => {
21452
+ if (e.data?.type !== "ow:get-template-brand") return;
21453
+ const brand = deriveTemplateBrand();
21454
+ postToParentRef.current({ type: "ow:template-brand-value", value: brand ? JSON.stringify(brand) : "" });
21455
+ };
21456
+ window.addEventListener("message", handleGetTemplateBrand);
20471
21457
  const handleMoveSection = (e) => {
20472
21458
  if (e.data?.type !== "ow:move-section") return;
20473
21459
  const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
@@ -20475,8 +21461,11 @@ function OhhwellsBridge() {
20475
21461
  if (!instanceId || !direction) return;
20476
21462
  const entries = moveSectionInstance(instanceId, direction, window.location.pathname);
20477
21463
  if (!entries) return;
20478
- const orderJson = JSON.stringify(entries);
21464
+ const orderJson = JSON.stringify(
21465
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
21466
+ );
20479
21467
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
21468
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
20480
21469
  setAiSectionOrder(orderJson, window.location.pathname);
20481
21470
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20482
21471
  window.dispatchEvent(new Event("resize"));
@@ -20522,8 +21511,11 @@ function OhhwellsBridge() {
20522
21511
  const currentEntries = getPageSectionOrderEntries(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname);
20523
21512
  const entries = deleteSectionInstance(instanceId, window.location.pathname, currentEntries);
20524
21513
  if (!entries) return;
20525
- const orderJson = JSON.stringify(entries);
21514
+ const orderJson = JSON.stringify(
21515
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
21516
+ );
20526
21517
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
21518
+ setAiSectionOrder(orderJson, window.location.pathname);
20527
21519
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20528
21520
  aiSectionApiRef.current?.clear();
20529
21521
  window.dispatchEvent(new Event("resize"));
@@ -20532,6 +21524,7 @@ function OhhwellsBridge() {
20532
21524
  const actionId = newInstanceId();
20533
21525
  pendingDeleteUndoRef.current = {
20534
21526
  actionId,
21527
+ sectionInstanceId: instanceId,
20535
21528
  restore: () => {
20536
21529
  const restoredEntries = getPageSectionOrderEntries(
20537
21530
  editContentRef.current[SECTION_ORDER_KEY],
@@ -20539,8 +21532,11 @@ function OhhwellsBridge() {
20539
21532
  );
20540
21533
  const restored = restoreSectionInstance(instanceId, window.location.pathname, restoredEntries);
20541
21534
  if (!restored) return;
20542
- const restoredJson = JSON.stringify(restored);
21535
+ const restoredJson = JSON.stringify(
21536
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, restored)
21537
+ );
20543
21538
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: restoredJson };
21539
+ setAiSectionOrder(restoredJson, window.location.pathname);
20544
21540
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: restoredJson }] });
20545
21541
  window.dispatchEvent(new Event("resize"));
20546
21542
  const restoreHeight = document.body.scrollHeight;
@@ -20566,7 +21562,9 @@ function OhhwellsBridge() {
20566
21562
  const result = duplicateSectionInstance(instanceId, newId, window.location.pathname, currentEntries);
20567
21563
  if (!result) return;
20568
21564
  const { entries, keyRekeys } = result;
20569
- const orderJson = JSON.stringify(entries);
21565
+ const orderJson = JSON.stringify(
21566
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
21567
+ );
20570
21568
  const nodes = [{ key: SECTION_ORDER_KEY, text: orderJson }];
20571
21569
  for (const { from, to } of keyRekeys) {
20572
21570
  const inherited = editContentRef.current[from];
@@ -20576,6 +21574,7 @@ function OhhwellsBridge() {
20576
21574
  ...editContentRef.current,
20577
21575
  ...Object.fromEntries(nodes.map(({ key, text }) => [key, text]))
20578
21576
  };
21577
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
20579
21578
  setAiSectionOrder(orderJson, window.location.pathname);
20580
21579
  postToParentRef.current({ type: "ow:change", nodes });
20581
21580
  window.dispatchEvent(new Event("resize"));
@@ -20594,6 +21593,12 @@ function OhhwellsBridge() {
20594
21593
  closeLinkPopoverRef.current();
20595
21594
  return;
20596
21595
  }
21596
+ if (floatingPanelOpenRef.current) {
21597
+ setFloatingPanelRef.current(null);
21598
+ deselectRef.current();
21599
+ deactivateRef.current();
21600
+ return;
21601
+ }
20597
21602
  deselectRef.current();
20598
21603
  deactivateRef.current();
20599
21604
  clearMediaSelectionRef.current();
@@ -20826,7 +21831,12 @@ function OhhwellsBridge() {
20826
21831
  }
20827
21832
  if (navDragRef.current) {
20828
21833
  const session = navDragRef.current;
20829
- const slot = hitTestNavDropSlot(session.lastClientX, session.lastClientY, session.hrefKey);
21834
+ const slot = hitTestNavDropSlot(
21835
+ session.lastClientX,
21836
+ session.lastClientY,
21837
+ session.hrefKey,
21838
+ session.draggedEl
21839
+ );
20830
21840
  refreshNavDragVisualsRef.current(session, slot, session.lastClientX, session.lastClientY);
20831
21841
  }
20832
21842
  if (hoveredImageRef.current) {
@@ -20839,6 +21849,10 @@ function OhhwellsBridge() {
20839
21849
  };
20840
21850
  const handleSave = (e) => {
20841
21851
  if (e.data?.type !== "ow:save") return;
21852
+ const pendingUndo = pendingDeleteUndoRef.current;
21853
+ const reapExclude = /* @__PURE__ */ new Set();
21854
+ if (pendingUndo?.sectionInstanceId) reapExclude.add(pendingUndo.sectionInstanceId);
21855
+ const reapNodes = reapCommittedAiSections(reapExclude);
20842
21856
  const nodes = collectEditableNodes(editContentRef.current);
20843
21857
  const tracker = document.querySelector("[data-ohw-sections-tracker]");
20844
21858
  if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
@@ -20858,6 +21872,11 @@ function OhhwellsBridge() {
20858
21872
  const text = specs.length ? JSON.stringify(specs) : editContentRef.current[fieldsKey(formKey)];
20859
21873
  if (text) nodes.push({ key: fieldsKey(formKey), type: "text", text });
20860
21874
  });
21875
+ for (const reapNode of reapNodes) {
21876
+ if (reapNode.key.startsWith(AI_SLOT_KEY_PREFIX) && !nodes.some((n) => n.key === reapNode.key)) {
21877
+ nodes.push({ key: reapNode.key, type: "text", text: reapNode.text });
21878
+ }
21879
+ }
20861
21880
  postToParentRef.current({ type: "ow:save-result", nodes });
20862
21881
  };
20863
21882
  const handleInsertSection = (e) => {
@@ -20868,8 +21887,12 @@ function OhhwellsBridge() {
20868
21887
  if (inserted) {
20869
21888
  const tracker = getSectionsTracker();
20870
21889
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
20871
- const h = document.body.scrollHeight;
20872
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
21890
+ const reportHeight = () => {
21891
+ const h = document.body.scrollHeight;
21892
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
21893
+ };
21894
+ reportHeight();
21895
+ setTimeout(reportHeight, 500);
20873
21896
  }
20874
21897
  };
20875
21898
  const handleSwitchSchedule = (e) => {
@@ -21019,18 +22042,21 @@ function OhhwellsBridge() {
21019
22042
  }
21020
22043
  return null;
21021
22044
  };
22045
+ const isPointInRect = (el, clientX, clientY) => {
22046
+ const r2 = el.getBoundingClientRect();
22047
+ return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
22048
+ };
21022
22049
  const isPointOverEditable = (scope, clientX, clientY) => {
21023
22050
  const editables = scope.querySelectorAll("[data-ohw-editable], [data-ohw-href-key]");
21024
22051
  for (const el of editables) {
21025
- const r2 = el.getBoundingClientRect();
21026
- if (clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom) return true;
22052
+ if (isPointInRect(el, clientX, clientY)) return true;
21027
22053
  }
21028
22054
  return false;
21029
22055
  };
21030
22056
  const handleCarouselHover = (e) => {
21031
22057
  const container = findCarouselAtPoint(e.clientX, e.clientY);
21032
22058
  const scope = container?.closest("[data-ohw-section]") ?? container?.parentElement ?? null;
21033
- if (!container || !scope || isPointOverEditable(scope, e.clientX, e.clientY)) {
22059
+ if (!container || !scope || isPointOverEditable(scope, e.clientX, e.clientY) || isPointOverNavigation(e.clientX, e.clientY) || isPointOverBridgeChrome(e.clientX, e.clientY)) {
21034
22060
  setCarouselHover((prev) => prev ? null : prev);
21035
22061
  return;
21036
22062
  }
@@ -21268,15 +22294,17 @@ function OhhwellsBridge() {
21268
22294
  window.removeEventListener("message", handleAiSetBrand);
21269
22295
  window.removeEventListener("message", handleAiSetStyles);
21270
22296
  window.removeEventListener("message", handleGetBrand);
22297
+ window.removeEventListener("message", handleGetTemplateFonts);
22298
+ window.removeEventListener("message", handleGetTemplateBrand);
21271
22299
  window.removeEventListener("message", handleMoveSection);
21272
22300
  window.removeEventListener("message", handlePanelDragging);
21273
22301
  window.removeEventListener("message", handleDeleteSection);
21274
22302
  window.removeEventListener("message", handleDuplicateSection);
21275
22303
  window.removeEventListener("message", handleDeactivate);
21276
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
21277
22304
  window.removeEventListener("message", handleToastAction);
21278
22305
  window.removeEventListener("message", handleFormCount);
21279
22306
  window.removeEventListener("message", handleUiEscape);
22307
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
21280
22308
  autoSaveTimers.current.forEach(clearTimeout);
21281
22309
  autoSaveTimers.current.clear();
21282
22310
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -21479,7 +22507,7 @@ function OhhwellsBridge() {
21479
22507
  postToParent2({
21480
22508
  type: "ow:ready",
21481
22509
  version: "1",
21482
- bridgeVersion: "0.1.91",
22510
+ bridgeVersion: "0.1.93",
21483
22511
  path: pathname,
21484
22512
  nodes: collectEditableNodes(editContentRef.current),
21485
22513
  sections
@@ -22095,7 +23123,7 @@ function OhhwellsBridge() {
22095
23123
  "span",
22096
23124
  {
22097
23125
  "data-ohw-form-count": "",
22098
- 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",
23126
+ 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",
22099
23127
  children: formPickCount
22100
23128
  }
22101
23129
  )
@@ -22332,11 +23360,11 @@ function OhhwellsBridge() {
22332
23360
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
22333
23361
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
22334
23362
  children: [
22335
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
23363
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } }),
22336
23364
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
22337
23365
  Badge,
22338
23366
  {
22339
- 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",
23367
+ 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",
22340
23368
  onClick: () => {
22341
23369
  window.parent.postMessage(
22342
23370
  {
@@ -22350,7 +23378,7 @@ function OhhwellsBridge() {
22350
23378
  children: "Add Section"
22351
23379
  }
22352
23380
  ),
22353
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
23381
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } })
22354
23382
  ]
22355
23383
  }
22356
23384
  ),
@@ -22398,6 +23426,59 @@ function OhhwellsBridge() {
22398
23426
  ) : null
22399
23427
  ] });
22400
23428
  }
23429
+
23430
+ // src/ui/EmptySection.tsx
23431
+ var import_link = __toESM(require("next/link"), 1);
23432
+ var import_jsx_runtime34 = require("react/jsx-runtime");
23433
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
23434
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
23435
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
23436
+ "p",
23437
+ {
23438
+ style: {
23439
+ fontFamily: "var(--brand-font-body)",
23440
+ fontSize: "0.75rem",
23441
+ fontWeight: 500,
23442
+ letterSpacing: "0.15em",
23443
+ textTransform: "uppercase",
23444
+ color: "var(--brand-accent)",
23445
+ marginBottom: "1.5rem"
23446
+ },
23447
+ children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_link.default, { href: homeHref, style: { color: "inherit" }, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { ...eyebrowKey ? { "data-ohw-editable": "text", "data-ohw-key": eyebrowKey } : {}, children: "Home" }) })
23448
+ }
23449
+ ),
23450
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
23451
+ "h1",
23452
+ {
23453
+ style: {
23454
+ fontFamily: "var(--brand-font-heading)",
23455
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
23456
+ lineHeight: 1.1,
23457
+ letterSpacing: "-0.025em",
23458
+ color: "var(--brand-text)",
23459
+ marginBottom: "1rem"
23460
+ },
23461
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
23462
+ children: title
23463
+ }
23464
+ ),
23465
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
23466
+ "p",
23467
+ {
23468
+ style: {
23469
+ fontFamily: "var(--brand-font-body)",
23470
+ fontSize: "1rem",
23471
+ lineHeight: 1.7,
23472
+ fontWeight: 300,
23473
+ color: "var(--brand-text-muted)",
23474
+ maxWidth: "340px"
23475
+ },
23476
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
23477
+ children: "This page doesn't have any content yet."
23478
+ }
23479
+ )
23480
+ ] });
23481
+ }
22401
23482
  // Annotate the CommonJS export names for ESM import in node:
22402
23483
  0 && (module.exports = {
22403
23484
  AI_DEFAULT_BRAND,
@@ -22415,6 +23496,7 @@ function OhhwellsBridge() {
22415
23496
  DropdownMenuItem,
22416
23497
  DropdownMenuSeparator,
22417
23498
  DropdownMenuTrigger,
23499
+ EmptySection,
22418
23500
  ItemActionToolbar,
22419
23501
  ItemInteractionLayer,
22420
23502
  LinkEditorPanel,