@ohhwells/bridge 0.1.92 → 0.1.93-next.277

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,10 @@ 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
+ );
464
561
  for (const align of ["left", "center", "right"]) {
465
562
  rules.push(`[data-ohw-style-align="${align}"] { text-align: ${align} !important; }`);
466
563
  }
@@ -491,6 +588,7 @@ var SECTION_ATTRS = {
491
588
  headlineScale: "data-ohw-style-headline",
492
589
  imageAspect: "data-ohw-style-aspect",
493
590
  spacing: "data-ohw-style-spacing",
591
+ cornerStyle: "data-ohw-style-corners",
494
592
  align: "data-ohw-style-align"
495
593
  };
496
594
  var NODE_WROTE_ATTR = "data-ohw-style-node";
@@ -632,9 +730,535 @@ function applyStylesToDom(store) {
632
730
  var import_react_dom = require("react-dom");
633
731
  var import_client = require("react-dom/client");
634
732
 
733
+ // src/lib/sections.ts
734
+ var LINK_PICKER_EXCLUDED_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
735
+ function isChromeSection(el) {
736
+ return el.matches("header, nav, footer, aside");
737
+ }
738
+ function titleCaseSectionId(id) {
739
+ return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
740
+ }
741
+ function parseSectionsFromRoot(root) {
742
+ const seen = /* @__PURE__ */ new Set();
743
+ const sections = [];
744
+ for (const el of root.querySelectorAll("[data-ohw-section]")) {
745
+ const id = el.getAttribute("data-ohw-section") ?? "";
746
+ if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
747
+ if (el.parentElement?.closest("[data-ohw-section]")) continue;
748
+ if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
749
+ continue;
750
+ seen.add(id);
751
+ const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
752
+ sections.push({ id, label });
753
+ }
754
+ return sections;
755
+ }
756
+ function collectSectionsFromDom() {
757
+ if (typeof document === "undefined") return [];
758
+ return parseSectionsFromRoot(document);
759
+ }
760
+ function parseSectionsFromHtml(html) {
761
+ const doc = new DOMParser().parseFromString(html, "text/html");
762
+ return parseSectionsFromRoot(doc);
763
+ }
764
+
765
+ // src/lib/section-instances.ts
766
+ var SECTION_ORDER_KEY = "__ohw_section_order";
767
+ var REMOVED_ATTR = "data-ohw-section-removed";
768
+ function isRemovedSection(el) {
769
+ return el.hasAttribute(REMOVED_ATTR);
770
+ }
771
+ function movableUnit(el) {
772
+ return el.closest("[data-ohw-section-container]") ?? el;
773
+ }
774
+ function sectionTypeOf(el) {
775
+ return el.getAttribute("data-ohw-section") ?? el.querySelector("[data-ohw-section]")?.getAttribute("data-ohw-section") ?? "";
776
+ }
777
+ function sectionElementOf(el) {
778
+ return el.hasAttribute("data-ohw-section") ? el : el.querySelector("[data-ohw-section]") ?? el;
779
+ }
780
+ function collectTopLevelUnits(predicate) {
781
+ const seen = /* @__PURE__ */ new Set();
782
+ const result = [];
783
+ document.querySelectorAll("[data-ohw-section]").forEach((el) => {
784
+ if (!predicate(el)) return;
785
+ const unit = movableUnit(el);
786
+ if (unit.parentElement?.closest("[data-ohw-section],[data-ohw-section-container]")) return;
787
+ if (seen.has(unit)) return;
788
+ seen.add(unit);
789
+ result.push(unit);
790
+ });
791
+ return result;
792
+ }
793
+ function topLevelSections() {
794
+ return collectTopLevelUnits((el) => !isChromeSection(el) && !isRemovedSection(movableUnit(el)));
795
+ }
796
+ function instanceIdOf(el) {
797
+ return el.dataset.ohwInstance ?? el.dataset.ohwSection ?? "";
798
+ }
799
+ function findByInstanceId(instanceId) {
800
+ const escapedId = CSS.escape(instanceId);
801
+ const direct = document.querySelector(`[data-ohw-instance="${escapedId}"]`);
802
+ if (direct) return movableUnit(direct);
803
+ const bare = document.querySelector(`[data-ohw-section="${escapedId}"]:not([data-ohw-instance])`);
804
+ return bare ? movableUnit(bare) : null;
805
+ }
806
+ function planSectionMove(instanceId, targetIndex, currentPath) {
807
+ const sections = topLevelSections();
808
+ const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
809
+ if (index === -1) return null;
810
+ const dragged = sections[index];
811
+ const others = sections.filter((_, i) => i !== index);
812
+ const clamped = Math.max(0, Math.min(targetIndex, others.length));
813
+ const reordered = [...others.slice(0, clamped), dragged, ...others.slice(clamped)];
814
+ return reordered.map((el, order) => ({
815
+ instanceId: instanceIdOf(el),
816
+ type: sectionTypeOf(el),
817
+ order,
818
+ pagePath: currentPath
819
+ }));
820
+ }
821
+ function moveSectionInstance(instanceId, direction, currentPath) {
822
+ const sections = topLevelSections();
823
+ const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
824
+ if (index === -1) return null;
825
+ const siblingIndex = direction === "up" ? index - 1 : index + 1;
826
+ if (siblingIndex < 0 || siblingIndex >= sections.length) return null;
827
+ const entries = planSectionMove(instanceId, siblingIndex, currentPath);
828
+ if (!entries) return null;
829
+ applyPersistedOrder(entries);
830
+ return entries;
831
+ }
832
+ function syncRemovedFlags(entries) {
833
+ const removedIds = new Set(entries.filter((e) => e.removed).map((e) => e.instanceId));
834
+ document.querySelectorAll(`[${REMOVED_ATTR}]`).forEach((el) => {
835
+ if (!removedIds.has(instanceIdOf(el))) {
836
+ el.style.removeProperty("display");
837
+ el.removeAttribute(REMOVED_ATTR);
838
+ }
839
+ });
840
+ for (const id of removedIds) {
841
+ const el = findByInstanceId(id);
842
+ if (el) {
843
+ el.style.display = "none";
844
+ el.setAttribute(REMOVED_ATTR, "");
845
+ }
846
+ }
847
+ }
848
+ function applyPersistedOrder(entries) {
849
+ syncRemovedFlags(entries);
850
+ if (entries.length === 0) return;
851
+ const sections = topLevelSections();
852
+ if (sections.length === 0) return;
853
+ const orderIndex = new Map(entries.map((e) => [e.instanceId, e.order]));
854
+ const ordered = [...sections].sort((a, b) => {
855
+ const aOrder = orderIndex.get(instanceIdOf(a));
856
+ const bOrder = orderIndex.get(instanceIdOf(b));
857
+ if (aOrder === void 0 && bOrder === void 0) return 0;
858
+ if (aOrder === void 0) return 1;
859
+ if (bOrder === void 0) return -1;
860
+ return aOrder - bOrder;
861
+ });
862
+ let prev = null;
863
+ for (const el of ordered) {
864
+ if (prev) prev.after(el);
865
+ prev = el;
866
+ }
867
+ }
868
+ function setSectionRemoved(instanceId, currentPath, existingEntries, removed) {
869
+ if (!findByInstanceId(instanceId)) return null;
870
+ const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
871
+ const allSections = collectTopLevelUnits((el) => !isChromeSection(el));
872
+ allSections.forEach((el, order) => {
873
+ const id = instanceIdOf(el);
874
+ if (!byId.has(id)) {
875
+ byId.set(id, { instanceId: id, type: sectionTypeOf(el), order, pagePath: currentPath });
876
+ }
877
+ });
878
+ const target = byId.get(instanceId);
879
+ if (!target) return null;
880
+ byId.set(instanceId, { ...target, removed });
881
+ const entries = Array.from(byId.values());
882
+ applyPersistedOrder(entries);
883
+ return entries;
884
+ }
885
+ function deleteSectionInstance(instanceId, currentPath, existingEntries) {
886
+ return setSectionRemoved(instanceId, currentPath, existingEntries, true);
887
+ }
888
+ function restoreSectionInstance(instanceId, currentPath, existingEntries) {
889
+ return setSectionRemoved(instanceId, currentPath, existingEntries, false);
890
+ }
891
+ function duplicateSectionInstance(instanceId, newId, currentPath, existingEntries) {
892
+ const original = findByInstanceId(instanceId);
893
+ if (!original) return null;
894
+ const clone = original.cloneNode(true);
895
+ clone.setAttribute("data-ohw-instance", newId);
896
+ const keyRekeys = rekeySectionSubtree(clone, newId);
897
+ original.insertAdjacentElement("afterend", clone);
898
+ const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
899
+ const entries = topLevelSections().map((el, order) => {
900
+ const id = instanceIdOf(el);
901
+ return {
902
+ instanceId: id,
903
+ type: sectionTypeOf(el),
904
+ order,
905
+ pagePath: currentPath,
906
+ ...byId.get(id)?.removed ? { removed: true } : {}
907
+ };
908
+ });
909
+ applyPersistedOrder(entries);
910
+ return { entries, keyRekeys };
911
+ }
912
+ function newInstanceId() {
913
+ return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `instance-${Date.now()}-${Math.random().toString(36).slice(2)}`;
914
+ }
915
+ function getPageSectionOrderEntries(raw, currentPath) {
916
+ if (!raw) return [];
917
+ try {
918
+ const entries = JSON.parse(raw);
919
+ return entries.filter((e) => !e.pagePath || e.pagePath === currentPath);
920
+ } catch {
921
+ return [];
922
+ }
923
+ }
924
+ function mergePageSectionOrder(raw, currentPath, pageEntries) {
925
+ let all = [];
926
+ if (raw) {
927
+ try {
928
+ const parsed = JSON.parse(raw);
929
+ if (Array.isArray(parsed)) all = parsed;
930
+ } catch {
931
+ }
932
+ }
933
+ const otherPages = all.filter((e) => e && e.pagePath && e.pagePath !== currentPath);
934
+ const pageIds = new Set(pageEntries.map((e) => e.instanceId));
935
+ const removedHere = all.filter(
936
+ (e) => e && (!e.pagePath || e.pagePath === currentPath) && e.removed && !pageIds.has(e.instanceId)
937
+ );
938
+ return [...otherPages, ...removedHere, ...pageEntries];
939
+ }
940
+ function rekeySectionSubtree(root, instanceId) {
941
+ const suffix = `::${instanceId}`;
942
+ const pairs = [];
943
+ const rekey = (el, attr) => {
944
+ const current = el.getAttribute(attr);
945
+ if (!current) return;
946
+ const base = current.includes("::") ? current.slice(0, current.indexOf("::")) : current;
947
+ const next = `${base}${suffix}`;
948
+ el.setAttribute(attr, next);
949
+ pairs.push({ from: current, to: next });
950
+ };
951
+ if (root.hasAttribute("data-ohw-key")) rekey(root, "data-ohw-key");
952
+ if (root.hasAttribute("data-ohw-href-key")) rekey(root, "data-ohw-href-key");
953
+ root.querySelectorAll("[data-ohw-key]").forEach((el) => rekey(el, "data-ohw-key"));
954
+ root.querySelectorAll("[data-ohw-href-key]").forEach((el) => rekey(el, "data-ohw-href-key"));
955
+ return pairs;
956
+ }
957
+ function initSectionInstancesFromContent(content, currentPath) {
958
+ document.querySelectorAll("[data-ohw-section]:not([data-ohw-instance])").forEach((el) => {
959
+ el.setAttribute("data-ohw-instance", el.getAttribute("data-ohw-section") ?? "");
960
+ });
961
+ document.querySelectorAll("[data-ohw-section-container]:not([data-ohw-instance])").forEach((el) => {
962
+ const type = sectionTypeOf(el);
963
+ if (type) el.setAttribute("data-ohw-instance", type);
964
+ });
965
+ const entries = getPageSectionOrderEntries(content[SECTION_ORDER_KEY], currentPath);
966
+ for (const entry of entries) {
967
+ if (entry.instanceId === entry.type) continue;
968
+ if (document.querySelector(`[data-ohw-instance="${CSS.escape(entry.instanceId)}"]`)) continue;
969
+ const original = document.querySelector(
970
+ `[data-ohw-section="${CSS.escape(entry.type)}"][data-ohw-instance="${CSS.escape(entry.type)}"]`
971
+ );
972
+ if (!original) continue;
973
+ const clone = original.cloneNode(true);
974
+ clone.setAttribute("data-ohw-instance", entry.instanceId);
975
+ rekeySectionSubtree(clone, entry.instanceId);
976
+ original.insertAdjacentElement("afterend", clone);
977
+ }
978
+ applyPersistedOrder(entries);
979
+ }
980
+
635
981
  // src/ui/ai-tree/AiTreeRenderer.tsx
636
982
  var import_react = __toESM(require("react"), 1);
637
983
  var import_lucide_react = require("lucide-react");
984
+
985
+ // src/lib/placeholder-imagery.ts
986
+ var U = (id) => `https://images.unsplash.com/photo-${id}?auto=format&fit=crop&w=1600&q=80`;
987
+ var GENERIC = [
988
+ U("1441986300917-64674bd600d8"),
989
+ U("1486406146926-c627a92ad1ab"),
990
+ U("1497032628192-86f99bcd76bc"),
991
+ U("1521737604893-d14cc237f11d"),
992
+ U("1522071820081-009f0129c71c"),
993
+ U("1519389950473-47ba0277781c"),
994
+ U("1460925895917-afdab827c52f"),
995
+ U("1504384308090-c894fdcc538d")
996
+ ];
997
+ var PEOPLE = [
998
+ U("1500648767791-00dcc994a43e"),
999
+ U("1494790108377-be9c29b29330"),
1000
+ U("1507003211169-0a1dd7228f2d"),
1001
+ U("1438761681033-6461ffad8d80"),
1002
+ U("1544005313-94ddf0286df2"),
1003
+ U("1472099645785-5658abf4ff4e"),
1004
+ U("1519085360753-af0119f7cbe7"),
1005
+ U("1534528741775-53994a69daeb")
1006
+ ];
1007
+ var THEMED = [
1008
+ {
1009
+ keywords: ["portrait", "headshot", "person", "people", "team", "staff", "avatar", "founder", "face"],
1010
+ pool: PEOPLE
1011
+ },
1012
+ {
1013
+ keywords: ["pet", "dog", "cat", "puppy", "kitten", "vet", "animal"],
1014
+ pool: [
1015
+ U("1548199973-03cce0bbc87b"),
1016
+ U("1450778869180-41d0601e046e"),
1017
+ U("1583511655857-d19b40a7a54e"),
1018
+ U("1587300003388-59208cc962cb"),
1019
+ U("1517849845537-4d257902454a"),
1020
+ U("1601758228041-f3b2795255f1")
1021
+ ]
1022
+ },
1023
+ {
1024
+ keywords: [
1025
+ "baker",
1026
+ "bakery",
1027
+ "cafe",
1028
+ "coffee",
1029
+ "latte",
1030
+ "restaurant",
1031
+ "pastr",
1032
+ "bread",
1033
+ "cake",
1034
+ "cater",
1035
+ "chef",
1036
+ "kitchen",
1037
+ "food",
1038
+ "pizza",
1039
+ "dessert",
1040
+ "brunch",
1041
+ "bistro",
1042
+ "deli",
1043
+ "dish",
1044
+ "menu"
1045
+ ],
1046
+ pool: [
1047
+ U("1509440159596-0249088772ff"),
1048
+ U("1555507036-ab1f4038808a"),
1049
+ U("1517433670267-08bbd4be890f"),
1050
+ U("1486427944299-d1955d23e34d"),
1051
+ U("1504754524776-8f4f37790ca0"),
1052
+ U("1495474472287-4d71bcdd2085"),
1053
+ U("1521017432531-fbd92d768814"),
1054
+ U("1556909114-f6e7ad7d3136")
1055
+ ]
1056
+ },
1057
+ {
1058
+ keywords: [
1059
+ "shop",
1060
+ "store",
1061
+ "boutique",
1062
+ "retail",
1063
+ "clothing",
1064
+ "fashion",
1065
+ "jewel",
1066
+ "gift",
1067
+ "florist",
1068
+ "market",
1069
+ "grocer",
1070
+ "product",
1071
+ "storefront"
1072
+ ],
1073
+ pool: [
1074
+ U("1441984904996-e0b6ba687e04"),
1075
+ U("1472851294608-062f824d29cc"),
1076
+ U("1523381210434-271e8be1f52b"),
1077
+ U("1534452203293-494d7ddbf7e0"),
1078
+ U("1445205170230-053b83016050"),
1079
+ U("1560243563-062bfc001d68")
1080
+ ]
1081
+ },
1082
+ {
1083
+ keywords: [
1084
+ "yoga",
1085
+ "pilates",
1086
+ "fitness",
1087
+ "gym",
1088
+ "workout",
1089
+ "trainer",
1090
+ "wellness",
1091
+ "meditat",
1092
+ "massage",
1093
+ "therap",
1094
+ "physio",
1095
+ "chiro",
1096
+ "nutrition",
1097
+ "spa",
1098
+ "studio"
1099
+ ],
1100
+ pool: [
1101
+ U("1544367567-0f2fcb009e0b"),
1102
+ U("1506126613408-eca07ce68773"),
1103
+ U("1545205597-3d9d02c29597"),
1104
+ U("1552196563-55cd4e45efb3"),
1105
+ U("1518611012118-696072aa579a"),
1106
+ U("1571019613454-1cb2f99b2d8b"),
1107
+ U("1540555700478-4be289fbecef"),
1108
+ U("1519824145371-296894a0daa9")
1109
+ ]
1110
+ },
1111
+ {
1112
+ keywords: [
1113
+ "salon",
1114
+ "hairdress",
1115
+ "haircut",
1116
+ "barber",
1117
+ "manicure",
1118
+ "pedicure",
1119
+ "nails",
1120
+ "beauty",
1121
+ "makeup",
1122
+ "cosmetic",
1123
+ "eyelash",
1124
+ "eyebrow",
1125
+ "skincare",
1126
+ "esthetic",
1127
+ "waxing",
1128
+ "hair"
1129
+ ],
1130
+ pool: [
1131
+ U("1560066984-138dadb4c035"),
1132
+ U("1522337660859-02fbefca4702"),
1133
+ U("1562322140-8baeececf3df"),
1134
+ U("1521590832167-7bcbfaa6381f"),
1135
+ U("1487412947147-5cebf100ffc2"),
1136
+ U("1526045478516-99145907023c")
1137
+ ]
1138
+ },
1139
+ {
1140
+ keywords: [
1141
+ "cleaning",
1142
+ "plumb",
1143
+ "electric",
1144
+ "landscap",
1145
+ "contractor",
1146
+ "handyman",
1147
+ "renov",
1148
+ "hvac",
1149
+ "roofing",
1150
+ "painting",
1151
+ "carpentry",
1152
+ "flooring",
1153
+ "movers",
1154
+ "construction",
1155
+ "tools"
1156
+ ],
1157
+ pool: [
1158
+ U("1581578731548-c64695cc6952"),
1159
+ U("1504307651254-35680f356dfd"),
1160
+ U("1581092160562-40aa08e78837"),
1161
+ U("1621905251189-08b45d6a269e"),
1162
+ U("1558618666-fcd25c85cd64"),
1163
+ U("1585128792020-803d29415281")
1164
+ ]
1165
+ },
1166
+ {
1167
+ keywords: [
1168
+ "legal",
1169
+ "attorney",
1170
+ "lawyer",
1171
+ "account",
1172
+ "bookkeep",
1173
+ "consult",
1174
+ "coaching",
1175
+ "financ",
1176
+ "insurance",
1177
+ "realtor",
1178
+ "estate",
1179
+ "marketing",
1180
+ "agency",
1181
+ "office",
1182
+ "business",
1183
+ "desk"
1184
+ ],
1185
+ pool: [
1186
+ U("1497366216548-37526070297c"),
1187
+ U("1497366811353-6870744d04b2"),
1188
+ U("1454165804606-c3d57bc86b40"),
1189
+ U("1521791136064-7986c2920216"),
1190
+ U("1556761175-b413da4baf72"),
1191
+ U("1542744173-8e7e53415bb0")
1192
+ ]
1193
+ },
1194
+ {
1195
+ keywords: [
1196
+ "wedding",
1197
+ "event",
1198
+ "party",
1199
+ "celebrat",
1200
+ "venue",
1201
+ "community",
1202
+ "nonprofit",
1203
+ "charity",
1204
+ "workshop",
1205
+ "photograph",
1206
+ "concert"
1207
+ ],
1208
+ pool: [
1209
+ U("1511578314322-379afb476865"),
1210
+ U("1501281668745-f7f57925c3b4"),
1211
+ U("1523580494863-6f3031224c94"),
1212
+ U("1540575467063-178a50c2df87"),
1213
+ U("1505236858219-8359eb29e329"),
1214
+ U("1528605248644-14dd04022da1")
1215
+ ]
1216
+ }
1217
+ ];
1218
+ function poolForSubject(subject) {
1219
+ for (const theme of THEMED) {
1220
+ if (theme.keywords.some((k) => subject.includes(k))) {
1221
+ return theme.pool;
1222
+ }
1223
+ }
1224
+ return GENERIC;
1225
+ }
1226
+ function mixedHash(text) {
1227
+ let hash = 2166136261;
1228
+ for (let i = 0; i < text.length; i++) {
1229
+ hash ^= text.charCodeAt(i);
1230
+ hash = Math.imul(hash, 16777619);
1231
+ }
1232
+ return hash >>> 16 & 65535;
1233
+ }
1234
+ function resolvePlaceholderRef(ref) {
1235
+ const match = /^placeholder:([a-z0-9-]+)$/.exec(ref);
1236
+ if (!match) return null;
1237
+ const subject = match[1];
1238
+ const pool = poolForSubject(subject.replace(/-\d+$/, ""));
1239
+ return pool[mixedHash(ref) % pool.length];
1240
+ }
1241
+ function collectPlaceholderRefs(tree) {
1242
+ const seen = /* @__PURE__ */ new Set();
1243
+ for (const match of JSON.stringify(tree ?? null).matchAll(/"(placeholder:[a-z0-9-]+)"/gu)) {
1244
+ seen.add(match[1]);
1245
+ }
1246
+ return [...seen];
1247
+ }
1248
+ function buildPlaceholderMap(tree) {
1249
+ const map = {};
1250
+ const cursor = /* @__PURE__ */ new Map();
1251
+ for (const ref of collectPlaceholderRefs(tree)) {
1252
+ const subject = ref.slice("placeholder:".length).replace(/-\d+$/, "");
1253
+ const pool = poolForSubject(subject);
1254
+ const start = cursor.get(pool) ?? mixedHash(ref) % pool.length;
1255
+ map[ref] = pool[start % pool.length];
1256
+ cursor.set(pool, start + 1);
1257
+ }
1258
+ return map;
1259
+ }
1260
+
1261
+ // src/ui/ai-tree/AiTreeRenderer.tsx
638
1262
  var import_jsx_runtime = require("react/jsx-runtime");
639
1263
  function lucideByName(name) {
640
1264
  const pascal = name.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
@@ -648,6 +1272,7 @@ var typeStyle = (spec, font) => ({
648
1272
  fontWeight: spec.weight
649
1273
  });
650
1274
  var str = (value) => typeof value === "string" ? value : "";
1275
+ var cardRadius = (slots) => slots.cornerStyle === "sharp" ? 0 : AI_TREE_TOKENS.radiusCard;
651
1276
  var featureLines = (value) => value.split(/\n|<br\s*\/?>/i).map((line) => line.trim()).filter(Boolean);
652
1277
  var CHECK_MASK = `data:image/svg+xml,${encodeURIComponent(
653
1278
  '<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 +1299,25 @@ var FEATURE_LINE_CSS = [
674
1299
  `background-color:currentColor;-webkit-mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px;`,
675
1300
  `mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px}`
676
1301
  ].join("");
1302
+ function buttonShellStyle(ctx, fullWidth) {
1303
+ const bs = ctx.buttonStyle;
1304
+ if (bs) {
1305
+ return {
1306
+ borderRadius: bs.radius,
1307
+ ...bs.padding ? { padding: bs.padding } : {},
1308
+ ...bs.fontFamily ? { fontFamily: bs.fontFamily } : { fontFamily: ctx.brand.fonts.body },
1309
+ ...bs.fontSize ? { fontSize: bs.fontSize } : {},
1310
+ ...bs.fontWeight ? { fontWeight: bs.fontWeight } : {},
1311
+ ...bs.letterSpacing && bs.letterSpacing !== "normal" ? { letterSpacing: bs.letterSpacing } : {},
1312
+ ...bs.textTransform && bs.textTransform !== "none" ? { textTransform: bs.textTransform } : {}
1313
+ };
1314
+ }
1315
+ return {
1316
+ borderRadius: AI_TREE_TOKENS.radiusButton,
1317
+ padding: fullWidth ? `${AI_TREE_TOKENS.spacing2}px ${AI_TREE_TOKENS.spacing4}px` : `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`,
1318
+ ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
1319
+ };
1320
+ }
677
1321
  function hexLuminance(color) {
678
1322
  const m = /^#([0-9a-f]{6})$/i.exec(color.trim());
679
1323
  if (!m) return null;
@@ -690,6 +1334,12 @@ function hexContrast(a, b) {
690
1334
  const [hi, lo] = la > lb ? [la, lb] : [lb, la];
691
1335
  return (hi + 0.05) / (lo + 0.05);
692
1336
  }
1337
+ function primaryButtonLabel(brand) {
1338
+ const darkC = hexContrast(brand.palette.primary, brand.palette.dark);
1339
+ const lightC = hexContrast(brand.palette.primary, AI_TREE_TOKENS.textPrimaryForeground);
1340
+ if (darkC === null || lightC === null) return AI_TREE_TOKENS.textPrimaryForeground;
1341
+ return darkC > lightC ? brand.palette.dark : AI_TREE_TOKENS.textPrimaryForeground;
1342
+ }
693
1343
  function accentBandContext(brand) {
694
1344
  const p = brand.palette;
695
1345
  const lightWins = (hexContrast(p.light, p.primary) ?? 99) >= (hexContrast(p.dark, p.primary) ?? 0);
@@ -707,6 +1357,22 @@ function accentBandContext(brand) {
707
1357
  function textAttrs(ctx, path) {
708
1358
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
709
1359
  }
1360
+ var AI_RESPONSIVE_CSS = [
1361
+ "@media (max-width: 960px) {",
1362
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
1363
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
1364
+ "}",
1365
+ "@media (max-width: 640px) {",
1366
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
1367
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
1368
+ // Group containers flatten to a column on phones; span placements come along for free.
1369
+ " [data-ai-responsive] [data-ai-group] { display: flex !important; flex-direction: column !important; }",
1370
+ " [data-ai-responsive] [data-ai-group] > * { grid-column: auto !important; }",
1371
+ " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
1372
+ " [data-ai-responsive] { overflow-x: hidden; }",
1373
+ " [data-ai-responsive] img { max-width: 100%; }",
1374
+ "}"
1375
+ ].join("\n");
710
1376
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
711
1377
  function MediaBox({
712
1378
  refValue,
@@ -719,13 +1385,17 @@ function MediaBox({
719
1385
  const url = refValue ? ctx.resolveMedia(refValue) : null;
720
1386
  const isIcon = /^(lucide|simple):/.test(refValue);
721
1387
  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" } : {};
1388
+ const editAttrs = ctx.keyFor && editPath ? {
1389
+ "data-ohw-key": ctx.keyFor(editPath),
1390
+ "data-ohw-editable": isIcon ? "icon" : "image"
1391
+ } : {};
723
1392
  if (isIcon) {
724
1393
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
725
1394
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
726
1395
  "span",
727
1396
  {
728
1397
  "data-ai-icon": refValue,
1398
+ ...editAttrs,
729
1399
  style: {
730
1400
  display: "inline-flex",
731
1401
  width: 48,
@@ -789,12 +1459,15 @@ function ButtonEl({
789
1459
  width: fullWidth ? "100%" : void 0,
790
1460
  alignItems: "center",
791
1461
  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
1462
  textDecoration: "none",
795
1463
  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 }
1464
+ ...buttonShellStyle(ctx, fullWidth),
1465
+ ...secondary ? { border: `1px solid ${ctx.brand.palette.dark}`, color: ctx.brand.palette.dark } : (
1466
+ // Off-band, prefer the template button's measured fill/label pair — a template may
1467
+ // fill its CTAs with any token (hvac: accent bg, primary text). On an accent band
1468
+ // the flipped palette keeps contrast, so the brand-driven colours stay.
1469
+ !ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : { background: ctx.brand.palette.primary, color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand) }
1470
+ )
798
1471
  },
799
1472
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
800
1473
  }
@@ -970,10 +1643,11 @@ function PricingCard({ node, ctx, path }) {
970
1643
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
971
1644
  "div",
972
1645
  {
1646
+ "data-ohw-card": "",
973
1647
  style: {
974
1648
  background: hasBg ? ctx.brand.palette.light : "transparent",
975
1649
  border: `1px solid ${dark}`,
976
- borderRadius: AI_TREE_TOKENS.radiusCard,
1650
+ borderRadius: cardRadius(slots),
977
1651
  padding: AI_TREE_TOKENS.paddingBlock,
978
1652
  display: "flex",
979
1653
  flexDirection: "column",
@@ -1078,10 +1752,11 @@ function TestimonialCard({ node, ctx, path }) {
1078
1752
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1079
1753
  "div",
1080
1754
  {
1755
+ "data-ohw-card": "",
1081
1756
  "data-ai-avatar-pos": avatarPos ?? void 0,
1082
1757
  style: {
1083
1758
  background: hasBg ? ctx.cardSurface : "transparent",
1084
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1759
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1085
1760
  overflow: "hidden",
1086
1761
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1087
1762
  minWidth: 0
@@ -1116,10 +1791,11 @@ function TeamCard({ node, ctx, path }) {
1116
1791
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1117
1792
  "div",
1118
1793
  {
1794
+ "data-ohw-card": "",
1119
1795
  "data-ai-avatar-pos": avatarPos ?? void 0,
1120
1796
  style: {
1121
1797
  background: hasBg ? ctx.cardSurface : "transparent",
1122
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1798
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1123
1799
  overflow: "hidden",
1124
1800
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1125
1801
  minWidth: 0,
@@ -1290,9 +1966,10 @@ function CardBlock({ node, ctx, path }) {
1290
1966
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1291
1967
  "div",
1292
1968
  {
1969
+ "data-ohw-card": "",
1293
1970
  style: {
1294
1971
  background: hasBg ? ctx.cardSurface : "transparent",
1295
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1972
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1296
1973
  overflow: "hidden",
1297
1974
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1298
1975
  display: horizontal ? "flex" : "block",
@@ -1320,7 +1997,7 @@ function CardBlock({ node, ctx, path }) {
1320
1997
  ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1321
1998
  "div",
1322
1999
  {
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" },
2000
+ 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
2001
  children: media
1325
2002
  }
1326
2003
  )),
@@ -1611,7 +2288,7 @@ function CollectionBlock({ node, ctx, path }) {
1611
2288
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1612
2289
  "div",
1613
2290
  {
1614
- "data-ai-grid": "",
2291
+ "data-ai-grid": String(itemsPerRow),
1615
2292
  style: {
1616
2293
  display: "grid",
1617
2294
  gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
@@ -1731,6 +2408,32 @@ function renderNode(node, ctx, path) {
1731
2408
  if (child) {
1732
2409
  return renderNode(child, ctx, `${path}.c0`);
1733
2410
  }
2411
+ if (str(slots.provider) === "map" && str(slots.query)) {
2412
+ const query = str(slots.query);
2413
+ const mapAttrs = ctx.keyFor ? {
2414
+ "data-ohw-key": ctx.keyFor(`${path}.query`),
2415
+ "data-ohw-editable": "map",
2416
+ "data-ohw-map-query": query
2417
+ } : {};
2418
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2419
+ "iframe",
2420
+ {
2421
+ ...mapAttrs,
2422
+ "data-ai-embed": "map",
2423
+ title: str(slots.title) || "Map",
2424
+ src: `https://www.google.com/maps?q=${encodeURIComponent(query)}&output=embed`,
2425
+ loading: "lazy",
2426
+ referrerPolicy: "no-referrer-when-downgrade",
2427
+ style: {
2428
+ width: "100%",
2429
+ minHeight: 320,
2430
+ border: 0,
2431
+ borderRadius: AI_TREE_TOKENS.radiusCard,
2432
+ display: "block"
2433
+ }
2434
+ }
2435
+ );
2436
+ }
1734
2437
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1735
2438
  "div",
1736
2439
  {
@@ -1834,15 +2537,14 @@ function renderNode(node, ctx, path) {
1834
2537
  alignSelf: submitAlign,
1835
2538
  border: "none",
1836
2539
  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)
2540
+ // Shape/padding/typography follow the host template's own buttons.
2541
+ ...buttonShellStyle(ctx),
2542
+ // Brand-styled: the template button's measured fill/label pair off-band, else
2543
+ // primary fill with a brand-derived label so it reads on custom palettes.
2544
+ ...!ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : {
2545
+ background: ctx.brand.palette.primary,
2546
+ color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand)
2547
+ }
1846
2548
  },
1847
2549
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
1848
2550
  },
@@ -1877,7 +2579,7 @@ function renderNode(node, ctx, path) {
1877
2579
  function AiTreeRenderer({
1878
2580
  tree,
1879
2581
  brand,
1880
- buttonRadius,
2582
+ buttonStyle,
1881
2583
  resolveMedia,
1882
2584
  editKeyPrefix
1883
2585
  }) {
@@ -1887,13 +2589,18 @@ function AiTreeRenderer({
1887
2589
  const resolvedBrand = brand ?? AI_DEFAULT_BRAND;
1888
2590
  const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
1889
2591
  const blockBrand = band?.brand ?? resolvedBrand;
2592
+ const placeholderMap = buildPlaceholderMap(tree);
1890
2593
  const ctx = {
1891
2594
  brand: blockBrand,
1892
- resolveMedia: resolveMedia ?? (() => null),
2595
+ // An owner/library ref resolves through the host resolver; a `placeholder:<subject>` ref the
2596
+ // host cannot resolve falls back to real stock photography (the per-section map first, then a
2597
+ // standalone resolve), so generated galleries, image rows, and overlay backgrounds arrive with
2598
+ // photos instead of grey boxes.
2599
+ resolveMedia: (ref) => resolveMedia?.(ref) ?? placeholderMap[ref] ?? resolvePlaceholderRef(ref),
1893
2600
  cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
1894
2601
  keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
1895
2602
  sectionAlignment: (tree.settings ?? {}).alignment === "center" ? "center" : "left",
1896
- buttonRadius,
2603
+ buttonStyle,
1897
2604
  ...band ? { buttonLabel: band.buttonLabel } : {}
1898
2605
  };
1899
2606
  const settings = tree.settings ?? {};
@@ -1934,6 +2641,7 @@ function AiTreeRenderer({
1934
2641
  {
1935
2642
  "data-ai-section": tree.tag ?? "",
1936
2643
  ...bgAttrs,
2644
+ "data-ai-responsive": "",
1937
2645
  style: {
1938
2646
  position: "relative",
1939
2647
  padding: `${pad}px 0`,
@@ -1944,12 +2652,13 @@ function AiTreeRenderer({
1944
2652
  color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
1945
2653
  },
1946
2654
  children: [
1947
- isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
2655
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
1948
2656
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
2657
+ isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
1949
2658
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1950
2659
  "div",
1951
2660
  {
1952
- "data-ai-container": "",
2661
+ "data-ai-section-inner": "",
1953
2662
  style: {
1954
2663
  position: "relative",
1955
2664
  maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
@@ -1960,7 +2669,7 @@ function AiTreeRenderer({
1960
2669
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1961
2670
  "div",
1962
2671
  {
1963
- "data-ai-row": "",
2672
+ "data-ai-columns": "",
1964
2673
  style: {
1965
2674
  display: "grid",
1966
2675
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -1999,21 +2708,63 @@ function AiTreeRenderer({
1999
2708
  var import_jsx_runtime2 = require("react/jsx-runtime");
2000
2709
  var CONTAINER_ATTR = "data-ohw-ai-generated";
2001
2710
  var REPLACED_ATTR = "data-ohw-ai-replaced-by";
2002
- var REMOVED_ATTR = "data-ohw-ai-removed";
2711
+ var REMOVED_ATTR2 = "data-ohw-ai-removed";
2003
2712
  var TEMPLATE_HIDDEN_ATTR = "data-ohw-ai-template-hidden";
2004
2713
  var CHROME_SECTION_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
2714
+ function isChromeSection2(el) {
2715
+ if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) return true;
2716
+ return el.tagName === "HEADER" || el.tagName === "FOOTER";
2717
+ }
2005
2718
  function readRootVar(name) {
2006
2719
  if (typeof document === "undefined") return "";
2007
2720
  return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
2008
2721
  }
2722
+ function normalizeColorToHex(value) {
2723
+ if (!value || typeof document === "undefined") return value;
2724
+ const canvas = document.createElement("canvas");
2725
+ canvas.width = 1;
2726
+ canvas.height = 1;
2727
+ const ctx = canvas.getContext("2d");
2728
+ if (!ctx) return value;
2729
+ ctx.fillStyle = value;
2730
+ ctx.fillRect(0, 0, 1, 1);
2731
+ const [r2, g, b] = ctx.getImageData(0, 0, 1, 1).data;
2732
+ const toHex = (n) => n.toString(16).padStart(2, "0");
2733
+ return `#${toHex(r2)}${toHex(g)}${toHex(b)}`;
2734
+ }
2735
+ var GENERIC_FONT_KEYWORDS = /* @__PURE__ */ new Set([
2736
+ "serif",
2737
+ "sans-serif",
2738
+ "monospace",
2739
+ "cursive",
2740
+ "fantasy",
2741
+ "system-ui",
2742
+ "ui-serif",
2743
+ "ui-sans-serif",
2744
+ "ui-monospace",
2745
+ "ui-rounded",
2746
+ "math",
2747
+ "emoji",
2748
+ "fangsong"
2749
+ ]);
2750
+ function primaryFontFamily(stack) {
2751
+ const first = stack.split(",").map((part) => part.trim().replace(/^["']|["']$/g, "")).find((part) => part && !GENERIC_FONT_KEYWORDS.has(part.toLowerCase()));
2752
+ return first ?? "";
2753
+ }
2754
+ function humanizeFontFamily(name) {
2755
+ 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());
2756
+ }
2757
+ function readFontVar(name) {
2758
+ return humanizeFontFamily(primaryFontFamily(readRootVar(name)));
2759
+ }
2009
2760
  function deriveBrandOverride() {
2010
2761
  const dark = readRootVar("--ohw-brand-dark");
2011
2762
  const primary = readRootVar("--ohw-brand-primary");
2012
2763
  const light = readRootVar("--ohw-brand-light");
2013
2764
  if (!dark || !primary || !light) return null;
2014
2765
  const accent = readRootVar("--ohw-brand-accent");
2015
- const heading = readRootVar("--font-heading") || readRootVar("--font-display");
2016
- const body = readRootVar("--font-body");
2766
+ const heading = readFontVar("--font-heading") || readFontVar("--font-display");
2767
+ const body = readFontVar("--font-body");
2017
2768
  return {
2018
2769
  palette: { dark, primary, accent: accent || dark, light },
2019
2770
  fonts: {
@@ -2022,28 +2773,93 @@ function deriveBrandOverride() {
2022
2773
  }
2023
2774
  };
2024
2775
  }
2025
- function deriveTemplateBrand() {
2026
- const dark = readRootVar("--color-dark");
2027
- const primary = readRootVar("--color-primary");
2028
- const light = readRootVar("--color-light");
2776
+ function deriveTemplateBrandLive() {
2777
+ const dark = readRootVar("--color-dark") || readRootVar("--brand-text");
2778
+ const primary = readRootVar("--color-primary") || readRootVar("--brand-primary");
2779
+ const light = readRootVar("--color-light") || readRootVar("--brand-background");
2029
2780
  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");
2781
+ const accent = readRootVar("--color-accent") || readRootVar("--brand-accent");
2782
+ const heading = readFontVar("--brand-font-heading") || readFontVar("--font-heading") || readFontVar("--font-display");
2783
+ const body = readFontVar("--brand-font-body") || readFontVar("--font-body");
2033
2784
  return {
2034
- palette: { dark, primary, accent: accent || dark, light },
2785
+ palette: {
2786
+ dark: normalizeColorToHex(dark),
2787
+ primary: normalizeColorToHex(primary),
2788
+ accent: normalizeColorToHex(accent || dark),
2789
+ light: normalizeColorToHex(light)
2790
+ },
2035
2791
  fonts: {
2036
2792
  heading: heading || AI_DEFAULT_BRAND.fonts.heading,
2037
2793
  body: body || AI_DEFAULT_BRAND.fonts.body
2038
2794
  }
2039
2795
  };
2040
2796
  }
2041
- function deriveTemplateButtonRadius() {
2797
+ function deriveTemplateFontsLive() {
2798
+ const heading = readFontVar("--brand-font-heading") || readFontVar("--font-heading") || readFontVar("--font-display");
2799
+ const body = readFontVar("--brand-font-body") || readFontVar("--font-body");
2800
+ if (!heading && !body) return null;
2801
+ return {
2802
+ heading: heading || AI_DEFAULT_BRAND.fonts.heading,
2803
+ body: body || AI_DEFAULT_BRAND.fonts.body
2804
+ };
2805
+ }
2806
+ var OVERRIDE_VAR_NAMES = [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES, ...FONT_VARS.heading, ...FONT_VARS.body];
2807
+ function withOverrideStripped(read) {
2808
+ const root = document.documentElement;
2809
+ const restore = OVERRIDE_VAR_NAMES.map((name) => [name, root.style.getPropertyValue(name)]);
2810
+ for (const name of OVERRIDE_VAR_NAMES) root.style.removeProperty(name);
2811
+ try {
2812
+ return read();
2813
+ } finally {
2814
+ for (const [name, value] of restore) if (value) root.style.setProperty(name, value);
2815
+ }
2816
+ }
2817
+ var TEMPLATE_BRAND_SNAPSHOT = typeof document === "undefined" ? null : withOverrideStripped(deriveTemplateBrandLive);
2818
+ var TEMPLATE_FONTS_SNAPSHOT = typeof document === "undefined" ? null : withOverrideStripped(deriveTemplateFontsLive);
2819
+ function deriveTemplateBrand() {
2820
+ return TEMPLATE_BRAND_SNAPSHOT;
2821
+ }
2822
+ function deriveTemplateFonts() {
2823
+ return TEMPLATE_FONTS_SNAPSHOT;
2824
+ }
2825
+ function deriveTemplateButtonStyle() {
2042
2826
  if (typeof document === "undefined") return null;
2043
- const btn = document.querySelector('[data-ohw-role="button"]');
2827
+ const candidates = Array.from(
2828
+ document.querySelectorAll('[data-ohw-role="button"]')
2829
+ ).filter((el) => !el.closest(`[${CONTAINER_ATTR}]`));
2830
+ const isFilled = (el) => {
2831
+ const bg = getComputedStyle(el).backgroundColor;
2832
+ if (!bg || bg === "transparent") return false;
2833
+ const alpha = bg.match(/rgba?\([^)]*,\s*([\d.]+)\)$/);
2834
+ return !alpha || parseFloat(alpha[1]) > 0;
2835
+ };
2836
+ const btn = candidates.find(isFilled) ?? candidates[0];
2044
2837
  if (!btn) return null;
2045
- const radius = getComputedStyle(btn).borderTopLeftRadius;
2046
- return radius || null;
2838
+ const cs = getComputedStyle(btn);
2839
+ const filled = isFilled(btn);
2840
+ const corners = [
2841
+ cs.borderTopLeftRadius,
2842
+ cs.borderTopRightRadius,
2843
+ cs.borderBottomRightRadius,
2844
+ cs.borderBottomLeftRadius
2845
+ ].map((v) => v || "0px");
2846
+ const radius = corners.every((v) => v === corners[0]) ? corners[0] : corners.join(" ");
2847
+ const px = (v) => parseFloat(v) || 0;
2848
+ const lineHeight = px(cs.lineHeight) || px(cs.fontSize) * 1.2;
2849
+ const contentH = btn.getBoundingClientRect().height - px(cs.borderTopWidth) - px(cs.borderBottomWidth);
2850
+ const impliedY = Math.round(Math.max(0, (contentH - lineHeight) / 2));
2851
+ const padY = Math.max(px(cs.paddingTop), px(cs.paddingBottom), impliedY);
2852
+ const padX = Math.max(px(cs.paddingLeft), px(cs.paddingRight));
2853
+ return {
2854
+ radius: radius || "10px",
2855
+ padding: `${padY}px ${padX}px`,
2856
+ ...filled ? { background: cs.backgroundColor, color: cs.color } : {},
2857
+ fontFamily: cs.fontFamily || "",
2858
+ fontSize: cs.fontSize || "",
2859
+ fontWeight: cs.fontWeight || "",
2860
+ letterSpacing: cs.letterSpacing || "",
2861
+ textTransform: cs.textTransform || ""
2862
+ };
2047
2863
  }
2048
2864
  var mounted = /* @__PURE__ */ new Map();
2049
2865
  function findTemplateSection(id) {
@@ -2059,6 +2875,19 @@ function findPlacementAnchor(id, exclude) {
2059
2875
  for (const el of document.querySelectorAll(`[data-ohw-section="${CSS.escape(id)}"]`)) {
2060
2876
  if (el === exclude) continue;
2061
2877
  if (el.parentElement?.closest("[data-ohw-section]")) continue;
2878
+ if (isChromeSection2(el)) return null;
2879
+ return el;
2880
+ }
2881
+ return null;
2882
+ }
2883
+ function findFooterSection() {
2884
+ const byId = findTemplateSection("footer");
2885
+ if (byId) return byId;
2886
+ for (const el of Array.from(
2887
+ document.querySelectorAll("footer[data-ohw-section]")
2888
+ ).reverse()) {
2889
+ if (el.hasAttribute(CONTAINER_ATTR)) continue;
2890
+ if (el.parentElement?.closest("[data-ohw-section]")) continue;
2062
2891
  return el;
2063
2892
  }
2064
2893
  return null;
@@ -2085,7 +2914,7 @@ function placeContainer(container, entry) {
2085
2914
  return;
2086
2915
  }
2087
2916
  }
2088
- const footer = findTemplateSection("footer");
2917
+ const footer = findFooterSection();
2089
2918
  if (footer) {
2090
2919
  footer.insertAdjacentElement("beforebegin", container);
2091
2920
  } else {
@@ -2094,18 +2923,18 @@ function placeContainer(container, entry) {
2094
2923
  }
2095
2924
  function syncRemovedSections(state) {
2096
2925
  const removed = new Set(state.removed ?? []);
2097
- for (const el of document.querySelectorAll(`[${REMOVED_ATTR}]`)) {
2926
+ for (const el of document.querySelectorAll(`[${REMOVED_ATTR2}]`)) {
2098
2927
  const id = el.getAttribute("data-ohw-section") ?? "";
2099
2928
  if (!removed.has(id)) {
2100
2929
  el.style.removeProperty("display");
2101
- el.removeAttribute(REMOVED_ATTR);
2930
+ el.removeAttribute(REMOVED_ATTR2);
2102
2931
  }
2103
2932
  }
2104
2933
  for (const id of removed) {
2105
2934
  const section = findTemplateSection(id);
2106
2935
  if (section && !section.hasAttribute(REPLACED_ATTR)) {
2107
2936
  section.style.display = "none";
2108
- section.setAttribute(REMOVED_ATTR, "");
2937
+ section.setAttribute(REMOVED_ATTR2, "");
2109
2938
  }
2110
2939
  }
2111
2940
  }
@@ -2120,9 +2949,9 @@ function syncTemplateHidden(state, pageHasSections) {
2120
2949
  if (!hide) return;
2121
2950
  for (const el of Array.from(document.querySelectorAll("[data-ohw-section]"))) {
2122
2951
  if (el.hasAttribute(CONTAINER_ATTR)) continue;
2123
- if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) continue;
2952
+ if (isChromeSection2(el)) continue;
2124
2953
  if (el.parentElement?.closest("[data-ohw-section]")) continue;
2125
- if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR)) continue;
2954
+ if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR2)) continue;
2126
2955
  el.style.display = "none";
2127
2956
  el.setAttribute(TEMPLATE_HIDDEN_ATTR, "");
2128
2957
  }
@@ -2146,18 +2975,23 @@ function syncReplacedOriginals(state) {
2146
2975
  }
2147
2976
  }
2148
2977
  var sectionOrderIndex = /* @__PURE__ */ new Map();
2978
+ var removedSectionIds = /* @__PURE__ */ new Set();
2149
2979
  function setAiSectionOrder(raw, currentPath) {
2150
2980
  const next = /* @__PURE__ */ new Map();
2981
+ const removed = /* @__PURE__ */ new Set();
2151
2982
  if (raw) {
2152
2983
  try {
2153
2984
  const entries = JSON.parse(raw);
2154
2985
  for (const entry of entries) {
2155
- if (!entry.pagePath || entry.pagePath === currentPath) next.set(entry.instanceId, entry.order);
2986
+ if (entry.pagePath && entry.pagePath !== currentPath) continue;
2987
+ next.set(entry.instanceId, entry.order);
2988
+ if (entry.removed) removed.add(entry.instanceId);
2156
2989
  }
2157
2990
  } catch {
2158
2991
  }
2159
2992
  }
2160
2993
  sectionOrderIndex = next;
2994
+ removedSectionIds = removed;
2161
2995
  }
2162
2996
  function applyExplicitOrder(entries) {
2163
2997
  if (sectionOrderIndex.size === 0) return entries;
@@ -2193,11 +3027,23 @@ function orderByChain(sections) {
2193
3027
  for (const root of roots) visit(root);
2194
3028
  return out.length === sections.length ? out : sections;
2195
3029
  }
3030
+ function syncSoftRemovedGenerated() {
3031
+ for (const [id, section] of mounted) {
3032
+ const el = section.container;
3033
+ if (removedSectionIds.has(id)) {
3034
+ el.style.display = "none";
3035
+ el.setAttribute(REMOVED_ATTR, "");
3036
+ } else if (el.hasAttribute(REMOVED_ATTR)) {
3037
+ el.style.removeProperty("display");
3038
+ el.removeAttribute(REMOVED_ATTR);
3039
+ }
3040
+ }
3041
+ }
2196
3042
  function applyAiSectionsToDom(state, options) {
2197
3043
  if (typeof document === "undefined") return;
2198
3044
  const brandOverride = deriveBrandOverride();
2199
3045
  const templateBrand = deriveTemplateBrand();
2200
- const templateButtonRadius = deriveTemplateButtonRadius();
3046
+ const templateButtonStyle = deriveTemplateButtonStyle();
2201
3047
  const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
2202
3048
  const pagePath = window.location.pathname;
2203
3049
  const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
@@ -2237,7 +3083,7 @@ function applyAiSectionsToDom(state, options) {
2237
3083
  {
2238
3084
  tree: entry.tree,
2239
3085
  brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
2240
- buttonRadius: templateButtonRadius,
3086
+ buttonStyle: templateButtonStyle,
2241
3087
  resolveMedia,
2242
3088
  editKeyPrefix: `ai.${entry.id}`
2243
3089
  }
@@ -2260,6 +3106,7 @@ function applyAiSectionsToDom(state, options) {
2260
3106
  syncReplacedOriginals(state);
2261
3107
  syncRemovedSections(state);
2262
3108
  syncTemplateHidden(state, pageSections.length > 0);
3109
+ syncSoftRemovedGenerated();
2263
3110
  }
2264
3111
 
2265
3112
  // src/useLinkHrefGuardian.ts
@@ -6451,12 +7298,12 @@ var cva = (base, config) => (props) => {
6451
7298
  var import_radix_ui2 = require("radix-ui");
6452
7299
  var import_jsx_runtime5 = require("react/jsx-runtime");
6453
7300
  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",
7301
+ "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
7302
  {
6456
7303
  variants: {
6457
7304
  variant: {
6458
7305
  default: "bg-transparent border-0",
6459
- outline: "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground"
7306
+ outline: "border border-input bg-transparent shadow-xs hover:bg-bridge-accent hover:text-accent-foreground"
6460
7307
  },
6461
7308
  size: {
6462
7309
  default: "px-2 py-1",
@@ -6549,10 +7396,10 @@ var DragHandle = React4.forwardRef(
6549
7396
  type,
6550
7397
  "data-slot": "drag-handle",
6551
7398
  className: cn(
6552
- "inline-flex h-7 w-4 shrink-0 items-center justify-center rounded-md transition-all duration-200 max-h-[30px]",
7399
+ "inline-flex h-7 w-4 shrink-0 items-center justify-center rounded-md transition-all duration-200 max-h-7.5",
6553
7400
  "bg-white border border-transparent text-stone-500 shadow-md cursor-grab",
6554
7401
  "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",
7402
+ "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
7403
  "disabled:cursor-not-allowed disabled:opacity-40 disabled:text-stone-950 disabled:pointer-events-none",
6557
7404
  className
6558
7405
  ),
@@ -6574,7 +7421,7 @@ var CustomToolbar = React5.forwardRef(({ className, onMouseDown, ...props }, ref
6574
7421
  "data-ohw-toolbar": "",
6575
7422
  className: cn(
6576
7423
  // 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",
7424
+ "inline-flex h-8 items-center gap-1 rounded-(--radius,0.5rem) bg-background p-0.5 font-sans whitespace-nowrap shadow-md",
6578
7425
  className
6579
7426
  ),
6580
7427
  onMouseDown: (e) => {
@@ -6603,7 +7450,7 @@ var CustomToolbarButton = React5.forwardRef(
6603
7450
  type,
6604
7451
  className: cn(
6605
7452
  "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",
7453
+ active ? "bg-bridge-primary text-primary-foreground" : "bg-transparent hover:bg-muted disabled:cursor-not-allowed disabled:text-muted-foreground disabled:opacity-60",
6607
7454
  className
6608
7455
  ),
6609
7456
  ...props
@@ -6808,9 +7655,7 @@ function ItemActionToolbar({
6808
7655
  ToggleGroup,
6809
7656
  {
6810
7657
  value: dropdownOpen ? "open" : "closed",
6811
- onValueChange: (value) => {
6812
- if (value !== "open" && value !== "closed") return;
6813
- onDropdownOpenChange?.(value === "open");
7658
+ onValueChange: () => {
6814
7659
  },
6815
7660
  className: "h-7 gap-0.5 rounded-[calc(var(--radius,0.5rem)-2px)] bg-muted p-0.5",
6816
7661
  onMouseDown: (e) => {
@@ -6825,6 +7670,11 @@ function ItemActionToolbar({
6825
7670
  size: "sm",
6826
7671
  "aria-label": "Closed",
6827
7672
  className: "h-6 min-w-0 px-2 text-xs font-medium",
7673
+ onMouseDown: (e) => {
7674
+ e.preventDefault();
7675
+ e.stopPropagation();
7676
+ if (dropdownOpen !== false) onDropdownOpenChange?.(false);
7677
+ },
6828
7678
  children: "Closed"
6829
7679
  }
6830
7680
  ),
@@ -6835,6 +7685,11 @@ function ItemActionToolbar({
6835
7685
  size: "sm",
6836
7686
  "aria-label": "Open",
6837
7687
  className: "h-6 min-w-0 px-2 text-xs font-medium",
7688
+ onMouseDown: (e) => {
7689
+ e.preventDefault();
7690
+ e.stopPropagation();
7691
+ if (dropdownOpen !== true) onDropdownOpenChange?.(true);
7692
+ },
6838
7693
  children: "Open"
6839
7694
  }
6840
7695
  )
@@ -7797,13 +8652,13 @@ function FormFieldToolbar({
7797
8652
  ]
7798
8653
  }
7799
8654
  ) }),
7800
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-[190px] p-1", children: FIELD_TYPES.map((entry) => {
8655
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-47.5 p-1", children: FIELD_TYPES.map((entry) => {
7801
8656
  const Icon = TYPE_ICONS[entry.type];
7802
8657
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
7803
8658
  DropdownMenuItem,
7804
8659
  {
7805
8660
  onSelect: () => onTypeChange(entry.type),
7806
- className: "rounded-md py-2 text-[13px] " + (entry.type === type ? "bg-primary/10" : ""),
8661
+ className: "rounded-md py-2 text-[13px] " + (entry.type === type ? "bg-bridge-primary/10" : ""),
7807
8662
  children: [
7808
8663
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Icon, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
7809
8664
  entry.label
@@ -7820,7 +8675,7 @@ function FormFieldToolbar({
7820
8675
  type: "button",
7821
8676
  "aria-pressed": required,
7822
8677
  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"),
8678
+ 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
8679
  "data-ohw-field-required-toggle": "",
7825
8680
  children: [
7826
8681
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.Asterisk, { size: 14, strokeWidth: 2, "aria-hidden": true }),
@@ -7840,7 +8695,7 @@ function FormFieldToolbar({
7840
8695
  children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.MoreHorizontal, { size: 15, "aria-hidden": true })
7841
8696
  }
7842
8697
  ) }),
7843
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-[170px] p-1", children: [
8698
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-42.5 p-1", children: [
7844
8699
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DropdownMenuItem, { onSelect: onDuplicate, className: "rounded-md py-2 text-[13px]", children: [
7845
8700
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.Copy, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
7846
8701
  "Duplicate"
@@ -7860,7 +8715,7 @@ function FieldTypePicker({ onPick }) {
7860
8715
  "div",
7861
8716
  {
7862
8717
  "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",
8718
+ className: "pointer-events-auto grid w-105 grid-cols-3 gap-3 rounded-xl border border-border bg-background p-4 shadow-lg",
7864
8719
  children: FIELD_TYPES.map((entry) => {
7865
8720
  const Icon = TYPE_ICONS[entry.type];
7866
8721
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
@@ -7868,7 +8723,7 @@ function FieldTypePicker({ onPick }) {
7868
8723
  {
7869
8724
  type: "button",
7870
8725
  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",
8726
+ 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
8727
  children: [
7873
8728
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Icon, { size: 26, strokeWidth: 1.5, "aria-hidden": true }),
7874
8729
  entry.label
@@ -7894,7 +8749,7 @@ var buttonVariants = cva(
7894
8749
  {
7895
8750
  variants: {
7896
8751
  variant: {
7897
- default: "bg-primary text-primary-foreground hover:opacity-90",
8752
+ default: "bg-bridge-primary text-primary-foreground hover:opacity-90",
7898
8753
  outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
7899
8754
  ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
7900
8755
  },
@@ -7989,6 +8844,7 @@ function MediaOverlay({
7989
8844
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
7990
8845
  );
7991
8846
  }, [isVideo]);
8847
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
7992
8848
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
7993
8849
  const box = {
7994
8850
  position: "fixed",
@@ -8094,8 +8950,8 @@ function MediaOverlay({
8094
8950
  pointerEvents: hover.hasTextOverlap ? "none" : "auto",
8095
8951
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
8096
8952
  // 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)"
8953
+ boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
8954
+ background: selected ? "transparent" : "color-mix(in srgb, var(--color-bridge-primary) 20%, transparent)"
8099
8955
  },
8100
8956
  onClick: () => onSelect && !selected ? onSelect(hover.key) : onReplace(hover.key),
8101
8957
  children: [
@@ -8118,17 +8974,17 @@ function MediaOverlay({
8118
8974
  },
8119
8975
  children: [
8120
8976
  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"
8977
+ replaceLabel
8122
8978
  ]
8123
8979
  }
8124
8980
  ),
8125
- replaceMode === "none" ? null : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8981
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8126
8982
  Button,
8127
8983
  {
8128
8984
  "data-ohw-media-overlay": "",
8129
8985
  variant: "outline",
8130
8986
  size: "sm",
8131
- "aria-label": isVideo ? "Replace video" : "Replace image",
8987
+ "aria-label": replaceLabel,
8132
8988
  className: "gap-1.5 cursor-pointer hover:bg-background",
8133
8989
  style: {
8134
8990
  ...OVERLAY_BUTTON_STYLE,
@@ -8151,7 +9007,7 @@ function MediaOverlay({
8151
9007
  },
8152
9008
  children: [
8153
9009
  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
9010
+ replaceMode === "full" ? replaceLabel : null
8155
9011
  ]
8156
9012
  }
8157
9013
  )
@@ -8186,253 +9042,41 @@ function CarouselOverlay({
8186
9042
  top: rect.top,
8187
9043
  left: rect.left,
8188
9044
  width: rect.width,
8189
- height: rect.height,
8190
- zIndex: 2147483646,
8191
- pointerEvents: "auto",
8192
- boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
8193
- background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
8194
- },
8195
- onClick: () => onEdit(hover.key),
8196
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
8197
- Button,
8198
- {
8199
- "data-ohw-carousel-overlay": "",
8200
- variant: "outline",
8201
- size: "sm",
8202
- className: "cursor-pointer gap-1.5 hover:bg-background",
8203
- style: OVERLAY_BUTTON_STYLE2,
8204
- onMouseDown: (e) => e.preventDefault(),
8205
- onClick: (e) => {
8206
- e.stopPropagation();
8207
- onEdit(hover.key);
8208
- },
8209
- children: [
8210
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react6.GalleryHorizontal, { size: 14 }),
8211
- "Edit gallery"
8212
- ]
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);
9045
+ height: rect.height,
9046
+ zIndex: 2147483646,
9047
+ pointerEvents: "auto",
9048
+ boxShadow: "inset 0 0 0 1.5px var(--color-bridge-primary)",
9049
+ background: "color-mix(in srgb, var(--color-bridge-primary) 20%, transparent)"
9050
+ },
9051
+ onClick: () => onEdit(hover.key),
9052
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
9053
+ Button,
9054
+ {
9055
+ "data-ohw-carousel-overlay": "",
9056
+ variant: "outline",
9057
+ size: "sm",
9058
+ className: "cursor-pointer gap-1.5 hover:bg-background",
9059
+ style: OVERLAY_BUTTON_STYLE2,
9060
+ onMouseDown: (e) => e.preventDefault(),
9061
+ onClick: (e) => {
9062
+ e.stopPropagation();
9063
+ onEdit(hover.key);
9064
+ },
9065
+ children: [
9066
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react6.GalleryHorizontal, { size: 14 }),
9067
+ "Edit gallery"
9068
+ ]
9069
+ }
9070
+ )
9071
+ }
9072
+ );
8428
9073
  }
8429
9074
 
8430
9075
  // src/ui/ai-section/AiSectionOverlay.tsx
9076
+ var import_react8 = require("react");
9077
+ var import_lucide_react7 = require("lucide-react");
8431
9078
  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
- }
9079
+ var findSectionElement = findByInstanceId;
8436
9080
  function readRect(instanceId) {
8437
9081
  const el = findSectionElement(instanceId);
8438
9082
  if (!el) return null;
@@ -8472,7 +9116,7 @@ function useLiveSectionRect(sectionId) {
8472
9116
  }
8473
9117
  function computeSectionBoundaryFlags(instanceId) {
8474
9118
  const topLevel = topLevelSections();
8475
- const index = topLevel.findIndex((el) => (el.dataset.ohwInstance ?? el.dataset.ohwSection) === instanceId);
9119
+ const index = topLevel.findIndex((el) => instanceIdOf(el) === instanceId);
8476
9120
  if (index === -1) return { isFirst: true, isLast: true };
8477
9121
  return { isFirst: index === 0, isLast: index === topLevel.length - 1 };
8478
9122
  }
@@ -8556,18 +9200,20 @@ function AiSectionOverlay({
8556
9200
  selectedIdRef.current = selectedId;
8557
9201
  const report = (0, import_react8.useCallback)(
8558
9202
  (el) => {
9203
+ const labelSrc = el ? sectionElementOf(el) : null;
8559
9204
  postToParent2({
8560
9205
  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
9206
+ sectionId: el ? instanceIdOf(el) || null : null,
9207
+ sectionLabel: labelSrc ? labelSrc.dataset.ohwSectionLabel ?? titleCaseSectionId(labelSrc.dataset.ohwSection ?? "") : null
8563
9208
  });
8564
9209
  },
8565
9210
  [postToParent2]
8566
9211
  );
8567
9212
  const selectFromElement = (0, import_react8.useCallback)(
8568
9213
  (el, options) => {
8569
- const sectionEl = el?.closest("[data-ohw-section]") ?? null;
8570
- const id = sectionEl ? sectionEl.dataset.ohwInstance ?? sectionEl.dataset.ohwSection ?? null : null;
9214
+ const inner = el?.closest("[data-ohw-section]") ?? null;
9215
+ const sectionEl = inner ? movableUnit(inner) : null;
9216
+ const id = sectionEl ? instanceIdOf(sectionEl) || null : null;
8571
9217
  if (id === selectedIdRef.current) return;
8572
9218
  setSelectedId(id);
8573
9219
  if (options?.report !== false) report(sectionEl);
@@ -8633,7 +9279,8 @@ function AiSectionOverlay({
8633
9279
  return;
8634
9280
  }
8635
9281
  const sec = t.closest("[data-ohw-section]");
8636
- setHoveredId(sec ? sec.dataset.ohwInstance ?? sec.dataset.ohwSection ?? null : null);
9282
+ const unit = sec ? movableUnit(sec) : null;
9283
+ setHoveredId(unit ? instanceIdOf(unit) || null : null);
8637
9284
  };
8638
9285
  const onLeave = () => setHoveredId(null);
8639
9286
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -9237,7 +9884,7 @@ function SectionTreeItem({
9237
9884
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
9238
9885
  "div",
9239
9886
  {
9240
- className: "mr-[-1px] h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
9887
+ className: "-mr-px h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
9241
9888
  "aria-hidden": true
9242
9889
  }
9243
9890
  ),
@@ -9256,7 +9903,7 @@ function SectionTreeItem({
9256
9903
  className: cn(
9257
9904
  "flex h-9 min-w-0 flex-1 items-center gap-2 rounded-md border border-border bg-background p-3",
9258
9905
  interactive && "cursor-pointer hover:bg-muted/30",
9259
- interactive && selected && "border-primary"
9906
+ interactive && selected && "border-bridge-primary"
9260
9907
  ),
9261
9908
  children: [
9262
9909
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
@@ -9386,7 +10033,7 @@ function UrlOrPageInput({
9386
10033
  };
9387
10034
  const fieldClassName = cn(
9388
10035
  "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"
10036
+ 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
10037
  );
9391
10038
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex w-full flex-col gap-2 p-0", children: [
9392
10039
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
@@ -10430,6 +11077,32 @@ function getNavbarDesktopContainer() {
10430
11077
  function getNavbarDrawerContainer() {
10431
11078
  return document.querySelector("[data-ohw-nav-drawer]");
10432
11079
  }
11080
+ function isNavbarListContainerVisible(el) {
11081
+ if (el.hasAttribute("hidden")) return false;
11082
+ if (el.getClientRects().length === 0) return false;
11083
+ const style = window.getComputedStyle(el);
11084
+ return style.display !== "none" && style.visibility !== "hidden";
11085
+ }
11086
+ function getActiveNavbarListContainer(draggedEl) {
11087
+ const desktop = getNavbarDesktopContainer();
11088
+ const drawer = getNavbarDrawerContainer();
11089
+ if (draggedEl) {
11090
+ if (drawer?.contains(draggedEl)) return drawer;
11091
+ if (desktop?.contains(draggedEl)) return desktop;
11092
+ }
11093
+ if (desktop && isNavbarListContainerVisible(desktop)) return desktop;
11094
+ if (drawer && isNavbarListContainerVisible(drawer)) return drawer;
11095
+ return desktop ?? drawer;
11096
+ }
11097
+ function listActiveNavbarItems(draggedEl) {
11098
+ const container = getActiveNavbarListContainer(draggedEl);
11099
+ if (container) {
11100
+ return Array.from(container.querySelectorAll("[data-ohw-href-key]")).filter(
11101
+ isNavbarLinkItem
11102
+ );
11103
+ }
11104
+ return listNavbarItems();
11105
+ }
10433
11106
  function listNavbarItems() {
10434
11107
  const desktop = getNavbarDesktopContainer();
10435
11108
  if (desktop) {
@@ -11542,20 +12215,23 @@ function isEmptyLabelValue(value) {
11542
12215
  function applyStoredValues(item, content) {
11543
12216
  const hrefKey = socialHrefKey(item);
11544
12217
  const iconKey = socialIconKey(item);
12218
+ const baseKey = hrefKey?.replace(/-href$/, "");
11545
12219
  if (hrefKey && content[hrefKey] !== void 0) item.setAttribute("href", content[hrefKey]);
11546
- if (iconKey) {
12220
+ const glyphKey = iconKey ?? baseKey ?? null;
12221
+ const glyphStored = glyphKey ? content[glyphKey]?.trim() : void 0;
12222
+ if (glyphStored) {
12223
+ ensureIconSlot(item);
11547
12224
  const glyph = item.querySelector(ICON_SELECTOR);
11548
- if (glyph && content[iconKey]) {
11549
- applyIconMarkup(glyph, content[iconKey]);
12225
+ if (glyph) {
12226
+ applyIconMarkup(glyph, glyphStored);
11550
12227
  glyph.removeAttribute(SOCIALS_ICON_PLACEHOLDER_ATTR);
11551
12228
  }
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
- }
12229
+ }
12230
+ const label = socialLabelElement(item);
12231
+ const labelKey = label?.getAttribute("data-ohw-key");
12232
+ if (label && labelKey && content[labelKey] !== void 0) {
12233
+ const words = isEmptyLabelValue(content[labelKey]) ? "" : content[labelKey];
12234
+ if (label.innerHTML !== words) label.innerHTML = words;
11559
12235
  }
11560
12236
  }
11561
12237
  function socialPlatformKey(iconKey) {
@@ -11644,6 +12320,9 @@ function insertSocialItem(row, after, content = {}, { placeholder = true, keys }
11644
12320
  if (placeholder) {
11645
12321
  const label = socialLabelElement(item);
11646
12322
  if (label) label.textContent = PLACEHOLDER_SOCIAL_LABEL;
12323
+ } else if (keys) {
12324
+ const label = socialLabelElement(item);
12325
+ if (label) label.textContent = "";
11647
12326
  }
11648
12327
  applySocialsDisplayToRow(row, display);
11649
12328
  return { item, hrefKey, iconKey, order: getSocialsOrderFromDom(row.ownerDocument) };
@@ -13144,6 +13823,7 @@ function readLogoSizeState(content, placement) {
13144
13823
  function getLogoElement(el) {
13145
13824
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
13146
13825
  if (marked) return marked;
13826
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
13147
13827
  const root = el.closest("nav, [data-ohw-nav-root], footer");
13148
13828
  if (!root) return null;
13149
13829
  const anchor = el.closest("a");
@@ -13431,7 +14111,7 @@ function DisplaySwitch({
13431
14111
  onClick: () => onChange(!checked),
13432
14112
  className: cn(
13433
14113
  "relative h-5 w-9 shrink-0 rounded-full transition-colors",
13434
- checked ? "bg-primary" : "bg-primary-50",
14114
+ checked ? "bg-bridge-primary" : "bg-primary-50",
13435
14115
  disabled ? "cursor-default opacity-50" : "cursor-pointer"
13436
14116
  ),
13437
14117
  children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
@@ -13439,7 +14119,7 @@ function DisplaySwitch({
13439
14119
  {
13440
14120
  className: cn(
13441
14121
  "absolute top-0.5 h-4 w-4 rounded-full bg-white transition-all",
13442
- checked ? "left-[1.125rem]" : "left-0.5"
14122
+ checked ? "left-4.5" : "left-0.5"
13443
14123
  )
13444
14124
  }
13445
14125
  )
@@ -13500,8 +14180,8 @@ var lockFooterDuringDrag = lockItemDuringDrag;
13500
14180
  var unlockFooterDragInteraction = unlockItemDragInteraction;
13501
14181
 
13502
14182
  // src/lib/nav-dnd.ts
13503
- function listReorderableNavItems() {
13504
- return listNavbarItems().filter((el) => {
14183
+ function listReorderableNavItems(draggedEl) {
14184
+ return listActiveNavbarItems(draggedEl).filter((el) => {
13505
14185
  const key = el.getAttribute("data-ohw-href-key");
13506
14186
  return isNavbarHrefKey(key) && !isNestedNavChild(el);
13507
14187
  });
@@ -13516,8 +14196,8 @@ function resolveParentNavHrefKey(child) {
13516
14196
  const key = trigger?.getAttribute("data-ohw-href-key");
13517
14197
  return key && isNavbarHrefKey(key) ? key : null;
13518
14198
  }
13519
- function listNavChildren(parentHrefKey) {
13520
- const items = listNavbarItems();
14199
+ function listNavChildren(parentHrefKey, draggedEl) {
14200
+ const items = listActiveNavbarItems(draggedEl);
13521
14201
  const parent = items.find((el) => el.getAttribute("data-ohw-href-key") === parentHrefKey);
13522
14202
  if (!parent) return [];
13523
14203
  const group = parent.closest("[data-ohw-nav-group]");
@@ -13527,20 +14207,83 @@ function listNavChildren(parentHrefKey) {
13527
14207
  (el) => isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))
13528
14208
  );
13529
14209
  }
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;
14210
+ function isVerticalNavLayout(items, draggedEl) {
14211
+ const visible = items.filter((el) => el.getClientRects().length > 0);
14212
+ if (visible.length >= 2) {
14213
+ const a = visible[0].getBoundingClientRect();
14214
+ const b = visible[1].getBoundingClientRect();
14215
+ const verticalGap = b.top - a.bottom;
14216
+ const horizontalGap = b.left - a.right;
14217
+ return verticalGap > horizontalGap;
14218
+ }
14219
+ const container = getActiveNavbarListContainer(draggedEl);
14220
+ if (!container) return false;
14221
+ const style = window.getComputedStyle(container);
14222
+ return style.flexDirection === "column" || style.flexDirection === "column-reverse";
14223
+ }
14224
+ function buildVerticalRootNavDropSlots(items, draggedEl) {
14225
+ const slots = [];
14226
+ const barThickness = 3;
14227
+ if (items.length === 0) {
14228
+ const container = getActiveNavbarListContainer(draggedEl);
14229
+ if (!container) return slots;
14230
+ const rect = container.getBoundingClientRect();
14231
+ slots.push({
14232
+ insertIndex: 0,
14233
+ parentId: null,
14234
+ left: rect.left,
14235
+ top: rect.top + rect.height / 2 - barThickness / 2,
14236
+ width: Math.max(rect.width, 40),
14237
+ height: barThickness,
14238
+ direction: "horizontal"
14239
+ });
14240
+ return slots;
14241
+ }
14242
+ const edgeGap = (() => {
14243
+ if (items.length < 2) return 16;
14244
+ const a = items[0].getBoundingClientRect();
14245
+ const b = items[1].getBoundingClientRect();
14246
+ return Math.max(0, b.top - a.bottom);
14247
+ })();
14248
+ for (let i = 0; i <= items.length; i++) {
14249
+ let top;
14250
+ let width;
14251
+ let left;
14252
+ if (i === 0) {
14253
+ const first = items[0].getBoundingClientRect();
14254
+ top = first.top - edgeGap / 2 - barThickness / 2;
14255
+ left = first.left;
14256
+ width = first.width;
14257
+ } else if (i === items.length) {
14258
+ const last = items[items.length - 1].getBoundingClientRect();
14259
+ const lastGap = items.length >= 2 ? Math.max(0, last.top - items[items.length - 2].getBoundingClientRect().bottom) : edgeGap;
14260
+ top = last.bottom + lastGap / 2 - barThickness / 2;
14261
+ left = last.left;
14262
+ width = last.width;
14263
+ } else {
14264
+ const prev = items[i - 1].getBoundingClientRect();
14265
+ const next = items[i].getBoundingClientRect();
14266
+ top = (prev.bottom + next.top) / 2 - barThickness / 2;
14267
+ left = Math.min(prev.left, next.left);
14268
+ width = Math.max(prev.right, next.right) - left;
14269
+ }
14270
+ slots.push({
14271
+ insertIndex: i,
14272
+ parentId: null,
14273
+ left,
14274
+ top,
14275
+ width: Math.max(width, 40),
14276
+ height: barThickness,
14277
+ direction: "horizontal"
14278
+ });
13535
14279
  }
13536
- return getNavbarDrawerContainer();
14280
+ return slots;
13537
14281
  }
13538
- function buildRootNavDropSlots() {
13539
- const items = listReorderableNavItems();
14282
+ function buildHorizontalRootNavDropSlots(items, draggedEl) {
13540
14283
  const slots = [];
13541
14284
  const barThickness = 3;
13542
14285
  if (items.length === 0) {
13543
- const container = activeNavListContainer();
14286
+ const container = getActiveNavbarListContainer(draggedEl);
13544
14287
  if (!container) return slots;
13545
14288
  const rect = container.getBoundingClientRect();
13546
14289
  slots.push({
@@ -13571,10 +14314,7 @@ function buildRootNavDropSlots() {
13571
14314
  height = first.height;
13572
14315
  } else if (i === items.length) {
13573
14316
  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;
14317
+ const lastGap = items.length >= 2 ? Math.max(0, last.left - items[items.length - 2].getBoundingClientRect().right) : edgeGap;
13578
14318
  left = last.right + lastGap / 2 - barThickness / 2;
13579
14319
  top = last.top;
13580
14320
  height = last.height;
@@ -13597,8 +14337,15 @@ function buildRootNavDropSlots() {
13597
14337
  }
13598
14338
  return slots;
13599
14339
  }
13600
- function buildChildNavDropSlots(parentHrefKey) {
13601
- const children = listNavChildren(parentHrefKey);
14340
+ function buildRootNavDropSlots(draggedEl) {
14341
+ const items = listReorderableNavItems(draggedEl);
14342
+ if (isVerticalNavLayout(items, draggedEl)) {
14343
+ return buildVerticalRootNavDropSlots(items, draggedEl);
14344
+ }
14345
+ return buildHorizontalRootNavDropSlots(items, draggedEl);
14346
+ }
14347
+ function buildChildNavDropSlots(parentHrefKey, draggedEl) {
14348
+ const children = listNavChildren(parentHrefKey, draggedEl);
13602
14349
  const slots = [];
13603
14350
  const barThickness = 3;
13604
14351
  if (children.length === 0) return slots;
@@ -13619,10 +14366,7 @@ function buildChildNavDropSlots(parentHrefKey) {
13619
14366
  width = first.width;
13620
14367
  } else if (i === children.length) {
13621
14368
  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;
14369
+ const lastGap = children.length >= 2 ? Math.max(0, last.top - children[children.length - 2].getBoundingClientRect().bottom) : edgeGap;
13626
14370
  top = last.bottom + lastGap / 2 - barThickness / 2;
13627
14371
  left = last.left;
13628
14372
  width = last.width;
@@ -13645,18 +14389,19 @@ function buildChildNavDropSlots(parentHrefKey) {
13645
14389
  }
13646
14390
  return slots;
13647
14391
  }
13648
- function buildAllNavDropSlots(draggedHrefKey) {
13649
- const dragged = listNavbarItems().find((el) => el.getAttribute("data-ohw-href-key") === draggedHrefKey);
13650
- if (!dragged) return buildRootNavDropSlots();
14392
+ function buildAllNavDropSlots(draggedHrefKey, draggedEl) {
14393
+ const activeItems = listActiveNavbarItems(draggedEl);
14394
+ 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);
14395
+ if (!dragged) return buildRootNavDropSlots(draggedEl);
13651
14396
  if (isNestedNavChild(dragged)) {
13652
14397
  const parentKey = resolveParentNavHrefKey(dragged);
13653
14398
  if (!parentKey) return [];
13654
- return buildChildNavDropSlots(parentKey);
14399
+ return buildChildNavDropSlots(parentKey, draggedEl);
13655
14400
  }
13656
- return buildRootNavDropSlots();
14401
+ return buildRootNavDropSlots(draggedEl);
13657
14402
  }
13658
- function hitTestNavDropSlot(clientX, clientY, draggedHrefKey) {
13659
- const slots = buildAllNavDropSlots(draggedHrefKey);
14403
+ function hitTestNavDropSlot(clientX, clientY, draggedHrefKey, draggedEl) {
14404
+ const slots = buildAllNavDropSlots(draggedHrefKey, draggedEl);
13660
14405
  let best = null;
13661
14406
  for (const slot of slots) {
13662
14407
  const cx2 = slot.left + slot.width / 2;
@@ -13707,6 +14452,12 @@ function useNavItemDrag({
13707
14452
  setIsItemDragging(false);
13708
14453
  if (keepOpenEl?.isConnected) {
13709
14454
  setNavGroupForceOpen(keepOpenEl, true);
14455
+ requestAnimationFrame(() => {
14456
+ if (keepOpenEl.isConnected) setNavGroupForceOpen(keepOpenEl, true);
14457
+ requestAnimationFrame(() => {
14458
+ if (keepOpenEl.isConnected) setNavGroupForceOpen(keepOpenEl, true);
14459
+ });
14460
+ });
13710
14461
  } else {
13711
14462
  setNavGroupForceOpen(null, false);
13712
14463
  }
@@ -13722,7 +14473,7 @@ function useNavItemDrag({
13722
14473
  }
13723
14474
  session.activeSlot = activeSlot;
13724
14475
  setSiblingHintRects([]);
13725
- const slots = buildAllNavDropSlots(session.hrefKey);
14476
+ const slots = buildAllNavDropSlots(session.hrefKey, session.draggedEl);
13726
14477
  setNavDropSlots(slots);
13727
14478
  const activeIdx = activeSlot ? slots.findIndex(
13728
14479
  (s) => s.parentId === activeSlot.parentId && s.insertIndex === activeSlot.insertIndex
@@ -13758,7 +14509,12 @@ function useNavItemDrag({
13758
14509
  if (session.wasSelected && selectedElRef.current === session.draggedEl) {
13759
14510
  setToolbarRect(rect);
13760
14511
  }
13761
- const initialSlot = hitTestNavDropSlot(session.lastClientX, session.lastClientY, session.hrefKey);
14512
+ const initialSlot = hitTestNavDropSlot(
14513
+ session.lastClientX,
14514
+ session.lastClientY,
14515
+ session.hrefKey,
14516
+ session.draggedEl
14517
+ );
13762
14518
  refreshNavDragVisuals(session, initialSlot, session.lastClientX, session.lastClientY);
13763
14519
  },
13764
14520
  [
@@ -13780,10 +14536,11 @@ function useNavItemDrag({
13780
14536
  }
13781
14537
  const x = typeof clientX === "number" && (clientX !== 0 || clientY !== 0) ? clientX : session.lastClientX;
13782
14538
  const y = typeof clientY === "number" && (clientX !== 0 || clientY !== 0) ? clientY : session.lastClientY;
13783
- const slot = session.activeSlot ?? hitTestNavDropSlot(x, y, session.hrefKey);
14539
+ const slot = session.activeSlot ?? hitTestNavDropSlot(x, y, session.hrefKey, session.draggedEl);
13784
14540
  const planned = slot != null ? planNavItemMove(session.hrefKey, slot.parentId, slot.insertIndex) : null;
13785
14541
  const wasSelected = session.wasSelected;
13786
14542
  const hrefKey = session.hrefKey;
14543
+ const keepDropdownOpen = Boolean(session.draggedEl.closest("[data-ohw-nav-children]"));
13787
14544
  const applySelectionAfterDrop = () => {
13788
14545
  if (!wasSelected) {
13789
14546
  deselectRef.current();
@@ -13817,6 +14574,14 @@ function useNavItemDrag({
13817
14574
  });
13818
14575
  applySelectionAfterDrop();
13819
14576
  clearNavDragVisuals();
14577
+ if (keepDropdownOpen) {
14578
+ requestAnimationFrame(() => {
14579
+ const desktop = document.querySelector("[data-ohw-nav-container]");
14580
+ const drawer = document.querySelector("[data-ohw-nav-drawer]");
14581
+ 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)}"]`);
14582
+ if (link) setNavGroupForceOpen(link, true);
14583
+ });
14584
+ }
13820
14585
  requestAnimationFrame(() => {
13821
14586
  if (editContentRef.current[NAV_ORDER_KEY] === planned.orderJson) {
13822
14587
  applyNavForest(planned.forest);
@@ -13856,7 +14621,7 @@ function useNavItemDrag({
13856
14621
  if (!session) return false;
13857
14622
  e.preventDefault();
13858
14623
  if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
13859
- const slot = hitTestNavDropSlot(e.clientX, e.clientY, session.hrefKey);
14624
+ const slot = hitTestNavDropSlot(e.clientX, e.clientY, session.hrefKey, session.draggedEl);
13860
14625
  refreshNavDragVisualsRef.current(session, slot, e.clientX, e.clientY);
13861
14626
  return true;
13862
14627
  },
@@ -13902,7 +14667,7 @@ function useNavItemDrag({
13902
14667
  clearTextSelection();
13903
14668
  const session = navDragRef.current;
13904
14669
  if (!session) return;
13905
- const slot = hitTestNavDropSlot(e.clientX, e.clientY, session.hrefKey);
14670
+ const slot = hitTestNavDropSlot(e.clientX, e.clientY, session.hrefKey, session.draggedEl);
13906
14671
  refreshNavDragVisualsRef.current(session, slot, e.clientX, e.clientY);
13907
14672
  return;
13908
14673
  }
@@ -13918,7 +14683,8 @@ function useNavItemDrag({
13918
14683
  } catch {
13919
14684
  }
13920
14685
  if (linkPopoverOpenRef.current) setLinkPopover(null);
13921
- if (activeElRef.current) deactivateRef.current();
14686
+ const isNestedDrag = Boolean(pending.el.closest("[data-ohw-nav-children]"));
14687
+ if (activeElRef.current && !isNestedDrag) deactivateRef.current();
13922
14688
  const key = pending.el.getAttribute("data-ohw-href-key");
13923
14689
  if (!key) return;
13924
14690
  beginNavDragRef.current({
@@ -13929,6 +14695,9 @@ function useNavItemDrag({
13929
14695
  lastClientY: e.clientY,
13930
14696
  activeSlot: null
13931
14697
  });
14698
+ if (activeElRef.current && isNestedDrag) {
14699
+ deactivateRef.current();
14700
+ }
13932
14701
  };
13933
14702
  const endPointerDrag = (e) => {
13934
14703
  const pending = navPointerDragRef.current;
@@ -14006,6 +14775,7 @@ function useNavItemDrag({
14006
14775
  );
14007
14776
  return {
14008
14777
  navDragRef,
14778
+ navPointerDragRef,
14009
14779
  navDropSlots,
14010
14780
  activeNavDropIndex,
14011
14781
  startNavLinkDrag,
@@ -14210,15 +14980,17 @@ function useSectionDrag({
14210
14980
  clearSectionDragVisuals();
14211
14981
  return;
14212
14982
  }
14213
- const orderJson = JSON.stringify(entries);
14983
+ const orderJson = JSON.stringify(
14984
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
14985
+ );
14214
14986
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
14215
14987
  setAiSectionOrder(orderJson, window.location.pathname);
14216
14988
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
14217
- applyPersistedOrder(entries);
14989
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
14218
14990
  clearSectionDragVisuals();
14219
14991
  requestAnimationFrame(() => {
14220
14992
  if (editContentRef.current[SECTION_ORDER_KEY] === orderJson) {
14221
- applyPersistedOrder(entries);
14993
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
14222
14994
  }
14223
14995
  requestAnimationFrame(() => {
14224
14996
  window.dispatchEvent(new Event("resize"));
@@ -14251,8 +15023,9 @@ function useSectionDrag({
14251
15023
  const target = e.target;
14252
15024
  if (!(target instanceof HTMLElement)) return;
14253
15025
  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;
15026
+ const inner = target.closest("[data-ohw-section]");
15027
+ if (!inner || isChromeSection(inner) || inner.dataset.ohwSection === "footer") return;
15028
+ const sectionEl = movableUnit(inner);
14256
15029
  if (!topLevelSections().includes(sectionEl)) return;
14257
15030
  startSectionPressDrag(sectionEl, e.clientX, e.clientY, e.pointerId);
14258
15031
  };
@@ -14519,6 +15292,9 @@ function collectEditableNodes(extraContent, root = document) {
14519
15292
  if (el.dataset.ohwEditable === "link") {
14520
15293
  return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref3(el) };
14521
15294
  }
15295
+ if (el.dataset.ohwEditable === "map") {
15296
+ return { key: el.dataset.ohwKey ?? "", type: "map", text: el.dataset.ohwMapQuery ?? "" };
15297
+ }
14522
15298
  return {
14523
15299
  key: el.dataset.ohwKey ?? "",
14524
15300
  type: el.dataset.ohwEditable ?? "text",
@@ -14959,7 +15735,7 @@ var badgeVariants = cva(
14959
15735
  {
14960
15736
  variants: {
14961
15737
  variant: {
14962
- default: "border-transparent bg-primary text-primary-foreground",
15738
+ default: "border-transparent bg-bridge-primary text-primary-foreground",
14963
15739
  secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
14964
15740
  destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
14965
15741
  outline: "text-foreground"
@@ -15081,21 +15857,10 @@ function parseSchedulingInsertAfter(insertAfter) {
15081
15857
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
15082
15858
  };
15083
15859
  }
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;
15860
+ function resolveEntryAnchor(entry) {
15861
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
15862
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
15863
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
15099
15864
  }
15100
15865
  function schedulingMountDepth(insertAfter) {
15101
15866
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -15112,8 +15877,7 @@ function getPageSchedulingEntries(raw) {
15112
15877
  }
15113
15878
  }
15114
15879
  function isSchedulingWidgetMissing(entry) {
15115
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
15116
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
15880
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
15117
15881
  }
15118
15882
  function hasMissingSchedulingWidgets(entries) {
15119
15883
  return entries.some(isSchedulingWidgetMissing);
@@ -15151,18 +15915,18 @@ function initSectionsFromContent(content, removeExisting = false, currentPath =
15151
15915
  } catch {
15152
15916
  }
15153
15917
  }
15154
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
15155
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
15156
- const sectionId = schedulingSectionId(effectiveInsertAfter);
15918
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
15919
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
15920
+ const sectionId = schedulingSectionId(widgetId);
15157
15921
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
15158
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
15159
- if (!mountPoint) return false;
15922
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
15923
+ if (!anchorEl) return false;
15924
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
15160
15925
  const container = document.createElement("div");
15161
15926
  container.dataset.ohwSectionContainer = "scheduling";
15162
- container.dataset.ohwSection = sectionId;
15163
15927
  container.dataset.ohwInstance = sectionId;
15164
- if (insertBefore) {
15165
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
15928
+ if (beforeId) {
15929
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
15166
15930
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
15167
15931
  if (!beforePoint) return false;
15168
15932
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -15173,20 +15937,26 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
15173
15937
  }
15174
15938
  tail.insertAdjacentElement("afterend", container);
15175
15939
  }
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
- });
15940
+ try {
15941
+ const root = (0, import_client2.createRoot)(container);
15942
+ schedulingRoots.set(container, root);
15943
+ (0, import_react_dom3.flushSync)(() => {
15944
+ root.render(
15945
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
15946
+ SchedulingWidget,
15947
+ {
15948
+ notifyOnConnect,
15949
+ initialScheduleId: scheduleId,
15950
+ insertAfter: widgetId
15951
+ }
15952
+ )
15953
+ );
15954
+ });
15955
+ } catch (err) {
15956
+ console.error("[ow:scheduling] render threw", err);
15957
+ container.remove();
15958
+ return false;
15959
+ }
15190
15960
  const tracker = getSectionsTracker();
15191
15961
  let sections = [];
15192
15962
  try {
@@ -15194,10 +15964,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
15194
15964
  } catch {
15195
15965
  }
15196
15966
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
15197
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
15967
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
15198
15968
  sections.push({
15199
15969
  type: "scheduling",
15200
- insertAfter: effectiveInsertAfter,
15970
+ insertAfter: widgetId,
15971
+ anchorId,
15972
+ beforeId: beforeId ?? null,
15201
15973
  pagePath: window.location.pathname,
15202
15974
  ...scheduleId ? { scheduleId } : {}
15203
15975
  });
@@ -15211,7 +15983,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
15211
15983
  for (let i = pending.length - 1; i >= 0; i--) {
15212
15984
  const entry = pending[i];
15213
15985
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
15214
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId ?? null)) {
15986
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
15987
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
15215
15988
  pending.splice(i, 1);
15216
15989
  }
15217
15990
  }
@@ -15303,7 +16076,7 @@ var EDITOR_CHROME_SELECTOR = '[data-ohw-toolbar], [data-ohw-form-toolbar], [data
15303
16076
  function isOverEditorChrome(x, y) {
15304
16077
  return document.elementsFromPoint(x, y).some((el) => el instanceof HTMLElement && el.matches(EDITOR_CHROME_SELECTOR));
15305
16078
  }
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"])';
16079
+ 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
16080
  function getVideoEl2(el) {
15308
16081
  return el instanceof HTMLVideoElement ? el : el.querySelector("video");
15309
16082
  }
@@ -15359,6 +16132,12 @@ function applyVideoSettingNode(key, val) {
15359
16132
  });
15360
16133
  return true;
15361
16134
  }
16135
+ function applyMapQuery(el, val) {
16136
+ if (!(el instanceof HTMLIFrameElement)) return;
16137
+ const nextSrc = `https://www.google.com/maps?q=${encodeURIComponent(val)}&output=embed`;
16138
+ if (el.src !== nextSrc) el.src = nextSrc;
16139
+ el.setAttribute("data-ohw-map-query", val);
16140
+ }
15362
16141
  function applyLinkByKey(key, val) {
15363
16142
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
15364
16143
  if (el.dataset.ohwEditable === "link") applyLinkHref(el, val);
@@ -15369,6 +16148,11 @@ function applyLinkByKey(key, val) {
15369
16148
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
15370
16149
  }
15371
16150
  }
16151
+ function isInsideLinkEditor(target) {
16152
+ return Boolean(
16153
+ 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"]')
16154
+ );
16155
+ }
15372
16156
  function isInsideFloatingPanel(target) {
15373
16157
  return Boolean(target.closest("[data-ohw-floating-panel]"));
15374
16158
  }
@@ -15376,11 +16160,6 @@ function isPointOverFloatingPanel(clientX, clientY) {
15376
16160
  const el = document.elementFromPoint(clientX, clientY);
15377
16161
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
15378
16162
  }
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
16163
  function getHrefKeyFromElement(el) {
15385
16164
  if (!el) return null;
15386
16165
  const anchor = el.closest("[data-ohw-href-key]");
@@ -15393,6 +16172,7 @@ function isNavDropdownPanelOpen(childrenRoot) {
15393
16172
  if (childrenRoot.closest("[data-ohw-nav-drawer]")) return true;
15394
16173
  const group = childrenRoot.closest("[data-ohw-nav-group]");
15395
16174
  if (group?.hasAttribute("data-ohw-nav-force-open")) return true;
16175
+ if (navDropdownsOpenOnClick()) return false;
15396
16176
  if (childrenRoot.clientHeight < 2 || childrenRoot.clientWidth < 2) return false;
15397
16177
  const style = window.getComputedStyle(childrenRoot);
15398
16178
  if (style.display === "none" || style.visibility === "hidden") return false;
@@ -15436,6 +16216,11 @@ function getNavigationItemAnchor(el) {
15436
16216
  function isNavigationItem2(el) {
15437
16217
  return getNavigationItemAnchor(el) !== null;
15438
16218
  }
16219
+ function isNavFooterScopedNavigationItem(el) {
16220
+ return Boolean(
16221
+ el.closest("nav, footer, [data-ohw-nav-container], [data-ohw-nav-drawer]")
16222
+ );
16223
+ }
15439
16224
  function socialWordsClicked(target, item) {
15440
16225
  const text = target.closest('[data-ohw-editable="text"], [data-ohw-editable="plain"]');
15441
16226
  return Boolean(text && item.contains(text));
@@ -15639,13 +16424,14 @@ function getNavigationSelectionParent(el) {
15639
16424
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
15640
16425
  return getFooterLinksContainer();
15641
16426
  }
15642
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
16427
+ 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
16428
  return getNavigationRoot(el);
15644
16429
  }
15645
16430
  return null;
15646
16431
  }
15647
16432
  function collectNavigationItemSiblingHintRects(selected) {
15648
16433
  if (!isNavigationItem2(selected)) return [];
16434
+ if (!isNavFooterScopedNavigationItem(selected)) return [];
15649
16435
  const socialsRow = findSocialsRow(selected);
15650
16436
  if (socialsRow) {
15651
16437
  return listSocialItems(socialsRow).filter((item) => item !== selected).map((item) => item.getBoundingClientRect());
@@ -15854,7 +16640,6 @@ var ICONS = {
15854
16640
  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
16641
  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
16642
  };
15857
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
15858
16643
  var SELECTION_CHROME_GAP2 = 4;
15859
16644
  var TOOLBAR_STROKE_GAP2 = 4;
15860
16645
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -16234,6 +17019,7 @@ function StateToggle({
16234
17019
  );
16235
17020
  }
16236
17021
  var contentCache = /* @__PURE__ */ new Map();
17022
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
16237
17023
  var brandingCache = /* @__PURE__ */ new Map();
16238
17024
  var OHW_LOADER_STYLE = {
16239
17025
  position: "fixed",
@@ -16763,13 +17549,6 @@ function OhhwellsBridge() {
16763
17549
  const [isItemDragging, setIsItemDragging] = (0, import_react17.useState)(false);
16764
17550
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react17.useState)(false);
16765
17551
  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
17552
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react17.useState)(null);
16774
17553
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react17.useState)(null);
16775
17554
  const footerDragRef = (0, import_react17.useRef)(null);
@@ -16787,6 +17566,13 @@ function OhhwellsBridge() {
16787
17566
  const brandKitRef = (0, import_react17.useRef)("");
16788
17567
  const stylesRef = (0, import_react17.useRef)("");
16789
17568
  const pendingDeleteUndoRef = (0, import_react17.useRef)(null);
17569
+ const [floatingPanel, setFloatingPanel] = (0, import_react17.useState)(null);
17570
+ const floatingPanelOpenRef = (0, import_react17.useRef)(false);
17571
+ const setFloatingPanelRef = (0, import_react17.useRef)(setFloatingPanel);
17572
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react17.useState)(null);
17573
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react17.useState)(null);
17574
+ const [editorViewport, setEditorViewport] = (0, import_react17.useState)("desktop");
17575
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react17.useState)(null);
16790
17576
  const [sitePages, setSitePages] = (0, import_react17.useState)([]);
16791
17577
  const [sectionsByPath, setSectionsByPath] = (0, import_react17.useState)({});
16792
17578
  const sectionsPrefetchGenRef = (0, import_react17.useRef)(0);
@@ -16795,9 +17581,21 @@ function OhhwellsBridge() {
16795
17581
  const linkPopoverOpenRef = (0, import_react17.useRef)(false);
16796
17582
  const linkPopoverGraceUntilRef = (0, import_react17.useRef)(0);
16797
17583
  setLinkPopoverRef.current = setLinkPopover;
17584
+ setFloatingPanelRef.current = setFloatingPanel;
16798
17585
  linkPopoverSessionRef.current = linkPopover;
17586
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
17587
+ (0, import_react17.useEffect)(() => {
17588
+ const syncViewport = () => {
17589
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
17590
+ setEditorViewport((prev) => prev === next ? prev : next);
17591
+ };
17592
+ syncViewport();
17593
+ window.addEventListener("resize", syncViewport);
17594
+ return () => window.removeEventListener("resize", syncViewport);
17595
+ }, []);
16799
17596
  const {
16800
17597
  navDragRef,
17598
+ navPointerDragRef,
16801
17599
  navDropSlots,
16802
17600
  activeNavDropIndex,
16803
17601
  startNavLinkDrag,
@@ -17000,7 +17798,12 @@ function OhhwellsBridge() {
17000
17798
  if (owner) persistFieldsRef.current(owner);
17001
17799
  }
17002
17800
  activeElRef.current = null;
17003
- setNavGroupForceOpen(null, false);
17801
+ const preserveNavDropdown = Boolean(navPointerDragRef.current?.el?.closest("[data-ohw-nav-children]")) || Boolean(navDragRef.current?.draggedEl?.closest("[data-ohw-nav-children]")) || Boolean(
17802
+ selectedElRef.current?.closest("[data-ohw-nav-children]") && selectedElRef.current?.closest("[data-ohw-nav-group]")?.hasAttribute("data-ohw-nav-force-open")
17803
+ );
17804
+ if (!preserveNavDropdown) {
17805
+ setNavGroupForceOpen(null, false);
17806
+ }
17004
17807
  setReorderHrefKey(null);
17005
17808
  setReorderDragDisabled(false);
17006
17809
  if (!selectedElRef.current) {
@@ -17122,7 +17925,10 @@ function OhhwellsBridge() {
17122
17925
  setSelectedIsSocialsRow(false);
17123
17926
  const isDropdownTrigger = !isNestedNavChild(navAnchor) && (navItemHasDropdownChildren(navAnchor) || navItemOwnsDropdownPanel(navAnchor));
17124
17927
  if (isNestedNavChild(navAnchor)) {
17125
- setNavGroupForceOpen(navAnchor, true);
17928
+ const group = navAnchor.closest("[data-ohw-nav-group]");
17929
+ if (group?.hasAttribute("data-ohw-nav-force-open")) {
17930
+ setNavGroupForceOpen(navAnchor, true);
17931
+ }
17126
17932
  setNavDropdownPreviewOpen(null);
17127
17933
  } else if (isDropdownTrigger) {
17128
17934
  const group = navAnchor.closest("[data-ohw-nav-group]");
@@ -17204,6 +18010,10 @@ function OhhwellsBridge() {
17204
18010
  const handleNavDropdownOpenChange = (0, import_react17.useCallback)((open) => {
17205
18011
  const selected = selectedElRef.current;
17206
18012
  if (!selected || !isNavigationItem2(selected)) return;
18013
+ if (isNestedNavChild(selected)) return;
18014
+ const group = selected.closest("[data-ohw-nav-group]");
18015
+ const domOpen = Boolean(group?.hasAttribute("data-ohw-nav-force-open"));
18016
+ if (open === domOpen) return;
17207
18017
  setNavGroupForceOpen(selected, open);
17208
18018
  setNavDropdownPreviewOpen(open);
17209
18019
  requestAnimationFrame(() => {
@@ -17733,7 +18543,10 @@ function OhhwellsBridge() {
17733
18543
  clearHrefKeyHover(anchor);
17734
18544
  const isDropdownTrigger = !isNestedNavChild(anchor) && (navItemHasDropdownChildren(anchor) || navItemOwnsDropdownPanel(anchor));
17735
18545
  if (isNestedNavChild(anchor)) {
17736
- setNavGroupForceOpen(anchor, true);
18546
+ const group = anchor.closest("[data-ohw-nav-group]");
18547
+ if (group?.hasAttribute("data-ohw-nav-force-open")) {
18548
+ setNavGroupForceOpen(anchor, true);
18549
+ }
17737
18550
  setNavDropdownPreviewOpen(null);
17738
18551
  } else if (isDropdownTrigger) {
17739
18552
  setNavGroupForceOpen(null, false);
@@ -18120,6 +18933,7 @@ function OhhwellsBridge() {
18120
18933
  }
18121
18934
  if (typeof content[STYLE_STORE_KEY] === "string") {
18122
18935
  stylesRef.current = content[STYLE_STORE_KEY];
18936
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
18123
18937
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
18124
18938
  }
18125
18939
  applyBrandChrome(content);
@@ -18127,11 +18941,11 @@ function OhhwellsBridge() {
18127
18941
  for (const [key, val] of Object.entries(content)) {
18128
18942
  if (key === "__ohw_sections") continue;
18129
18943
  if (key === AI_SECTIONS_KEY) continue;
18944
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
18945
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
18130
18946
  if (key === BRAND_KIT_KEY) continue;
18131
18947
  if (key === STYLE_STORE_KEY) continue;
18132
18948
  if (BRAND_CHROME_KEYS.has(key)) continue;
18133
- if (key === LOGO_PLACEHOLDER_KEY) continue;
18134
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
18135
18949
  if (applyVideoSettingNode(key, val)) continue;
18136
18950
  if (applyCarouselNode(key, val)) continue;
18137
18951
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -18159,6 +18973,8 @@ function OhhwellsBridge() {
18159
18973
  }
18160
18974
  } else if (el.dataset.ohwEditable === "link") {
18161
18975
  applyLinkHref(el, val);
18976
+ } else if (el.dataset.ohwEditable === "map") {
18977
+ applyMapQuery(el, val);
18162
18978
  } else if (el.dataset.ohwEditable === "icon") {
18163
18979
  applyIconMarkup(el, val);
18164
18980
  } else if (el.dataset.ohwEditable === "form") {
@@ -18197,7 +19013,9 @@ function OhhwellsBridge() {
18197
19013
  let cancelled = false;
18198
19014
  setFetchState("loading");
18199
19015
  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) => {
19016
+ const initialPath = pathname;
19017
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
19018
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18201
19019
  if (cancelled) return;
18202
19020
  const content = data?.content ?? {};
18203
19021
  const branding = Boolean(data?.showBranding);
@@ -18306,7 +19124,9 @@ function OhhwellsBridge() {
18306
19124
  }, [isEditMode]);
18307
19125
  (0, import_react17.useEffect)(() => {
18308
19126
  if (isEditMode || fetchState !== "done") return;
19127
+ console.log("env", process.env.NEXT_PUBLIC_FLOWOPS_API_URL);
18309
19128
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19129
+ console.log({ apiUrl, subdomain });
18310
19130
  bindPublishedForms(apiUrl, subdomain, contentCache.get(subdomain) ?? {});
18311
19131
  }, [isEditMode, fetchState, subdomain]);
18312
19132
  (0, import_react17.useEffect)(() => {
@@ -18316,10 +19136,10 @@ function OhhwellsBridge() {
18316
19136
  const applyFromCache = () => {
18317
19137
  const content = contentCache.get(subdomain);
18318
19138
  if (!content) return;
18319
- retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
18320
- initSectionInstancesFromContent(content, window.location.pathname);
18321
19139
  observer?.disconnect();
18322
19140
  try {
19141
+ retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
19142
+ initSectionInstancesFromContent(content, window.location.pathname);
18323
19143
  applyBrandChrome(content);
18324
19144
  if (typeof content[BRAND_KIT_KEY] === "string") {
18325
19145
  applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
@@ -18331,16 +19151,17 @@ function OhhwellsBridge() {
18331
19151
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
18332
19152
  }
18333
19153
  if (typeof content[STYLE_STORE_KEY] === "string") {
19154
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
18334
19155
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
18335
19156
  }
18336
19157
  for (const [key, val] of Object.entries(content)) {
18337
19158
  if (key === "__ohw_sections") continue;
18338
19159
  if (key === AI_SECTIONS_KEY) continue;
19160
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
19161
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
18339
19162
  if (key === BRAND_KIT_KEY) continue;
18340
19163
  if (key === STYLE_STORE_KEY) continue;
18341
19164
  if (BRAND_CHROME_KEYS.has(key)) continue;
18342
- if (key === LOGO_PLACEHOLDER_KEY) continue;
18343
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
18344
19165
  if (applyVideoSettingNode(key, val)) continue;
18345
19166
  if (applyCarouselNode(key, val)) continue;
18346
19167
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -18355,6 +19176,8 @@ function OhhwellsBridge() {
18355
19176
  if (video && video.src !== val) applyVideoSrc(video, val);
18356
19177
  } else if (el.dataset.ohwEditable === "link") {
18357
19178
  applyLinkHref(el, val);
19179
+ } else if (el.dataset.ohwEditable === "map") {
19180
+ applyMapQuery(el, val);
18358
19181
  } else if (el.dataset.ohwEditable === "form") {
18359
19182
  } else if (isIconMarkupValue(val)) {
18360
19183
  } else if (el.innerHTML !== val) {
@@ -18386,6 +19209,17 @@ function OhhwellsBridge() {
18386
19209
  debounceTimer = setTimeout(applyFromCache, 150);
18387
19210
  };
18388
19211
  applyFromCache();
19212
+ const pathCacheKey = `${subdomain}::${pathname}`;
19213
+ if (!fetchedContentPaths.has(pathCacheKey)) {
19214
+ fetchedContentPaths.add(pathCacheKey);
19215
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19216
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
19217
+ if (!data?.content) return;
19218
+ contentCache.set(subdomain, data.content);
19219
+ applyFromCache();
19220
+ }).catch(() => {
19221
+ });
19222
+ }
18389
19223
  observer = new MutationObserver(scheduleApply);
18390
19224
  observer.observe(document.body, { childList: true, subtree: true });
18391
19225
  return () => {
@@ -18410,6 +19244,10 @@ function OhhwellsBridge() {
18410
19244
  deselectRef.current();
18411
19245
  deactivateRef.current();
18412
19246
  }, [pathname, isEditMode]);
19247
+ (0, import_react17.useEffect)(() => {
19248
+ if (!isEditMode) return;
19249
+ initSectionInstancesFromContent(editContentRef.current, pathname);
19250
+ }, [pathname, isEditMode]);
18413
19251
  (0, import_react17.useEffect)(() => {
18414
19252
  const contentForNav = () => {
18415
19253
  if (isEditMode) return editContentRef.current;
@@ -18501,26 +19339,11 @@ function OhhwellsBridge() {
18501
19339
  const t2 = setTimeout(measure, 500);
18502
19340
  const ro = new ResizeObserver(schedule);
18503
19341
  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
19342
  return () => {
18518
19343
  clearTimeout(t1);
18519
19344
  clearTimeout(t2);
18520
19345
  if (raf != null) cancelAnimationFrame(raf);
18521
19346
  ro.disconnect();
18522
- clearResizeTimers();
18523
- window.removeEventListener("resize", handleResize);
18524
19347
  };
18525
19348
  }, [pathname, isEditMode, postToParent2]);
18526
19349
  (0, import_react17.useEffect)(() => {
@@ -18551,28 +19374,19 @@ function OhhwellsBridge() {
18551
19374
  return;
18552
19375
  }
18553
19376
  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
- }
19377
+ const canvasHeight = "var(--ohw-canvas-h, 852px)";
18559
19378
  const baseCss = `
18560
19379
  html { height: auto !important; }
18561
19380
  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; }
19381
+ .min-h-screen, .min-h-svh, .min-h-dvh { min-height: ${canvasHeight} !important; }
19382
+ .h-screen, .h-svh, .h-dvh { height: ${canvasHeight} !important; }
19383
+ /* Literal inline viewport units only \u2014 skip R11 sections that already reference --ohw-canvas-h
19384
+ (their style attribute contains "100svh" only as a var() fallback string). */
19385
+ [style*="100vh"]:not([style*="--ohw-canvas-h"]) { min-height: ${canvasHeight} !important; height: ${canvasHeight} !important; }
19386
+ [style*="100svh"]:not([style*="--ohw-canvas-h"]) { min-height: ${canvasHeight} !important; height: ${canvasHeight} !important; }
19387
+ [style*="100dvh"]:not([style*="--ohw-canvas-h"]) { min-height: ${canvasHeight} !important; height: ${canvasHeight} !important; }
19388
+ /* R11: min-height via --ohw-canvas-h; height must grow with content taller than one screen. */
19389
+ [style*="min-height"][style*="--ohw-canvas-h"] { height: auto !important; }
18576
19390
  /* Emptied text keeps somewhere to click. A label typed down to nothing collapses to a
18577
19391
  couple of pixels, and getting back into it meant hunting for the caret with the mouse.
18578
19392
  Edit mode only \u2014 the published page shows nothing where there is nothing (OHH-736). */
@@ -18719,6 +19533,10 @@ function OhhwellsBridge() {
18719
19533
  outline-offset: 0 !important;
18720
19534
  box-shadow: none !important;
18721
19535
  }
19536
+ /* Open nav dropdown panels paint above AI section selection chrome (2147483000). */
19537
+ [data-ohw-nav-group][data-ohw-nav-force-open] > [data-ohw-nav-children] {
19538
+ z-index: 2147483100 !important;
19539
+ }
18722
19540
  /* Text edit wins over grab/default (must beat [data-ohw-can-drag] *). */
18723
19541
  [data-ohw-editing],
18724
19542
  [data-ohw-editing] *,
@@ -18775,9 +19593,6 @@ function OhhwellsBridge() {
18775
19593
  if (target.closest("[data-ohw-state-toggle]")) return;
18776
19594
  if (target.closest("[data-ohw-max-badge]")) return;
18777
19595
  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
19596
  if (isInsideFloatingPanel(target)) return;
18782
19597
  if (target.closest("[data-ohw-form-toolbar]")) return;
18783
19598
  if (target.closest(
@@ -18785,6 +19600,9 @@ function OhhwellsBridge() {
18785
19600
  )) {
18786
19601
  return;
18787
19602
  }
19603
+ if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
19604
+ clearMediaSelectionRef.current();
19605
+ }
18788
19606
  {
18789
19607
  const formEl = getFormElement(target);
18790
19608
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -18936,14 +19754,6 @@ function OhhwellsBridge() {
18936
19754
  }
18937
19755
  const clickedButton = findClosestButtonLike(target);
18938
19756
  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
19757
  if (isMediaEditable(editable) && !buttonOnMedia) {
18948
19758
  e.preventDefault();
18949
19759
  e.stopPropagation();
@@ -18970,11 +19780,6 @@ function OhhwellsBridge() {
18970
19780
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
18971
19781
  const hrefCtx = getHrefKeyFromElement(hrefLookupTarget);
18972
19782
  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
19783
  if (navAnchor) {
18979
19784
  e.preventDefault();
18980
19785
  e.stopPropagation();
@@ -19144,6 +19949,9 @@ function OhhwellsBridge() {
19144
19949
  setHoveredItemRect(null);
19145
19950
  hoveredNavContainerRef.current = null;
19146
19951
  setHoveredNavContainerRect(null);
19952
+ siblingHintElRef.current = null;
19953
+ setSiblingHintRect(null);
19954
+ setSiblingHintRects([]);
19147
19955
  return;
19148
19956
  }
19149
19957
  {
@@ -19262,7 +20070,6 @@ function OhhwellsBridge() {
19262
20070
  hoveredNavContainerRef.current = null;
19263
20071
  setHoveredNavContainerRect(null);
19264
20072
  hoveredItemElRef.current = editable;
19265
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
19266
20073
  }
19267
20074
  }
19268
20075
  }
@@ -19559,7 +20366,7 @@ function OhhwellsBridge() {
19559
20366
  }
19560
20367
  };
19561
20368
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
19562
- if (linkPopoverOpenRef.current) {
20369
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
19563
20370
  if (hoveredImageRef.current) {
19564
20371
  hoveredImageRef.current = null;
19565
20372
  hoveredImageHasTextOverlapRef.current = false;
@@ -19924,8 +20731,7 @@ function OhhwellsBridge() {
19924
20731
  };
19925
20732
  const handleMouseMove = (e) => {
19926
20733
  const { clientX, clientY } = e;
19927
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
19928
- if (isOverEditorChrome(clientX, clientY)) {
20734
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
19929
20735
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
19930
20736
  formHoverElRef.current = null;
19931
20737
  setFormHoverRect(null);
@@ -19933,6 +20739,12 @@ function OhhwellsBridge() {
19933
20739
  setHoveredItemRect(null);
19934
20740
  hoveredNavContainerRef.current = null;
19935
20741
  setHoveredNavContainerRect(null);
20742
+ siblingHintElRef.current = null;
20743
+ setSiblingHintRect(null);
20744
+ setSiblingHintRects([]);
20745
+ dismissImageHover();
20746
+ clearImageHover();
20747
+ setSectionGap(null);
19936
20748
  return;
19937
20749
  }
19938
20750
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -19944,7 +20756,11 @@ function OhhwellsBridge() {
19944
20756
  if (e.data?.type !== "ow:pointer-sync") return;
19945
20757
  const { clientX, clientY } = e.data;
19946
20758
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
19947
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
20759
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
20760
+ dismissImageHover();
20761
+ clearImageHover();
20762
+ return;
20763
+ }
19948
20764
  if (probeSocialsRowAt(clientX, clientY)) return;
19949
20765
  probeSectionGapAt(clientX, clientY);
19950
20766
  probeImageAt(clientX, clientY);
@@ -20058,7 +20874,11 @@ function OhhwellsBridge() {
20058
20874
  const slotFromItem = hrefKey ? findSocialByHrefKey(hrefKey)?.querySelector('[data-ohw-editable="icon"]') : null;
20059
20875
  const requestedIsIconSlot = Boolean(requestedIconKey) && Boolean(document.querySelector(`[data-ohw-key="${requestedIconKey}"][data-ohw-editable="icon"]`));
20060
20876
  const requestedIsFree = Boolean(requestedIconKey) && !document.querySelector(`[data-ohw-key="${requestedIconKey}"]`);
20061
- const iconKey = requestedIsIconSlot ? requestedIconKey : slotFromItem?.dataset.ohwKey ?? (requestedIsFree ? requestedIconKey : void 0);
20877
+ let iconKey = requestedIsIconSlot ? requestedIconKey : slotFromItem?.dataset.ohwKey ?? (requestedIsFree ? requestedIconKey : void 0);
20878
+ const item = hrefKey ? findSocialByHrefKey(hrefKey) : null;
20879
+ if (item && typeof iconMarkup === "string" && iconMarkup) {
20880
+ iconKey = ensureIconSlot(item) ?? iconKey;
20881
+ }
20062
20882
  if (iconKey && typeof iconMarkup === "string" && iconMarkup) {
20063
20883
  document.querySelectorAll(`[data-ohw-key="${iconKey}"][data-ohw-editable="icon"]`).forEach((el) => {
20064
20884
  applyIconMarkup(el, iconMarkup);
@@ -20068,7 +20888,7 @@ function OhhwellsBridge() {
20068
20888
  }
20069
20889
  if (iconKey && platformId) nodes.push({ key: socialPlatformKey(iconKey), text: platformId });
20070
20890
  if (iconKey && label) {
20071
- const labelKey = socialLabelKey(iconKey);
20891
+ const labelKey = (item ? socialLabelElement(item)?.getAttribute("data-ohw-key") : null) ?? socialLabelKey(iconKey);
20072
20892
  document.querySelectorAll(`[data-ohw-key="${labelKey}"]`).forEach((el) => {
20073
20893
  if (keepLabel && el.textContent?.trim()) return;
20074
20894
  el.textContent = label;
@@ -20223,6 +21043,44 @@ function OhhwellsBridge() {
20223
21043
  if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
20224
21044
  }, 400));
20225
21045
  };
21046
+ const reapCommittedAiSections = (excludeIds) => {
21047
+ const aiState = parseAiSectionsState(aiSectionsRef.current);
21048
+ if (aiState.sections.length === 0) return [];
21049
+ let orderEntries = [];
21050
+ try {
21051
+ const parsed = JSON.parse(editContentRef.current[SECTION_ORDER_KEY] ?? "[]");
21052
+ if (Array.isArray(parsed)) orderEntries = parsed;
21053
+ } catch {
21054
+ return [];
21055
+ }
21056
+ const removedIds = orderEntries.filter((entry) => entry && typeof entry.instanceId === "string" && entry.removed).map((entry) => entry.instanceId);
21057
+ if (removedIds.length === 0) return [];
21058
+ const result = reapRemovedAiSections(aiState, parseStyleStore(stylesRef.current), removedIds, excludeIds);
21059
+ if (!result.changed) return [];
21060
+ const nodes = [];
21061
+ aiSectionsRef.current = serializeAiSectionsState(result.state);
21062
+ nodes.push({ key: AI_SECTIONS_KEY, text: aiSectionsRef.current });
21063
+ const reaped = new Set(result.reapedIds);
21064
+ const nextOrderJson = JSON.stringify(orderEntries.filter((entry) => !reaped.has(entry.instanceId)));
21065
+ editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: nextOrderJson };
21066
+ setAiSectionOrder(nextOrderJson, window.location.pathname);
21067
+ nodes.push({ key: SECTION_ORDER_KEY, text: nextOrderJson });
21068
+ if (result.store) {
21069
+ stylesRef.current = JSON.stringify(result.store);
21070
+ nodes.push({ key: STYLE_STORE_KEY, text: stylesRef.current });
21071
+ }
21072
+ const nextContent = { ...editContentRef.current };
21073
+ for (const key of Object.keys(nextContent)) {
21074
+ if (key.startsWith(AI_SLOT_KEY_PREFIX) && result.slotPrefixes.some((prefix) => key.startsWith(prefix)) && nextContent[key] !== "") {
21075
+ nextContent[key] = "";
21076
+ nodes.push({ key, text: "" });
21077
+ }
21078
+ }
21079
+ editContentRef.current = nextContent;
21080
+ applyAiSectionsToDom(result.state);
21081
+ applyStylesToDom(parseStyleStore(stylesRef.current));
21082
+ return nodes;
21083
+ };
20226
21084
  const handleHydrate = (e) => {
20227
21085
  if (e.data?.type !== "ow:hydrate") return;
20228
21086
  const content = e.data.content;
@@ -20241,6 +21099,7 @@ function OhhwellsBridge() {
20241
21099
  }
20242
21100
  if (typeof content[STYLE_STORE_KEY] === "string") {
20243
21101
  stylesRef.current = content[STYLE_STORE_KEY];
21102
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
20244
21103
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
20245
21104
  }
20246
21105
  applyBrandChrome(content);
@@ -20252,11 +21111,11 @@ function OhhwellsBridge() {
20252
21111
  continue;
20253
21112
  }
20254
21113
  if (key === AI_SECTIONS_KEY) continue;
21114
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
21115
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
20255
21116
  if (key === BRAND_KIT_KEY) continue;
20256
21117
  if (key === STYLE_STORE_KEY) continue;
20257
21118
  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
21119
  if (applyVideoSettingNode(key, val)) continue;
20261
21120
  if (applyCarouselNode(key, val)) continue;
20262
21121
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -20270,6 +21129,8 @@ function OhhwellsBridge() {
20270
21129
  if (video && video.src !== val) applyVideoSrc(video, val);
20271
21130
  } else if (el.dataset.ohwEditable === "link") {
20272
21131
  applyLinkHref(el, val);
21132
+ } else if (el.dataset.ohwEditable === "map") {
21133
+ applyMapQuery(el, val);
20273
21134
  } else if (el.dataset.ohwEditable === "icon") {
20274
21135
  applyIconMarkup(el, val);
20275
21136
  } else if (isIconMarkupValue(val)) {
@@ -20291,8 +21152,16 @@ function OhhwellsBridge() {
20291
21152
  reconcileFooterOrderFromContent(editContentRef.current);
20292
21153
  syncNavigationDragCursorAttrs();
20293
21154
  enforceLinkHrefs();
21155
+ const hydrateReapExclude = /* @__PURE__ */ new Set();
21156
+ const hydratePendingUndo = pendingDeleteUndoRef.current;
21157
+ if (hydratePendingUndo?.sectionInstanceId) hydrateReapExclude.add(hydratePendingUndo.sectionInstanceId);
21158
+ const reapNodes = reapCommittedAiSections(hydrateReapExclude);
21159
+ if (reapNodes.length > 0) postToParentRef.current({ type: "ow:change", nodes: reapNodes });
20294
21160
  const hydratedHeight = document.body.scrollHeight;
20295
21161
  if (hydratedHeight > 50) postToParentRef.current({ type: "ow:height", height: hydratedHeight });
21162
+ if (parseAiSectionsState(aiSectionsRef.current).sections.length > 0) {
21163
+ postAiSectionsChanged();
21164
+ }
20296
21165
  postToParentRef.current({ type: "ow:hydrate-done" });
20297
21166
  };
20298
21167
  const handleUpdateLogoIdentity = (e) => {
@@ -20464,10 +21333,26 @@ function OhhwellsBridge() {
20464
21333
  const handleGetBrand = (e) => {
20465
21334
  if (e.data?.type !== "ow:get-brand") return;
20466
21335
  const template = deriveTemplateBrand();
20467
- const value = brandKitRef.current || (template ? JSON.stringify(template) : "");
21336
+ const fallback = template ?? (() => {
21337
+ const fonts = deriveTemplateFonts();
21338
+ return fonts ? { palette: AI_DEFAULT_BRAND.palette, fonts } : null;
21339
+ })();
21340
+ const value = brandKitRef.current || (fallback ? JSON.stringify(fallback) : "");
20468
21341
  postToParentRef.current({ type: "ow:brand-value", value });
20469
21342
  };
20470
21343
  window.addEventListener("message", handleGetBrand);
21344
+ const handleGetTemplateFonts = (e) => {
21345
+ if (e.data?.type !== "ow:get-template-fonts") return;
21346
+ const fonts = deriveTemplateFonts();
21347
+ postToParentRef.current({ type: "ow:template-fonts-value", value: fonts ? JSON.stringify(fonts) : "" });
21348
+ };
21349
+ window.addEventListener("message", handleGetTemplateFonts);
21350
+ const handleGetTemplateBrand = (e) => {
21351
+ if (e.data?.type !== "ow:get-template-brand") return;
21352
+ const brand = deriveTemplateBrand();
21353
+ postToParentRef.current({ type: "ow:template-brand-value", value: brand ? JSON.stringify(brand) : "" });
21354
+ };
21355
+ window.addEventListener("message", handleGetTemplateBrand);
20471
21356
  const handleMoveSection = (e) => {
20472
21357
  if (e.data?.type !== "ow:move-section") return;
20473
21358
  const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
@@ -20475,8 +21360,11 @@ function OhhwellsBridge() {
20475
21360
  if (!instanceId || !direction) return;
20476
21361
  const entries = moveSectionInstance(instanceId, direction, window.location.pathname);
20477
21362
  if (!entries) return;
20478
- const orderJson = JSON.stringify(entries);
21363
+ const orderJson = JSON.stringify(
21364
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
21365
+ );
20479
21366
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
21367
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
20480
21368
  setAiSectionOrder(orderJson, window.location.pathname);
20481
21369
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20482
21370
  window.dispatchEvent(new Event("resize"));
@@ -20522,8 +21410,11 @@ function OhhwellsBridge() {
20522
21410
  const currentEntries = getPageSectionOrderEntries(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname);
20523
21411
  const entries = deleteSectionInstance(instanceId, window.location.pathname, currentEntries);
20524
21412
  if (!entries) return;
20525
- const orderJson = JSON.stringify(entries);
21413
+ const orderJson = JSON.stringify(
21414
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
21415
+ );
20526
21416
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
21417
+ setAiSectionOrder(orderJson, window.location.pathname);
20527
21418
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20528
21419
  aiSectionApiRef.current?.clear();
20529
21420
  window.dispatchEvent(new Event("resize"));
@@ -20532,6 +21423,7 @@ function OhhwellsBridge() {
20532
21423
  const actionId = newInstanceId();
20533
21424
  pendingDeleteUndoRef.current = {
20534
21425
  actionId,
21426
+ sectionInstanceId: instanceId,
20535
21427
  restore: () => {
20536
21428
  const restoredEntries = getPageSectionOrderEntries(
20537
21429
  editContentRef.current[SECTION_ORDER_KEY],
@@ -20539,8 +21431,11 @@ function OhhwellsBridge() {
20539
21431
  );
20540
21432
  const restored = restoreSectionInstance(instanceId, window.location.pathname, restoredEntries);
20541
21433
  if (!restored) return;
20542
- const restoredJson = JSON.stringify(restored);
21434
+ const restoredJson = JSON.stringify(
21435
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, restored)
21436
+ );
20543
21437
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: restoredJson };
21438
+ setAiSectionOrder(restoredJson, window.location.pathname);
20544
21439
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: restoredJson }] });
20545
21440
  window.dispatchEvent(new Event("resize"));
20546
21441
  const restoreHeight = document.body.scrollHeight;
@@ -20566,7 +21461,9 @@ function OhhwellsBridge() {
20566
21461
  const result = duplicateSectionInstance(instanceId, newId, window.location.pathname, currentEntries);
20567
21462
  if (!result) return;
20568
21463
  const { entries, keyRekeys } = result;
20569
- const orderJson = JSON.stringify(entries);
21464
+ const orderJson = JSON.stringify(
21465
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
21466
+ );
20570
21467
  const nodes = [{ key: SECTION_ORDER_KEY, text: orderJson }];
20571
21468
  for (const { from, to } of keyRekeys) {
20572
21469
  const inherited = editContentRef.current[from];
@@ -20576,6 +21473,7 @@ function OhhwellsBridge() {
20576
21473
  ...editContentRef.current,
20577
21474
  ...Object.fromEntries(nodes.map(({ key, text }) => [key, text]))
20578
21475
  };
21476
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
20579
21477
  setAiSectionOrder(orderJson, window.location.pathname);
20580
21478
  postToParentRef.current({ type: "ow:change", nodes });
20581
21479
  window.dispatchEvent(new Event("resize"));
@@ -20594,6 +21492,12 @@ function OhhwellsBridge() {
20594
21492
  closeLinkPopoverRef.current();
20595
21493
  return;
20596
21494
  }
21495
+ if (floatingPanelOpenRef.current) {
21496
+ setFloatingPanelRef.current(null);
21497
+ deselectRef.current();
21498
+ deactivateRef.current();
21499
+ return;
21500
+ }
20597
21501
  deselectRef.current();
20598
21502
  deactivateRef.current();
20599
21503
  clearMediaSelectionRef.current();
@@ -20826,7 +21730,12 @@ function OhhwellsBridge() {
20826
21730
  }
20827
21731
  if (navDragRef.current) {
20828
21732
  const session = navDragRef.current;
20829
- const slot = hitTestNavDropSlot(session.lastClientX, session.lastClientY, session.hrefKey);
21733
+ const slot = hitTestNavDropSlot(
21734
+ session.lastClientX,
21735
+ session.lastClientY,
21736
+ session.hrefKey,
21737
+ session.draggedEl
21738
+ );
20830
21739
  refreshNavDragVisualsRef.current(session, slot, session.lastClientX, session.lastClientY);
20831
21740
  }
20832
21741
  if (hoveredImageRef.current) {
@@ -20839,6 +21748,10 @@ function OhhwellsBridge() {
20839
21748
  };
20840
21749
  const handleSave = (e) => {
20841
21750
  if (e.data?.type !== "ow:save") return;
21751
+ const pendingUndo = pendingDeleteUndoRef.current;
21752
+ const reapExclude = /* @__PURE__ */ new Set();
21753
+ if (pendingUndo?.sectionInstanceId) reapExclude.add(pendingUndo.sectionInstanceId);
21754
+ const reapNodes = reapCommittedAiSections(reapExclude);
20842
21755
  const nodes = collectEditableNodes(editContentRef.current);
20843
21756
  const tracker = document.querySelector("[data-ohw-sections-tracker]");
20844
21757
  if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
@@ -20858,6 +21771,11 @@ function OhhwellsBridge() {
20858
21771
  const text = specs.length ? JSON.stringify(specs) : editContentRef.current[fieldsKey(formKey)];
20859
21772
  if (text) nodes.push({ key: fieldsKey(formKey), type: "text", text });
20860
21773
  });
21774
+ for (const reapNode of reapNodes) {
21775
+ if (reapNode.key.startsWith(AI_SLOT_KEY_PREFIX) && !nodes.some((n) => n.key === reapNode.key)) {
21776
+ nodes.push({ key: reapNode.key, type: "text", text: reapNode.text });
21777
+ }
21778
+ }
20861
21779
  postToParentRef.current({ type: "ow:save-result", nodes });
20862
21780
  };
20863
21781
  const handleInsertSection = (e) => {
@@ -20868,8 +21786,12 @@ function OhhwellsBridge() {
20868
21786
  if (inserted) {
20869
21787
  const tracker = getSectionsTracker();
20870
21788
  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 });
21789
+ const reportHeight = () => {
21790
+ const h = document.body.scrollHeight;
21791
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
21792
+ };
21793
+ reportHeight();
21794
+ setTimeout(reportHeight, 500);
20873
21795
  }
20874
21796
  };
20875
21797
  const handleSwitchSchedule = (e) => {
@@ -21019,18 +21941,21 @@ function OhhwellsBridge() {
21019
21941
  }
21020
21942
  return null;
21021
21943
  };
21944
+ const isPointInRect = (el, clientX, clientY) => {
21945
+ const r2 = el.getBoundingClientRect();
21946
+ return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
21947
+ };
21022
21948
  const isPointOverEditable = (scope, clientX, clientY) => {
21023
21949
  const editables = scope.querySelectorAll("[data-ohw-editable], [data-ohw-href-key]");
21024
21950
  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;
21951
+ if (isPointInRect(el, clientX, clientY)) return true;
21027
21952
  }
21028
21953
  return false;
21029
21954
  };
21030
21955
  const handleCarouselHover = (e) => {
21031
21956
  const container = findCarouselAtPoint(e.clientX, e.clientY);
21032
21957
  const scope = container?.closest("[data-ohw-section]") ?? container?.parentElement ?? null;
21033
- if (!container || !scope || isPointOverEditable(scope, e.clientX, e.clientY)) {
21958
+ if (!container || !scope || isPointOverEditable(scope, e.clientX, e.clientY) || isPointOverNavigation(e.clientX, e.clientY) || isPointOverBridgeChrome(e.clientX, e.clientY)) {
21034
21959
  setCarouselHover((prev) => prev ? null : prev);
21035
21960
  return;
21036
21961
  }
@@ -21268,15 +22193,17 @@ function OhhwellsBridge() {
21268
22193
  window.removeEventListener("message", handleAiSetBrand);
21269
22194
  window.removeEventListener("message", handleAiSetStyles);
21270
22195
  window.removeEventListener("message", handleGetBrand);
22196
+ window.removeEventListener("message", handleGetTemplateFonts);
22197
+ window.removeEventListener("message", handleGetTemplateBrand);
21271
22198
  window.removeEventListener("message", handleMoveSection);
21272
22199
  window.removeEventListener("message", handlePanelDragging);
21273
22200
  window.removeEventListener("message", handleDeleteSection);
21274
22201
  window.removeEventListener("message", handleDuplicateSection);
21275
22202
  window.removeEventListener("message", handleDeactivate);
21276
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
21277
22203
  window.removeEventListener("message", handleToastAction);
21278
22204
  window.removeEventListener("message", handleFormCount);
21279
22205
  window.removeEventListener("message", handleUiEscape);
22206
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
21280
22207
  autoSaveTimers.current.forEach(clearTimeout);
21281
22208
  autoSaveTimers.current.clear();
21282
22209
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -21479,7 +22406,7 @@ function OhhwellsBridge() {
21479
22406
  postToParent2({
21480
22407
  type: "ow:ready",
21481
22408
  version: "1",
21482
- bridgeVersion: "0.1.91",
22409
+ bridgeVersion: "0.1.93",
21483
22410
  path: pathname,
21484
22411
  nodes: collectEditableNodes(editContentRef.current),
21485
22412
  sections
@@ -22095,7 +23022,7 @@ function OhhwellsBridge() {
22095
23022
  "span",
22096
23023
  {
22097
23024
  "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",
23025
+ 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
23026
  children: formPickCount
22100
23027
  }
22101
23028
  )
@@ -22332,11 +23259,11 @@ function OhhwellsBridge() {
22332
23259
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
22333
23260
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
22334
23261
  children: [
22335
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
23262
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } }),
22336
23263
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
22337
23264
  Badge,
22338
23265
  {
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",
23266
+ 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
23267
  onClick: () => {
22341
23268
  window.parent.postMessage(
22342
23269
  {
@@ -22350,7 +23277,7 @@ function OhhwellsBridge() {
22350
23277
  children: "Add Section"
22351
23278
  }
22352
23279
  ),
22353
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
23280
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } })
22354
23281
  ]
22355
23282
  }
22356
23283
  ),
@@ -22398,6 +23325,59 @@ function OhhwellsBridge() {
22398
23325
  ) : null
22399
23326
  ] });
22400
23327
  }
23328
+
23329
+ // src/ui/EmptySection.tsx
23330
+ var import_link = __toESM(require("next/link"), 1);
23331
+ var import_jsx_runtime34 = require("react/jsx-runtime");
23332
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
23333
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
23334
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
23335
+ "p",
23336
+ {
23337
+ style: {
23338
+ fontFamily: "var(--brand-font-body)",
23339
+ fontSize: "0.75rem",
23340
+ fontWeight: 500,
23341
+ letterSpacing: "0.15em",
23342
+ textTransform: "uppercase",
23343
+ color: "var(--brand-accent)",
23344
+ marginBottom: "1.5rem"
23345
+ },
23346
+ 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" }) })
23347
+ }
23348
+ ),
23349
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
23350
+ "h1",
23351
+ {
23352
+ style: {
23353
+ fontFamily: "var(--brand-font-heading)",
23354
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
23355
+ lineHeight: 1.1,
23356
+ letterSpacing: "-0.025em",
23357
+ color: "var(--brand-text)",
23358
+ marginBottom: "1rem"
23359
+ },
23360
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
23361
+ children: title
23362
+ }
23363
+ ),
23364
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
23365
+ "p",
23366
+ {
23367
+ style: {
23368
+ fontFamily: "var(--brand-font-body)",
23369
+ fontSize: "1rem",
23370
+ lineHeight: 1.7,
23371
+ fontWeight: 300,
23372
+ color: "var(--brand-text-muted)",
23373
+ maxWidth: "340px"
23374
+ },
23375
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
23376
+ children: "This page doesn't have any content yet."
23377
+ }
23378
+ )
23379
+ ] });
23380
+ }
22401
23381
  // Annotate the CommonJS export names for ESM import in node:
22402
23382
  0 && (module.exports = {
22403
23383
  AI_DEFAULT_BRAND,
@@ -22415,6 +23395,7 @@ function OhhwellsBridge() {
22415
23395
  DropdownMenuItem,
22416
23396
  DropdownMenuSeparator,
22417
23397
  DropdownMenuTrigger,
23398
+ EmptySection,
22418
23399
  ItemActionToolbar,
22419
23400
  ItemInteractionLayer,
22420
23401
  LinkEditorPanel,