@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.js CHANGED
@@ -70,6 +70,9 @@ function isRenderableTree(value) {
70
70
  // src/lib/ai-sections-store.ts
71
71
  var AI_SECTIONS_KEY = "__ohw_ai_sections";
72
72
  var AI_SLOT_KEY_PREFIX = "ai.";
73
+ function aiSlotKeyPrefixFor(sectionId) {
74
+ return `${AI_SLOT_KEY_PREFIX}${sectionId}.`;
75
+ }
73
76
  var EMPTY_AI_SECTIONS = { v: 1, sections: [] };
74
77
  function parseAiSectionsState(raw) {
75
78
  if (!raw) return EMPTY_AI_SECTIONS;
@@ -181,6 +184,33 @@ function deleteSectionFromState(state, sectionId) {
181
184
  if (removed.includes(sectionId)) return state;
182
185
  return { ...state, v: 1, sections: state.sections, removed: [...removed, sectionId] };
183
186
  }
187
+ function reapRemovedAiSections(state, store, removedIds, excludeIds = /* @__PURE__ */ new Set()) {
188
+ const generated = new Set(state.sections.map((entry) => entry.id));
189
+ const reapedIds = [...new Set(removedIds)].filter((id) => generated.has(id) && !excludeIds.has(id));
190
+ if (reapedIds.length === 0) {
191
+ return { state, store, reapedIds: [], slotPrefixes: [], changed: false };
192
+ }
193
+ const reaped = new Set(reapedIds);
194
+ const slotPrefixes = reapedIds.map(aiSlotKeyPrefixFor);
195
+ const nextState = {
196
+ ...state,
197
+ v: 1,
198
+ sections: state.sections.filter((entry) => !reaped.has(entry.id))
199
+ };
200
+ let nextStore = store;
201
+ if (store) {
202
+ const sections = {};
203
+ for (const [key, override] of Object.entries(store.sections)) {
204
+ if (!reaped.has(key)) sections[key] = override;
205
+ }
206
+ const nodes = {};
207
+ for (const [key, override] of Object.entries(store.nodes)) {
208
+ if (!slotPrefixes.some((prefix) => key.startsWith(prefix))) nodes[key] = override;
209
+ }
210
+ nextStore = { v: 1, sections, nodes };
211
+ }
212
+ return { state: nextState, store: nextStore, reapedIds, slotPrefixes, changed: true };
213
+ }
184
214
 
185
215
  // src/lib/brand-chrome.ts
186
216
  var BRAND_NAME_KEY = "__ohw_brand_name";
@@ -249,16 +279,17 @@ var LEGACY_BRAND_VAR_NAMES = [
249
279
  "text-muted",
250
280
  "surface",
251
281
  "border",
252
- "on-primary",
253
- "navbar-background"
282
+ "on-primary"
254
283
  ].map((role) => `--brand-${role}`);
255
284
  var FONT_VARS = {
256
285
  heading: ["--font-heading", "--font-display", "--brand-font-heading"],
257
286
  body: ["--font-body", "--brand-font-body"]
258
287
  };
259
288
  var BRAND_FONT_LINK_ID = "ohw-brand-fonts";
260
- function brandColorVars(kit) {
261
- const { dark, primary, accent, light } = kit.palette;
289
+ var CUSTOM_FONT_STYLE_ID = "ohw-brand-custom-fonts";
290
+ var CUSTOM_FONT_FORMATS = /* @__PURE__ */ new Set(["woff2", "woff", "truetype", "opentype"]);
291
+ function brandColorVars(palette) {
292
+ const { dark, primary, accent, light } = palette;
262
293
  const mix = (a, aPct, b) => `color-mix(in srgb, ${a} ${aPct}%, ${b})`;
263
294
  const surface = mix(light, 95, dark);
264
295
  const border = mix(light, 85, dark);
@@ -281,22 +312,42 @@ function brandColorVars(kit) {
281
312
  "--brand-border": border,
282
313
  // Buttons/bands painted in the primary colour assume it's dark/saturated enough to need
283
314
  // light text on top — the same assumption LOGO_IMAGE's light-on-dark navbar mark makes.
284
- "--brand-on-primary": light,
285
- "--brand-navbar-background": dark
315
+ "--brand-on-primary": light
286
316
  };
287
317
  }
318
+ function parseCustomFontWeight(raw) {
319
+ if (typeof raw !== "object" || raw === null) return null;
320
+ const w = raw;
321
+ if (typeof w.weight !== "number" || !Number.isFinite(w.weight)) return null;
322
+ if (typeof w.url !== "string" || !w.url) return null;
323
+ if (typeof w.format !== "string" || !CUSTOM_FONT_FORMATS.has(w.format)) return null;
324
+ const weightEnd = typeof w.weightEnd === "number" && Number.isFinite(w.weightEnd) && w.weightEnd > w.weight ? w.weightEnd : void 0;
325
+ return { weight: w.weight, ...weightEnd !== void 0 ? { weightEnd } : {}, url: w.url, format: w.format };
326
+ }
327
+ function parseCustomFont(raw) {
328
+ if (typeof raw !== "object" || raw === null) return null;
329
+ const f = raw;
330
+ if (typeof f.family !== "string" || !f.family) return null;
331
+ if (typeof f.label !== "string" || !f.label) return null;
332
+ if (!Array.isArray(f.weights)) return null;
333
+ const weights = f.weights.map(parseCustomFontWeight).filter((w) => w !== null);
334
+ if (weights.length === 0) return null;
335
+ return { family: f.family, label: f.label, weights };
336
+ }
288
337
  function parseBrandKit(raw) {
289
338
  if (!raw) return null;
290
339
  try {
291
340
  const parsed = JSON.parse(raw);
292
341
  const p = parsed?.palette;
293
342
  const f = parsed?.fonts;
294
- if (!p || !f || typeof p.dark !== "string" || typeof p.primary !== "string" || typeof p.accent !== "string" || typeof p.light !== "string" || typeof f.heading !== "string" || typeof f.body !== "string") {
295
- return null;
296
- }
343
+ const palette = p && typeof p.dark === "string" && typeof p.primary === "string" && typeof p.accent === "string" && typeof p.light === "string" ? { dark: p.dark, primary: p.primary, accent: p.accent, light: p.light } : void 0;
344
+ const fonts = f && typeof f.heading === "string" && typeof f.body === "string" ? { heading: f.heading, body: f.body } : void 0;
345
+ const customFonts = Array.isArray(parsed?.customFonts) ? parsed.customFonts.map(parseCustomFont).filter((c) => c !== null) : void 0;
346
+ if (!palette && !fonts && !customFonts) return null;
297
347
  return {
298
- palette: { dark: p.dark, primary: p.primary, accent: p.accent, light: p.light },
299
- fonts: { heading: f.heading, body: f.body }
348
+ ...palette ? { palette } : {},
349
+ ...fonts ? { fonts } : {},
350
+ ...customFonts ? { customFonts } : {}
300
351
  };
301
352
  } catch {
302
353
  return null;
@@ -306,11 +357,16 @@ function familyOf(stack) {
306
357
  const first = stack.split(",")[0]?.trim() ?? "";
307
358
  return first.replace(/^['"]|['"]$/g, "");
308
359
  }
360
+ function quoteFamily(family) {
361
+ return `'${family.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
362
+ }
363
+ var WEIGHT_SCALE = [100, 200, 300, 400, 500, 600, 700, 800, 900];
309
364
  function loadBrandFonts(families) {
310
365
  const unique = [...new Set(families.map((f) => f.trim()).filter(Boolean))];
311
366
  if (unique.length === 0) return;
312
- const spec = unique.map((f) => `${f.replace(/ /g, "+")}:400,500,600,700`).join("|");
313
- const href = `https://fonts.googleapis.com/css?family=${spec}&display=swap`;
367
+ const weights = WEIGHT_SCALE.join(";");
368
+ const spec = unique.map((f) => `family=${f.replace(/ /g, "+")}:wght@${weights}`).join("&");
369
+ const href = `https://fonts.googleapis.com/css2?${spec}&display=swap`;
314
370
  let link = document.getElementById(BRAND_FONT_LINK_ID);
315
371
  if (!link) {
316
372
  link = document.createElement("link");
@@ -320,18 +376,54 @@ function loadBrandFonts(families) {
320
376
  }
321
377
  if (link.href !== href) link.href = href;
322
378
  }
379
+ function escapeCssString(value) {
380
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
381
+ }
382
+ function loadCustomFonts(fonts) {
383
+ const list = fonts ?? [];
384
+ let style = document.getElementById(CUSTOM_FONT_STYLE_ID);
385
+ if (list.length === 0) {
386
+ style?.remove();
387
+ return;
388
+ }
389
+ if (!style) {
390
+ style = document.createElement("style");
391
+ style.id = CUSTOM_FONT_STYLE_ID;
392
+ document.head.appendChild(style);
393
+ }
394
+ style.textContent = list.flatMap(
395
+ (font) => font.weights.map((w) => {
396
+ const weightDescriptor = w.weightEnd ? `${w.weight} ${w.weightEnd}` : `${w.weight}`;
397
+ return `@font-face { font-family: '${escapeCssString(font.family)}'; src: url('${escapeCssString(w.url)}') format('${w.format}'); font-weight: ${weightDescriptor}; font-display: swap; }`;
398
+ })
399
+ ).join("\n");
400
+ }
323
401
  function applyBrandToDom(kit) {
324
402
  const root = document.documentElement;
325
403
  if (!kit) {
326
404
  for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES]) root.style.removeProperty(name);
327
405
  for (const name of [...FONT_VARS.heading, ...FONT_VARS.body]) root.style.removeProperty(name);
328
406
  document.getElementById(BRAND_FONT_LINK_ID)?.remove();
407
+ document.getElementById(CUSTOM_FONT_STYLE_ID)?.remove();
329
408
  return;
330
409
  }
331
- for (const [name, value] of Object.entries(brandColorVars(kit))) root.style.setProperty(name, value);
332
- for (const name of FONT_VARS.heading) root.style.setProperty(name, kit.fonts.heading);
333
- for (const name of FONT_VARS.body) root.style.setProperty(name, kit.fonts.body);
334
- loadBrandFonts([familyOf(kit.fonts.heading), familyOf(kit.fonts.body)]);
410
+ if (kit.palette) {
411
+ for (const [name, value] of Object.entries(brandColorVars(kit.palette))) root.style.setProperty(name, value);
412
+ }
413
+ if (kit.customFonts) {
414
+ loadCustomFonts(kit.customFonts);
415
+ }
416
+ if (kit.fonts) {
417
+ const heading = quoteFamily(kit.fonts.heading);
418
+ const body = quoteFamily(kit.fonts.body);
419
+ for (const name of FONT_VARS.heading) root.style.setProperty(name, heading);
420
+ for (const name of FONT_VARS.body) root.style.setProperty(name, body);
421
+ const customFamilies = new Set((kit.customFonts ?? []).map((f) => f.family));
422
+ const googleFamilies = [familyOf(kit.fonts.heading), familyOf(kit.fonts.body)].filter(
423
+ (f) => !customFamilies.has(f)
424
+ );
425
+ loadBrandFonts(googleFamilies);
426
+ }
335
427
  }
336
428
 
337
429
  // src/lib/section-styles.ts
@@ -388,6 +480,10 @@ function styleSheetCss() {
388
480
  `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px}px !important; padding-bottom: ${px}px !important; }`
389
481
  );
390
482
  }
483
+ rules.push(
484
+ `[data-ohw-style-corners="sharp"] :is(.card, [data-ohw-card], img, picture, figure, [data-ohw-editable="bg-image"]) { border-radius: 0 !important; }`,
485
+ `[data-ohw-style-corners="sharp"] :has(> img) { border-radius: 0 !important; }`
486
+ );
391
487
  for (const align of ["left", "center", "right"]) {
392
488
  rules.push(`[data-ohw-style-align="${align}"] { text-align: ${align} !important; }`);
393
489
  }
@@ -418,6 +514,7 @@ var SECTION_ATTRS = {
418
514
  headlineScale: "data-ohw-style-headline",
419
515
  imageAspect: "data-ohw-style-aspect",
420
516
  spacing: "data-ohw-style-spacing",
517
+ cornerStyle: "data-ohw-style-corners",
421
518
  align: "data-ohw-style-align"
422
519
  };
423
520
  var NODE_WROTE_ATTR = "data-ohw-style-node";
@@ -559,9 +656,535 @@ function applyStylesToDom(store) {
559
656
  import { flushSync } from "react-dom";
560
657
  import { createRoot } from "react-dom/client";
561
658
 
659
+ // src/lib/sections.ts
660
+ var LINK_PICKER_EXCLUDED_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
661
+ function isChromeSection(el) {
662
+ return el.matches("header, nav, footer, aside");
663
+ }
664
+ function titleCaseSectionId(id) {
665
+ return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
666
+ }
667
+ function parseSectionsFromRoot(root) {
668
+ const seen = /* @__PURE__ */ new Set();
669
+ const sections = [];
670
+ for (const el of root.querySelectorAll("[data-ohw-section]")) {
671
+ const id = el.getAttribute("data-ohw-section") ?? "";
672
+ if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
673
+ if (el.parentElement?.closest("[data-ohw-section]")) continue;
674
+ if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
675
+ continue;
676
+ seen.add(id);
677
+ const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
678
+ sections.push({ id, label });
679
+ }
680
+ return sections;
681
+ }
682
+ function collectSectionsFromDom() {
683
+ if (typeof document === "undefined") return [];
684
+ return parseSectionsFromRoot(document);
685
+ }
686
+ function parseSectionsFromHtml(html) {
687
+ const doc = new DOMParser().parseFromString(html, "text/html");
688
+ return parseSectionsFromRoot(doc);
689
+ }
690
+
691
+ // src/lib/section-instances.ts
692
+ var SECTION_ORDER_KEY = "__ohw_section_order";
693
+ var REMOVED_ATTR = "data-ohw-section-removed";
694
+ function isRemovedSection(el) {
695
+ return el.hasAttribute(REMOVED_ATTR);
696
+ }
697
+ function movableUnit(el) {
698
+ return el.closest("[data-ohw-section-container]") ?? el;
699
+ }
700
+ function sectionTypeOf(el) {
701
+ return el.getAttribute("data-ohw-section") ?? el.querySelector("[data-ohw-section]")?.getAttribute("data-ohw-section") ?? "";
702
+ }
703
+ function sectionElementOf(el) {
704
+ return el.hasAttribute("data-ohw-section") ? el : el.querySelector("[data-ohw-section]") ?? el;
705
+ }
706
+ function collectTopLevelUnits(predicate) {
707
+ const seen = /* @__PURE__ */ new Set();
708
+ const result = [];
709
+ document.querySelectorAll("[data-ohw-section]").forEach((el) => {
710
+ if (!predicate(el)) return;
711
+ const unit = movableUnit(el);
712
+ if (unit.parentElement?.closest("[data-ohw-section],[data-ohw-section-container]")) return;
713
+ if (seen.has(unit)) return;
714
+ seen.add(unit);
715
+ result.push(unit);
716
+ });
717
+ return result;
718
+ }
719
+ function topLevelSections() {
720
+ return collectTopLevelUnits((el) => !isChromeSection(el) && !isRemovedSection(movableUnit(el)));
721
+ }
722
+ function instanceIdOf(el) {
723
+ return el.dataset.ohwInstance ?? el.dataset.ohwSection ?? "";
724
+ }
725
+ function findByInstanceId(instanceId) {
726
+ const escapedId = CSS.escape(instanceId);
727
+ const direct = document.querySelector(`[data-ohw-instance="${escapedId}"]`);
728
+ if (direct) return movableUnit(direct);
729
+ const bare = document.querySelector(`[data-ohw-section="${escapedId}"]:not([data-ohw-instance])`);
730
+ return bare ? movableUnit(bare) : null;
731
+ }
732
+ function planSectionMove(instanceId, targetIndex, currentPath) {
733
+ const sections = topLevelSections();
734
+ const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
735
+ if (index === -1) return null;
736
+ const dragged = sections[index];
737
+ const others = sections.filter((_, i) => i !== index);
738
+ const clamped = Math.max(0, Math.min(targetIndex, others.length));
739
+ const reordered = [...others.slice(0, clamped), dragged, ...others.slice(clamped)];
740
+ return reordered.map((el, order) => ({
741
+ instanceId: instanceIdOf(el),
742
+ type: sectionTypeOf(el),
743
+ order,
744
+ pagePath: currentPath
745
+ }));
746
+ }
747
+ function moveSectionInstance(instanceId, direction, currentPath) {
748
+ const sections = topLevelSections();
749
+ const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
750
+ if (index === -1) return null;
751
+ const siblingIndex = direction === "up" ? index - 1 : index + 1;
752
+ if (siblingIndex < 0 || siblingIndex >= sections.length) return null;
753
+ const entries = planSectionMove(instanceId, siblingIndex, currentPath);
754
+ if (!entries) return null;
755
+ applyPersistedOrder(entries);
756
+ return entries;
757
+ }
758
+ function syncRemovedFlags(entries) {
759
+ const removedIds = new Set(entries.filter((e) => e.removed).map((e) => e.instanceId));
760
+ document.querySelectorAll(`[${REMOVED_ATTR}]`).forEach((el) => {
761
+ if (!removedIds.has(instanceIdOf(el))) {
762
+ el.style.removeProperty("display");
763
+ el.removeAttribute(REMOVED_ATTR);
764
+ }
765
+ });
766
+ for (const id of removedIds) {
767
+ const el = findByInstanceId(id);
768
+ if (el) {
769
+ el.style.display = "none";
770
+ el.setAttribute(REMOVED_ATTR, "");
771
+ }
772
+ }
773
+ }
774
+ function applyPersistedOrder(entries) {
775
+ syncRemovedFlags(entries);
776
+ if (entries.length === 0) return;
777
+ const sections = topLevelSections();
778
+ if (sections.length === 0) return;
779
+ const orderIndex = new Map(entries.map((e) => [e.instanceId, e.order]));
780
+ const ordered = [...sections].sort((a, b) => {
781
+ const aOrder = orderIndex.get(instanceIdOf(a));
782
+ const bOrder = orderIndex.get(instanceIdOf(b));
783
+ if (aOrder === void 0 && bOrder === void 0) return 0;
784
+ if (aOrder === void 0) return 1;
785
+ if (bOrder === void 0) return -1;
786
+ return aOrder - bOrder;
787
+ });
788
+ let prev = null;
789
+ for (const el of ordered) {
790
+ if (prev) prev.after(el);
791
+ prev = el;
792
+ }
793
+ }
794
+ function setSectionRemoved(instanceId, currentPath, existingEntries, removed) {
795
+ if (!findByInstanceId(instanceId)) return null;
796
+ const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
797
+ const allSections = collectTopLevelUnits((el) => !isChromeSection(el));
798
+ allSections.forEach((el, order) => {
799
+ const id = instanceIdOf(el);
800
+ if (!byId.has(id)) {
801
+ byId.set(id, { instanceId: id, type: sectionTypeOf(el), order, pagePath: currentPath });
802
+ }
803
+ });
804
+ const target = byId.get(instanceId);
805
+ if (!target) return null;
806
+ byId.set(instanceId, { ...target, removed });
807
+ const entries = Array.from(byId.values());
808
+ applyPersistedOrder(entries);
809
+ return entries;
810
+ }
811
+ function deleteSectionInstance(instanceId, currentPath, existingEntries) {
812
+ return setSectionRemoved(instanceId, currentPath, existingEntries, true);
813
+ }
814
+ function restoreSectionInstance(instanceId, currentPath, existingEntries) {
815
+ return setSectionRemoved(instanceId, currentPath, existingEntries, false);
816
+ }
817
+ function duplicateSectionInstance(instanceId, newId, currentPath, existingEntries) {
818
+ const original = findByInstanceId(instanceId);
819
+ if (!original) return null;
820
+ const clone = original.cloneNode(true);
821
+ clone.setAttribute("data-ohw-instance", newId);
822
+ const keyRekeys = rekeySectionSubtree(clone, newId);
823
+ original.insertAdjacentElement("afterend", clone);
824
+ const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
825
+ const entries = topLevelSections().map((el, order) => {
826
+ const id = instanceIdOf(el);
827
+ return {
828
+ instanceId: id,
829
+ type: sectionTypeOf(el),
830
+ order,
831
+ pagePath: currentPath,
832
+ ...byId.get(id)?.removed ? { removed: true } : {}
833
+ };
834
+ });
835
+ applyPersistedOrder(entries);
836
+ return { entries, keyRekeys };
837
+ }
838
+ function newInstanceId() {
839
+ return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `instance-${Date.now()}-${Math.random().toString(36).slice(2)}`;
840
+ }
841
+ function getPageSectionOrderEntries(raw, currentPath) {
842
+ if (!raw) return [];
843
+ try {
844
+ const entries = JSON.parse(raw);
845
+ return entries.filter((e) => !e.pagePath || e.pagePath === currentPath);
846
+ } catch {
847
+ return [];
848
+ }
849
+ }
850
+ function mergePageSectionOrder(raw, currentPath, pageEntries) {
851
+ let all = [];
852
+ if (raw) {
853
+ try {
854
+ const parsed = JSON.parse(raw);
855
+ if (Array.isArray(parsed)) all = parsed;
856
+ } catch {
857
+ }
858
+ }
859
+ const otherPages = all.filter((e) => e && e.pagePath && e.pagePath !== currentPath);
860
+ const pageIds = new Set(pageEntries.map((e) => e.instanceId));
861
+ const removedHere = all.filter(
862
+ (e) => e && (!e.pagePath || e.pagePath === currentPath) && e.removed && !pageIds.has(e.instanceId)
863
+ );
864
+ return [...otherPages, ...removedHere, ...pageEntries];
865
+ }
866
+ function rekeySectionSubtree(root, instanceId) {
867
+ const suffix = `::${instanceId}`;
868
+ const pairs = [];
869
+ const rekey = (el, attr) => {
870
+ const current = el.getAttribute(attr);
871
+ if (!current) return;
872
+ const base = current.includes("::") ? current.slice(0, current.indexOf("::")) : current;
873
+ const next = `${base}${suffix}`;
874
+ el.setAttribute(attr, next);
875
+ pairs.push({ from: current, to: next });
876
+ };
877
+ if (root.hasAttribute("data-ohw-key")) rekey(root, "data-ohw-key");
878
+ if (root.hasAttribute("data-ohw-href-key")) rekey(root, "data-ohw-href-key");
879
+ root.querySelectorAll("[data-ohw-key]").forEach((el) => rekey(el, "data-ohw-key"));
880
+ root.querySelectorAll("[data-ohw-href-key]").forEach((el) => rekey(el, "data-ohw-href-key"));
881
+ return pairs;
882
+ }
883
+ function initSectionInstancesFromContent(content, currentPath) {
884
+ document.querySelectorAll("[data-ohw-section]:not([data-ohw-instance])").forEach((el) => {
885
+ el.setAttribute("data-ohw-instance", el.getAttribute("data-ohw-section") ?? "");
886
+ });
887
+ document.querySelectorAll("[data-ohw-section-container]:not([data-ohw-instance])").forEach((el) => {
888
+ const type = sectionTypeOf(el);
889
+ if (type) el.setAttribute("data-ohw-instance", type);
890
+ });
891
+ const entries = getPageSectionOrderEntries(content[SECTION_ORDER_KEY], currentPath);
892
+ for (const entry of entries) {
893
+ if (entry.instanceId === entry.type) continue;
894
+ if (document.querySelector(`[data-ohw-instance="${CSS.escape(entry.instanceId)}"]`)) continue;
895
+ const original = document.querySelector(
896
+ `[data-ohw-section="${CSS.escape(entry.type)}"][data-ohw-instance="${CSS.escape(entry.type)}"]`
897
+ );
898
+ if (!original) continue;
899
+ const clone = original.cloneNode(true);
900
+ clone.setAttribute("data-ohw-instance", entry.instanceId);
901
+ rekeySectionSubtree(clone, entry.instanceId);
902
+ original.insertAdjacentElement("afterend", clone);
903
+ }
904
+ applyPersistedOrder(entries);
905
+ }
906
+
562
907
  // src/ui/ai-tree/AiTreeRenderer.tsx
563
908
  import React from "react";
564
909
  import { ArrowLeft, ArrowRight, ChevronDown, icons as lucideIcons } from "lucide-react";
910
+
911
+ // src/lib/placeholder-imagery.ts
912
+ var U = (id) => `https://images.unsplash.com/photo-${id}?auto=format&fit=crop&w=1600&q=80`;
913
+ var GENERIC = [
914
+ U("1441986300917-64674bd600d8"),
915
+ U("1486406146926-c627a92ad1ab"),
916
+ U("1497032628192-86f99bcd76bc"),
917
+ U("1521737604893-d14cc237f11d"),
918
+ U("1522071820081-009f0129c71c"),
919
+ U("1519389950473-47ba0277781c"),
920
+ U("1460925895917-afdab827c52f"),
921
+ U("1504384308090-c894fdcc538d")
922
+ ];
923
+ var PEOPLE = [
924
+ U("1500648767791-00dcc994a43e"),
925
+ U("1494790108377-be9c29b29330"),
926
+ U("1507003211169-0a1dd7228f2d"),
927
+ U("1438761681033-6461ffad8d80"),
928
+ U("1544005313-94ddf0286df2"),
929
+ U("1472099645785-5658abf4ff4e"),
930
+ U("1519085360753-af0119f7cbe7"),
931
+ U("1534528741775-53994a69daeb")
932
+ ];
933
+ var THEMED = [
934
+ {
935
+ keywords: ["portrait", "headshot", "person", "people", "team", "staff", "avatar", "founder", "face"],
936
+ pool: PEOPLE
937
+ },
938
+ {
939
+ keywords: ["pet", "dog", "cat", "puppy", "kitten", "vet", "animal"],
940
+ pool: [
941
+ U("1548199973-03cce0bbc87b"),
942
+ U("1450778869180-41d0601e046e"),
943
+ U("1583511655857-d19b40a7a54e"),
944
+ U("1587300003388-59208cc962cb"),
945
+ U("1517849845537-4d257902454a"),
946
+ U("1601758228041-f3b2795255f1")
947
+ ]
948
+ },
949
+ {
950
+ keywords: [
951
+ "baker",
952
+ "bakery",
953
+ "cafe",
954
+ "coffee",
955
+ "latte",
956
+ "restaurant",
957
+ "pastr",
958
+ "bread",
959
+ "cake",
960
+ "cater",
961
+ "chef",
962
+ "kitchen",
963
+ "food",
964
+ "pizza",
965
+ "dessert",
966
+ "brunch",
967
+ "bistro",
968
+ "deli",
969
+ "dish",
970
+ "menu"
971
+ ],
972
+ pool: [
973
+ U("1509440159596-0249088772ff"),
974
+ U("1555507036-ab1f4038808a"),
975
+ U("1517433670267-08bbd4be890f"),
976
+ U("1486427944299-d1955d23e34d"),
977
+ U("1504754524776-8f4f37790ca0"),
978
+ U("1495474472287-4d71bcdd2085"),
979
+ U("1521017432531-fbd92d768814"),
980
+ U("1556909114-f6e7ad7d3136")
981
+ ]
982
+ },
983
+ {
984
+ keywords: [
985
+ "shop",
986
+ "store",
987
+ "boutique",
988
+ "retail",
989
+ "clothing",
990
+ "fashion",
991
+ "jewel",
992
+ "gift",
993
+ "florist",
994
+ "market",
995
+ "grocer",
996
+ "product",
997
+ "storefront"
998
+ ],
999
+ pool: [
1000
+ U("1441984904996-e0b6ba687e04"),
1001
+ U("1472851294608-062f824d29cc"),
1002
+ U("1523381210434-271e8be1f52b"),
1003
+ U("1534452203293-494d7ddbf7e0"),
1004
+ U("1445205170230-053b83016050"),
1005
+ U("1560243563-062bfc001d68")
1006
+ ]
1007
+ },
1008
+ {
1009
+ keywords: [
1010
+ "yoga",
1011
+ "pilates",
1012
+ "fitness",
1013
+ "gym",
1014
+ "workout",
1015
+ "trainer",
1016
+ "wellness",
1017
+ "meditat",
1018
+ "massage",
1019
+ "therap",
1020
+ "physio",
1021
+ "chiro",
1022
+ "nutrition",
1023
+ "spa",
1024
+ "studio"
1025
+ ],
1026
+ pool: [
1027
+ U("1544367567-0f2fcb009e0b"),
1028
+ U("1506126613408-eca07ce68773"),
1029
+ U("1545205597-3d9d02c29597"),
1030
+ U("1552196563-55cd4e45efb3"),
1031
+ U("1518611012118-696072aa579a"),
1032
+ U("1571019613454-1cb2f99b2d8b"),
1033
+ U("1540555700478-4be289fbecef"),
1034
+ U("1519824145371-296894a0daa9")
1035
+ ]
1036
+ },
1037
+ {
1038
+ keywords: [
1039
+ "salon",
1040
+ "hairdress",
1041
+ "haircut",
1042
+ "barber",
1043
+ "manicure",
1044
+ "pedicure",
1045
+ "nails",
1046
+ "beauty",
1047
+ "makeup",
1048
+ "cosmetic",
1049
+ "eyelash",
1050
+ "eyebrow",
1051
+ "skincare",
1052
+ "esthetic",
1053
+ "waxing",
1054
+ "hair"
1055
+ ],
1056
+ pool: [
1057
+ U("1560066984-138dadb4c035"),
1058
+ U("1522337660859-02fbefca4702"),
1059
+ U("1562322140-8baeececf3df"),
1060
+ U("1521590832167-7bcbfaa6381f"),
1061
+ U("1487412947147-5cebf100ffc2"),
1062
+ U("1526045478516-99145907023c")
1063
+ ]
1064
+ },
1065
+ {
1066
+ keywords: [
1067
+ "cleaning",
1068
+ "plumb",
1069
+ "electric",
1070
+ "landscap",
1071
+ "contractor",
1072
+ "handyman",
1073
+ "renov",
1074
+ "hvac",
1075
+ "roofing",
1076
+ "painting",
1077
+ "carpentry",
1078
+ "flooring",
1079
+ "movers",
1080
+ "construction",
1081
+ "tools"
1082
+ ],
1083
+ pool: [
1084
+ U("1581578731548-c64695cc6952"),
1085
+ U("1504307651254-35680f356dfd"),
1086
+ U("1581092160562-40aa08e78837"),
1087
+ U("1621905251189-08b45d6a269e"),
1088
+ U("1558618666-fcd25c85cd64"),
1089
+ U("1585128792020-803d29415281")
1090
+ ]
1091
+ },
1092
+ {
1093
+ keywords: [
1094
+ "legal",
1095
+ "attorney",
1096
+ "lawyer",
1097
+ "account",
1098
+ "bookkeep",
1099
+ "consult",
1100
+ "coaching",
1101
+ "financ",
1102
+ "insurance",
1103
+ "realtor",
1104
+ "estate",
1105
+ "marketing",
1106
+ "agency",
1107
+ "office",
1108
+ "business",
1109
+ "desk"
1110
+ ],
1111
+ pool: [
1112
+ U("1497366216548-37526070297c"),
1113
+ U("1497366811353-6870744d04b2"),
1114
+ U("1454165804606-c3d57bc86b40"),
1115
+ U("1521791136064-7986c2920216"),
1116
+ U("1556761175-b413da4baf72"),
1117
+ U("1542744173-8e7e53415bb0")
1118
+ ]
1119
+ },
1120
+ {
1121
+ keywords: [
1122
+ "wedding",
1123
+ "event",
1124
+ "party",
1125
+ "celebrat",
1126
+ "venue",
1127
+ "community",
1128
+ "nonprofit",
1129
+ "charity",
1130
+ "workshop",
1131
+ "photograph",
1132
+ "concert"
1133
+ ],
1134
+ pool: [
1135
+ U("1511578314322-379afb476865"),
1136
+ U("1501281668745-f7f57925c3b4"),
1137
+ U("1523580494863-6f3031224c94"),
1138
+ U("1540575467063-178a50c2df87"),
1139
+ U("1505236858219-8359eb29e329"),
1140
+ U("1528605248644-14dd04022da1")
1141
+ ]
1142
+ }
1143
+ ];
1144
+ function poolForSubject(subject) {
1145
+ for (const theme of THEMED) {
1146
+ if (theme.keywords.some((k) => subject.includes(k))) {
1147
+ return theme.pool;
1148
+ }
1149
+ }
1150
+ return GENERIC;
1151
+ }
1152
+ function mixedHash(text) {
1153
+ let hash = 2166136261;
1154
+ for (let i = 0; i < text.length; i++) {
1155
+ hash ^= text.charCodeAt(i);
1156
+ hash = Math.imul(hash, 16777619);
1157
+ }
1158
+ return hash >>> 16 & 65535;
1159
+ }
1160
+ function resolvePlaceholderRef(ref) {
1161
+ const match = /^placeholder:([a-z0-9-]+)$/.exec(ref);
1162
+ if (!match) return null;
1163
+ const subject = match[1];
1164
+ const pool = poolForSubject(subject.replace(/-\d+$/, ""));
1165
+ return pool[mixedHash(ref) % pool.length];
1166
+ }
1167
+ function collectPlaceholderRefs(tree) {
1168
+ const seen = /* @__PURE__ */ new Set();
1169
+ for (const match of JSON.stringify(tree ?? null).matchAll(/"(placeholder:[a-z0-9-]+)"/gu)) {
1170
+ seen.add(match[1]);
1171
+ }
1172
+ return [...seen];
1173
+ }
1174
+ function buildPlaceholderMap(tree) {
1175
+ const map = {};
1176
+ const cursor = /* @__PURE__ */ new Map();
1177
+ for (const ref of collectPlaceholderRefs(tree)) {
1178
+ const subject = ref.slice("placeholder:".length).replace(/-\d+$/, "");
1179
+ const pool = poolForSubject(subject);
1180
+ const start = cursor.get(pool) ?? mixedHash(ref) % pool.length;
1181
+ map[ref] = pool[start % pool.length];
1182
+ cursor.set(pool, start + 1);
1183
+ }
1184
+ return map;
1185
+ }
1186
+
1187
+ // src/ui/ai-tree/AiTreeRenderer.tsx
565
1188
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
566
1189
  function lucideByName(name) {
567
1190
  const pascal = name.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
@@ -575,6 +1198,7 @@ var typeStyle = (spec, font) => ({
575
1198
  fontWeight: spec.weight
576
1199
  });
577
1200
  var str = (value) => typeof value === "string" ? value : "";
1201
+ var cardRadius = (slots) => slots.cornerStyle === "sharp" ? 0 : AI_TREE_TOKENS.radiusCard;
578
1202
  var featureLines = (value) => value.split(/\n|<br\s*\/?>/i).map((line) => line.trim()).filter(Boolean);
579
1203
  var CHECK_MASK = `data:image/svg+xml,${encodeURIComponent(
580
1204
  '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>'
@@ -601,6 +1225,25 @@ var FEATURE_LINE_CSS = [
601
1225
  `background-color:currentColor;-webkit-mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px;`,
602
1226
  `mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px}`
603
1227
  ].join("");
1228
+ function buttonShellStyle(ctx, fullWidth) {
1229
+ const bs = ctx.buttonStyle;
1230
+ if (bs) {
1231
+ return {
1232
+ borderRadius: bs.radius,
1233
+ ...bs.padding ? { padding: bs.padding } : {},
1234
+ ...bs.fontFamily ? { fontFamily: bs.fontFamily } : { fontFamily: ctx.brand.fonts.body },
1235
+ ...bs.fontSize ? { fontSize: bs.fontSize } : {},
1236
+ ...bs.fontWeight ? { fontWeight: bs.fontWeight } : {},
1237
+ ...bs.letterSpacing && bs.letterSpacing !== "normal" ? { letterSpacing: bs.letterSpacing } : {},
1238
+ ...bs.textTransform && bs.textTransform !== "none" ? { textTransform: bs.textTransform } : {}
1239
+ };
1240
+ }
1241
+ return {
1242
+ borderRadius: AI_TREE_TOKENS.radiusButton,
1243
+ padding: fullWidth ? `${AI_TREE_TOKENS.spacing2}px ${AI_TREE_TOKENS.spacing4}px` : `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`,
1244
+ ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
1245
+ };
1246
+ }
604
1247
  function hexLuminance(color) {
605
1248
  const m = /^#([0-9a-f]{6})$/i.exec(color.trim());
606
1249
  if (!m) return null;
@@ -617,6 +1260,12 @@ function hexContrast(a, b) {
617
1260
  const [hi, lo] = la > lb ? [la, lb] : [lb, la];
618
1261
  return (hi + 0.05) / (lo + 0.05);
619
1262
  }
1263
+ function primaryButtonLabel(brand) {
1264
+ const darkC = hexContrast(brand.palette.primary, brand.palette.dark);
1265
+ const lightC = hexContrast(brand.palette.primary, AI_TREE_TOKENS.textPrimaryForeground);
1266
+ if (darkC === null || lightC === null) return AI_TREE_TOKENS.textPrimaryForeground;
1267
+ return darkC > lightC ? brand.palette.dark : AI_TREE_TOKENS.textPrimaryForeground;
1268
+ }
620
1269
  function accentBandContext(brand) {
621
1270
  const p = brand.palette;
622
1271
  const lightWins = (hexContrast(p.light, p.primary) ?? 99) >= (hexContrast(p.dark, p.primary) ?? 0);
@@ -634,6 +1283,22 @@ function accentBandContext(brand) {
634
1283
  function textAttrs(ctx, path) {
635
1284
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
636
1285
  }
1286
+ var AI_RESPONSIVE_CSS = [
1287
+ "@media (max-width: 960px) {",
1288
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
1289
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
1290
+ "}",
1291
+ "@media (max-width: 640px) {",
1292
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
1293
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
1294
+ // Group containers flatten to a column on phones; span placements come along for free.
1295
+ " [data-ai-responsive] [data-ai-group] { display: flex !important; flex-direction: column !important; }",
1296
+ " [data-ai-responsive] [data-ai-group] > * { grid-column: auto !important; }",
1297
+ " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
1298
+ " [data-ai-responsive] { overflow-x: hidden; }",
1299
+ " [data-ai-responsive] img { max-width: 100%; }",
1300
+ "}"
1301
+ ].join("\n");
637
1302
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
638
1303
  function MediaBox({
639
1304
  refValue,
@@ -646,13 +1311,17 @@ function MediaBox({
646
1311
  const url = refValue ? ctx.resolveMedia(refValue) : null;
647
1312
  const isIcon = /^(lucide|simple):/.test(refValue);
648
1313
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
649
- const editAttrs = ctx.keyFor && editPath && !isIcon ? { "data-ohw-key": ctx.keyFor(editPath), "data-ohw-editable": "image" } : {};
1314
+ const editAttrs = ctx.keyFor && editPath ? {
1315
+ "data-ohw-key": ctx.keyFor(editPath),
1316
+ "data-ohw-editable": isIcon ? "icon" : "image"
1317
+ } : {};
650
1318
  if (isIcon) {
651
1319
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
652
1320
  return /* @__PURE__ */ jsx(
653
1321
  "span",
654
1322
  {
655
1323
  "data-ai-icon": refValue,
1324
+ ...editAttrs,
656
1325
  style: {
657
1326
  display: "inline-flex",
658
1327
  width: 48,
@@ -716,12 +1385,15 @@ function ButtonEl({
716
1385
  width: fullWidth ? "100%" : void 0,
717
1386
  alignItems: "center",
718
1387
  justifyContent: "center",
719
- padding: fullWidth ? `${AI_TREE_TOKENS.spacing2}px ${AI_TREE_TOKENS.spacing4}px` : `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`,
720
- borderRadius: AI_TREE_TOKENS.radiusButton,
721
1388
  textDecoration: "none",
722
1389
  cursor: "pointer",
723
- ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body),
724
- ...secondary ? { border: `1px solid ${ctx.brand.palette.dark}`, color: ctx.brand.palette.dark } : { background: ctx.brand.palette.primary, color: ctx.buttonLabel ?? AI_TREE_TOKENS.textPrimaryForeground }
1390
+ ...buttonShellStyle(ctx, fullWidth),
1391
+ ...secondary ? { border: `1px solid ${ctx.brand.palette.dark}`, color: ctx.brand.palette.dark } : (
1392
+ // Off-band, prefer the template button's measured fill/label pair — a template may
1393
+ // fill its CTAs with any token (hvac: accent bg, primary text). On an accent band
1394
+ // the flipped palette keeps contrast, so the brand-driven colours stay.
1395
+ !ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : { background: ctx.brand.palette.primary, color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand) }
1396
+ )
725
1397
  },
726
1398
  children: /* @__PURE__ */ jsx("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
727
1399
  }
@@ -897,10 +1569,11 @@ function PricingCard({ node, ctx, path }) {
897
1569
  return /* @__PURE__ */ jsxs(
898
1570
  "div",
899
1571
  {
1572
+ "data-ohw-card": "",
900
1573
  style: {
901
1574
  background: hasBg ? ctx.brand.palette.light : "transparent",
902
1575
  border: `1px solid ${dark}`,
903
- borderRadius: AI_TREE_TOKENS.radiusCard,
1576
+ borderRadius: cardRadius(slots),
904
1577
  padding: AI_TREE_TOKENS.paddingBlock,
905
1578
  display: "flex",
906
1579
  flexDirection: "column",
@@ -1005,10 +1678,11 @@ function TestimonialCard({ node, ctx, path }) {
1005
1678
  return /* @__PURE__ */ jsx(
1006
1679
  "div",
1007
1680
  {
1681
+ "data-ohw-card": "",
1008
1682
  "data-ai-avatar-pos": avatarPos ?? void 0,
1009
1683
  style: {
1010
1684
  background: hasBg ? ctx.cardSurface : "transparent",
1011
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1685
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1012
1686
  overflow: "hidden",
1013
1687
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1014
1688
  minWidth: 0
@@ -1043,10 +1717,11 @@ function TeamCard({ node, ctx, path }) {
1043
1717
  return /* @__PURE__ */ jsxs(
1044
1718
  "div",
1045
1719
  {
1720
+ "data-ohw-card": "",
1046
1721
  "data-ai-avatar-pos": avatarPos ?? void 0,
1047
1722
  style: {
1048
1723
  background: hasBg ? ctx.cardSurface : "transparent",
1049
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1724
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1050
1725
  overflow: "hidden",
1051
1726
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1052
1727
  minWidth: 0,
@@ -1217,9 +1892,10 @@ function CardBlock({ node, ctx, path }) {
1217
1892
  return /* @__PURE__ */ jsxs(
1218
1893
  "div",
1219
1894
  {
1895
+ "data-ohw-card": "",
1220
1896
  style: {
1221
1897
  background: hasBg ? ctx.cardSurface : "transparent",
1222
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1898
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1223
1899
  overflow: "hidden",
1224
1900
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1225
1901
  display: horizontal ? "flex" : "block",
@@ -1247,7 +1923,7 @@ function CardBlock({ node, ctx, path }) {
1247
1923
  ) : /* @__PURE__ */ jsx(
1248
1924
  "div",
1249
1925
  {
1250
- style: /^(lucide|simple):/.test(mediaRef) ? { padding: `${AI_TREE_TOKENS.paddingBlock}px ${AI_TREE_TOKENS.paddingBlock}px 0` } : hasBg ? void 0 : { borderRadius: AI_TREE_TOKENS.radiusCard, overflow: "hidden" },
1926
+ style: /^(lucide|simple):/.test(mediaRef) ? { padding: `${AI_TREE_TOKENS.paddingBlock}px ${AI_TREE_TOKENS.paddingBlock}px 0` } : hasBg ? void 0 : { borderRadius: cardRadius(slots), overflow: "hidden" },
1251
1927
  children: media
1252
1928
  }
1253
1929
  )),
@@ -1538,7 +2214,7 @@ function CollectionBlock({ node, ctx, path }) {
1538
2214
  return /* @__PURE__ */ jsx(
1539
2215
  "div",
1540
2216
  {
1541
- "data-ai-grid": "",
2217
+ "data-ai-grid": String(itemsPerRow),
1542
2218
  style: {
1543
2219
  display: "grid",
1544
2220
  gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
@@ -1658,6 +2334,32 @@ function renderNode(node, ctx, path) {
1658
2334
  if (child) {
1659
2335
  return renderNode(child, ctx, `${path}.c0`);
1660
2336
  }
2337
+ if (str(slots.provider) === "map" && str(slots.query)) {
2338
+ const query = str(slots.query);
2339
+ const mapAttrs = ctx.keyFor ? {
2340
+ "data-ohw-key": ctx.keyFor(`${path}.query`),
2341
+ "data-ohw-editable": "map",
2342
+ "data-ohw-map-query": query
2343
+ } : {};
2344
+ return /* @__PURE__ */ jsx(
2345
+ "iframe",
2346
+ {
2347
+ ...mapAttrs,
2348
+ "data-ai-embed": "map",
2349
+ title: str(slots.title) || "Map",
2350
+ src: `https://www.google.com/maps?q=${encodeURIComponent(query)}&output=embed`,
2351
+ loading: "lazy",
2352
+ referrerPolicy: "no-referrer-when-downgrade",
2353
+ style: {
2354
+ width: "100%",
2355
+ minHeight: 320,
2356
+ border: 0,
2357
+ borderRadius: AI_TREE_TOKENS.radiusCard,
2358
+ display: "block"
2359
+ }
2360
+ }
2361
+ );
2362
+ }
1661
2363
  return /* @__PURE__ */ jsx(
1662
2364
  "div",
1663
2365
  {
@@ -1761,15 +2463,14 @@ function renderNode(node, ctx, path) {
1761
2463
  alignSelf: submitAlign,
1762
2464
  border: "none",
1763
2465
  cursor: "pointer",
1764
- padding: "12px 24px",
1765
- // Corner radius follows the host template's own buttons (measured from a template
1766
- // CTA); 8px only when the page has no template button to match.
1767
- borderRadius: ctx.buttonRadius ?? 8,
1768
- // Brand-styled: primary fill, brand-derived label colour (not a fixed token) so it
1769
- // reads correctly on custom palettes.
1770
- background: ctx.brand.palette.primary,
1771
- color: ctx.buttonLabel ?? ctx.brand.palette.light,
1772
- ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
2466
+ // Shape/padding/typography follow the host template's own buttons.
2467
+ ...buttonShellStyle(ctx),
2468
+ // Brand-styled: the template button's measured fill/label pair off-band, else
2469
+ // primary fill with a brand-derived label so it reads on custom palettes.
2470
+ ...!ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : {
2471
+ background: ctx.brand.palette.primary,
2472
+ color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand)
2473
+ }
1773
2474
  },
1774
2475
  children: /* @__PURE__ */ jsx("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
1775
2476
  },
@@ -1804,7 +2505,7 @@ function renderNode(node, ctx, path) {
1804
2505
  function AiTreeRenderer({
1805
2506
  tree,
1806
2507
  brand,
1807
- buttonRadius,
2508
+ buttonStyle,
1808
2509
  resolveMedia,
1809
2510
  editKeyPrefix
1810
2511
  }) {
@@ -1814,13 +2515,18 @@ function AiTreeRenderer({
1814
2515
  const resolvedBrand = brand ?? AI_DEFAULT_BRAND;
1815
2516
  const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
1816
2517
  const blockBrand = band?.brand ?? resolvedBrand;
2518
+ const placeholderMap = buildPlaceholderMap(tree);
1817
2519
  const ctx = {
1818
2520
  brand: blockBrand,
1819
- resolveMedia: resolveMedia ?? (() => null),
2521
+ // An owner/library ref resolves through the host resolver; a `placeholder:<subject>` ref the
2522
+ // host cannot resolve falls back to real stock photography (the per-section map first, then a
2523
+ // standalone resolve), so generated galleries, image rows, and overlay backgrounds arrive with
2524
+ // photos instead of grey boxes.
2525
+ resolveMedia: (ref) => resolveMedia?.(ref) ?? placeholderMap[ref] ?? resolvePlaceholderRef(ref),
1820
2526
  cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
1821
2527
  keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
1822
2528
  sectionAlignment: (tree.settings ?? {}).alignment === "center" ? "center" : "left",
1823
- buttonRadius,
2529
+ buttonStyle,
1824
2530
  ...band ? { buttonLabel: band.buttonLabel } : {}
1825
2531
  };
1826
2532
  const settings = tree.settings ?? {};
@@ -1861,6 +2567,7 @@ function AiTreeRenderer({
1861
2567
  {
1862
2568
  "data-ai-section": tree.tag ?? "",
1863
2569
  ...bgAttrs,
2570
+ "data-ai-responsive": "",
1864
2571
  style: {
1865
2572
  position: "relative",
1866
2573
  padding: `${pad}px 0`,
@@ -1871,12 +2578,13 @@ function AiTreeRenderer({
1871
2578
  color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
1872
2579
  },
1873
2580
  children: [
1874
- isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
2581
+ /* @__PURE__ */ jsx("style", { children: AI_RESPONSIVE_CSS }),
1875
2582
  /* @__PURE__ */ jsx("style", { children: AI_MOBILE_CSS }),
2583
+ isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
1876
2584
  /* @__PURE__ */ jsx(
1877
2585
  "div",
1878
2586
  {
1879
- "data-ai-container": "",
2587
+ "data-ai-section-inner": "",
1880
2588
  style: {
1881
2589
  position: "relative",
1882
2590
  maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
@@ -1887,7 +2595,7 @@ function AiTreeRenderer({
1887
2595
  children: tree.rows.map((row, r2) => /* @__PURE__ */ jsx(
1888
2596
  "div",
1889
2597
  {
1890
- "data-ai-row": "",
2598
+ "data-ai-columns": "",
1891
2599
  style: {
1892
2600
  display: "grid",
1893
2601
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -1926,21 +2634,63 @@ function AiTreeRenderer({
1926
2634
  import { jsx as jsx2 } from "react/jsx-runtime";
1927
2635
  var CONTAINER_ATTR = "data-ohw-ai-generated";
1928
2636
  var REPLACED_ATTR = "data-ohw-ai-replaced-by";
1929
- var REMOVED_ATTR = "data-ohw-ai-removed";
2637
+ var REMOVED_ATTR2 = "data-ohw-ai-removed";
1930
2638
  var TEMPLATE_HIDDEN_ATTR = "data-ohw-ai-template-hidden";
1931
2639
  var CHROME_SECTION_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
2640
+ function isChromeSection2(el) {
2641
+ if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) return true;
2642
+ return el.tagName === "HEADER" || el.tagName === "FOOTER";
2643
+ }
1932
2644
  function readRootVar(name) {
1933
2645
  if (typeof document === "undefined") return "";
1934
2646
  return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
1935
2647
  }
2648
+ function normalizeColorToHex(value) {
2649
+ if (!value || typeof document === "undefined") return value;
2650
+ const canvas = document.createElement("canvas");
2651
+ canvas.width = 1;
2652
+ canvas.height = 1;
2653
+ const ctx = canvas.getContext("2d");
2654
+ if (!ctx) return value;
2655
+ ctx.fillStyle = value;
2656
+ ctx.fillRect(0, 0, 1, 1);
2657
+ const [r2, g, b] = ctx.getImageData(0, 0, 1, 1).data;
2658
+ const toHex = (n) => n.toString(16).padStart(2, "0");
2659
+ return `#${toHex(r2)}${toHex(g)}${toHex(b)}`;
2660
+ }
2661
+ var GENERIC_FONT_KEYWORDS = /* @__PURE__ */ new Set([
2662
+ "serif",
2663
+ "sans-serif",
2664
+ "monospace",
2665
+ "cursive",
2666
+ "fantasy",
2667
+ "system-ui",
2668
+ "ui-serif",
2669
+ "ui-sans-serif",
2670
+ "ui-monospace",
2671
+ "ui-rounded",
2672
+ "math",
2673
+ "emoji",
2674
+ "fangsong"
2675
+ ]);
2676
+ function primaryFontFamily(stack) {
2677
+ const first = stack.split(",").map((part) => part.trim().replace(/^["']|["']$/g, "")).find((part) => part && !GENERIC_FONT_KEYWORDS.has(part.toLowerCase()));
2678
+ return first ?? "";
2679
+ }
2680
+ function humanizeFontFamily(name) {
2681
+ 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());
2682
+ }
2683
+ function readFontVar(name) {
2684
+ return humanizeFontFamily(primaryFontFamily(readRootVar(name)));
2685
+ }
1936
2686
  function deriveBrandOverride() {
1937
2687
  const dark = readRootVar("--ohw-brand-dark");
1938
2688
  const primary = readRootVar("--ohw-brand-primary");
1939
2689
  const light = readRootVar("--ohw-brand-light");
1940
2690
  if (!dark || !primary || !light) return null;
1941
2691
  const accent = readRootVar("--ohw-brand-accent");
1942
- const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1943
- const body = readRootVar("--font-body");
2692
+ const heading = readFontVar("--font-heading") || readFontVar("--font-display");
2693
+ const body = readFontVar("--font-body");
1944
2694
  return {
1945
2695
  palette: { dark, primary, accent: accent || dark, light },
1946
2696
  fonts: {
@@ -1949,28 +2699,93 @@ function deriveBrandOverride() {
1949
2699
  }
1950
2700
  };
1951
2701
  }
1952
- function deriveTemplateBrand() {
1953
- const dark = readRootVar("--color-dark");
1954
- const primary = readRootVar("--color-primary");
1955
- const light = readRootVar("--color-light");
2702
+ function deriveTemplateBrandLive() {
2703
+ const dark = readRootVar("--color-dark") || readRootVar("--brand-text");
2704
+ const primary = readRootVar("--color-primary") || readRootVar("--brand-primary");
2705
+ const light = readRootVar("--color-light") || readRootVar("--brand-background");
1956
2706
  if (!dark || !primary || !light) return null;
1957
- const accent = readRootVar("--color-accent");
1958
- const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1959
- const body = readRootVar("--font-body");
2707
+ const accent = readRootVar("--color-accent") || readRootVar("--brand-accent");
2708
+ const heading = readFontVar("--brand-font-heading") || readFontVar("--font-heading") || readFontVar("--font-display");
2709
+ const body = readFontVar("--brand-font-body") || readFontVar("--font-body");
1960
2710
  return {
1961
- palette: { dark, primary, accent: accent || dark, light },
2711
+ palette: {
2712
+ dark: normalizeColorToHex(dark),
2713
+ primary: normalizeColorToHex(primary),
2714
+ accent: normalizeColorToHex(accent || dark),
2715
+ light: normalizeColorToHex(light)
2716
+ },
1962
2717
  fonts: {
1963
2718
  heading: heading || AI_DEFAULT_BRAND.fonts.heading,
1964
2719
  body: body || AI_DEFAULT_BRAND.fonts.body
1965
2720
  }
1966
2721
  };
1967
2722
  }
1968
- function deriveTemplateButtonRadius() {
2723
+ function deriveTemplateFontsLive() {
2724
+ const heading = readFontVar("--brand-font-heading") || readFontVar("--font-heading") || readFontVar("--font-display");
2725
+ const body = readFontVar("--brand-font-body") || readFontVar("--font-body");
2726
+ if (!heading && !body) return null;
2727
+ return {
2728
+ heading: heading || AI_DEFAULT_BRAND.fonts.heading,
2729
+ body: body || AI_DEFAULT_BRAND.fonts.body
2730
+ };
2731
+ }
2732
+ var OVERRIDE_VAR_NAMES = [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES, ...FONT_VARS.heading, ...FONT_VARS.body];
2733
+ function withOverrideStripped(read) {
2734
+ const root = document.documentElement;
2735
+ const restore = OVERRIDE_VAR_NAMES.map((name) => [name, root.style.getPropertyValue(name)]);
2736
+ for (const name of OVERRIDE_VAR_NAMES) root.style.removeProperty(name);
2737
+ try {
2738
+ return read();
2739
+ } finally {
2740
+ for (const [name, value] of restore) if (value) root.style.setProperty(name, value);
2741
+ }
2742
+ }
2743
+ var TEMPLATE_BRAND_SNAPSHOT = typeof document === "undefined" ? null : withOverrideStripped(deriveTemplateBrandLive);
2744
+ var TEMPLATE_FONTS_SNAPSHOT = typeof document === "undefined" ? null : withOverrideStripped(deriveTemplateFontsLive);
2745
+ function deriveTemplateBrand() {
2746
+ return TEMPLATE_BRAND_SNAPSHOT;
2747
+ }
2748
+ function deriveTemplateFonts() {
2749
+ return TEMPLATE_FONTS_SNAPSHOT;
2750
+ }
2751
+ function deriveTemplateButtonStyle() {
1969
2752
  if (typeof document === "undefined") return null;
1970
- const btn = document.querySelector('[data-ohw-role="button"]');
2753
+ const candidates = Array.from(
2754
+ document.querySelectorAll('[data-ohw-role="button"]')
2755
+ ).filter((el) => !el.closest(`[${CONTAINER_ATTR}]`));
2756
+ const isFilled = (el) => {
2757
+ const bg = getComputedStyle(el).backgroundColor;
2758
+ if (!bg || bg === "transparent") return false;
2759
+ const alpha = bg.match(/rgba?\([^)]*,\s*([\d.]+)\)$/);
2760
+ return !alpha || parseFloat(alpha[1]) > 0;
2761
+ };
2762
+ const btn = candidates.find(isFilled) ?? candidates[0];
1971
2763
  if (!btn) return null;
1972
- const radius = getComputedStyle(btn).borderTopLeftRadius;
1973
- return radius || null;
2764
+ const cs = getComputedStyle(btn);
2765
+ const filled = isFilled(btn);
2766
+ const corners = [
2767
+ cs.borderTopLeftRadius,
2768
+ cs.borderTopRightRadius,
2769
+ cs.borderBottomRightRadius,
2770
+ cs.borderBottomLeftRadius
2771
+ ].map((v) => v || "0px");
2772
+ const radius = corners.every((v) => v === corners[0]) ? corners[0] : corners.join(" ");
2773
+ const px = (v) => parseFloat(v) || 0;
2774
+ const lineHeight = px(cs.lineHeight) || px(cs.fontSize) * 1.2;
2775
+ const contentH = btn.getBoundingClientRect().height - px(cs.borderTopWidth) - px(cs.borderBottomWidth);
2776
+ const impliedY = Math.round(Math.max(0, (contentH - lineHeight) / 2));
2777
+ const padY = Math.max(px(cs.paddingTop), px(cs.paddingBottom), impliedY);
2778
+ const padX = Math.max(px(cs.paddingLeft), px(cs.paddingRight));
2779
+ return {
2780
+ radius: radius || "10px",
2781
+ padding: `${padY}px ${padX}px`,
2782
+ ...filled ? { background: cs.backgroundColor, color: cs.color } : {},
2783
+ fontFamily: cs.fontFamily || "",
2784
+ fontSize: cs.fontSize || "",
2785
+ fontWeight: cs.fontWeight || "",
2786
+ letterSpacing: cs.letterSpacing || "",
2787
+ textTransform: cs.textTransform || ""
2788
+ };
1974
2789
  }
1975
2790
  var mounted = /* @__PURE__ */ new Map();
1976
2791
  function findTemplateSection(id) {
@@ -1986,6 +2801,19 @@ function findPlacementAnchor(id, exclude) {
1986
2801
  for (const el of document.querySelectorAll(`[data-ohw-section="${CSS.escape(id)}"]`)) {
1987
2802
  if (el === exclude) continue;
1988
2803
  if (el.parentElement?.closest("[data-ohw-section]")) continue;
2804
+ if (isChromeSection2(el)) return null;
2805
+ return el;
2806
+ }
2807
+ return null;
2808
+ }
2809
+ function findFooterSection() {
2810
+ const byId = findTemplateSection("footer");
2811
+ if (byId) return byId;
2812
+ for (const el of Array.from(
2813
+ document.querySelectorAll("footer[data-ohw-section]")
2814
+ ).reverse()) {
2815
+ if (el.hasAttribute(CONTAINER_ATTR)) continue;
2816
+ if (el.parentElement?.closest("[data-ohw-section]")) continue;
1989
2817
  return el;
1990
2818
  }
1991
2819
  return null;
@@ -2012,7 +2840,7 @@ function placeContainer(container, entry) {
2012
2840
  return;
2013
2841
  }
2014
2842
  }
2015
- const footer = findTemplateSection("footer");
2843
+ const footer = findFooterSection();
2016
2844
  if (footer) {
2017
2845
  footer.insertAdjacentElement("beforebegin", container);
2018
2846
  } else {
@@ -2021,18 +2849,18 @@ function placeContainer(container, entry) {
2021
2849
  }
2022
2850
  function syncRemovedSections(state) {
2023
2851
  const removed = new Set(state.removed ?? []);
2024
- for (const el of document.querySelectorAll(`[${REMOVED_ATTR}]`)) {
2852
+ for (const el of document.querySelectorAll(`[${REMOVED_ATTR2}]`)) {
2025
2853
  const id = el.getAttribute("data-ohw-section") ?? "";
2026
2854
  if (!removed.has(id)) {
2027
2855
  el.style.removeProperty("display");
2028
- el.removeAttribute(REMOVED_ATTR);
2856
+ el.removeAttribute(REMOVED_ATTR2);
2029
2857
  }
2030
2858
  }
2031
2859
  for (const id of removed) {
2032
2860
  const section = findTemplateSection(id);
2033
2861
  if (section && !section.hasAttribute(REPLACED_ATTR)) {
2034
2862
  section.style.display = "none";
2035
- section.setAttribute(REMOVED_ATTR, "");
2863
+ section.setAttribute(REMOVED_ATTR2, "");
2036
2864
  }
2037
2865
  }
2038
2866
  }
@@ -2047,9 +2875,9 @@ function syncTemplateHidden(state, pageHasSections) {
2047
2875
  if (!hide) return;
2048
2876
  for (const el of Array.from(document.querySelectorAll("[data-ohw-section]"))) {
2049
2877
  if (el.hasAttribute(CONTAINER_ATTR)) continue;
2050
- if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) continue;
2878
+ if (isChromeSection2(el)) continue;
2051
2879
  if (el.parentElement?.closest("[data-ohw-section]")) continue;
2052
- if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR)) continue;
2880
+ if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR2)) continue;
2053
2881
  el.style.display = "none";
2054
2882
  el.setAttribute(TEMPLATE_HIDDEN_ATTR, "");
2055
2883
  }
@@ -2073,18 +2901,23 @@ function syncReplacedOriginals(state) {
2073
2901
  }
2074
2902
  }
2075
2903
  var sectionOrderIndex = /* @__PURE__ */ new Map();
2904
+ var removedSectionIds = /* @__PURE__ */ new Set();
2076
2905
  function setAiSectionOrder(raw, currentPath) {
2077
2906
  const next = /* @__PURE__ */ new Map();
2907
+ const removed = /* @__PURE__ */ new Set();
2078
2908
  if (raw) {
2079
2909
  try {
2080
2910
  const entries = JSON.parse(raw);
2081
2911
  for (const entry of entries) {
2082
- if (!entry.pagePath || entry.pagePath === currentPath) next.set(entry.instanceId, entry.order);
2912
+ if (entry.pagePath && entry.pagePath !== currentPath) continue;
2913
+ next.set(entry.instanceId, entry.order);
2914
+ if (entry.removed) removed.add(entry.instanceId);
2083
2915
  }
2084
2916
  } catch {
2085
2917
  }
2086
2918
  }
2087
2919
  sectionOrderIndex = next;
2920
+ removedSectionIds = removed;
2088
2921
  }
2089
2922
  function applyExplicitOrder(entries) {
2090
2923
  if (sectionOrderIndex.size === 0) return entries;
@@ -2120,11 +2953,23 @@ function orderByChain(sections) {
2120
2953
  for (const root of roots) visit(root);
2121
2954
  return out.length === sections.length ? out : sections;
2122
2955
  }
2956
+ function syncSoftRemovedGenerated() {
2957
+ for (const [id, section] of mounted) {
2958
+ const el = section.container;
2959
+ if (removedSectionIds.has(id)) {
2960
+ el.style.display = "none";
2961
+ el.setAttribute(REMOVED_ATTR, "");
2962
+ } else if (el.hasAttribute(REMOVED_ATTR)) {
2963
+ el.style.removeProperty("display");
2964
+ el.removeAttribute(REMOVED_ATTR);
2965
+ }
2966
+ }
2967
+ }
2123
2968
  function applyAiSectionsToDom(state, options) {
2124
2969
  if (typeof document === "undefined") return;
2125
2970
  const brandOverride = deriveBrandOverride();
2126
2971
  const templateBrand = deriveTemplateBrand();
2127
- const templateButtonRadius = deriveTemplateButtonRadius();
2972
+ const templateButtonStyle = deriveTemplateButtonStyle();
2128
2973
  const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
2129
2974
  const pagePath = window.location.pathname;
2130
2975
  const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
@@ -2164,7 +3009,7 @@ function applyAiSectionsToDom(state, options) {
2164
3009
  {
2165
3010
  tree: entry.tree,
2166
3011
  brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
2167
- buttonRadius: templateButtonRadius,
3012
+ buttonStyle: templateButtonStyle,
2168
3013
  resolveMedia,
2169
3014
  editKeyPrefix: `ai.${entry.id}`
2170
3015
  }
@@ -2187,6 +3032,7 @@ function applyAiSectionsToDom(state, options) {
2187
3032
  syncReplacedOriginals(state);
2188
3033
  syncRemovedSections(state);
2189
3034
  syncTemplateHidden(state, pageSections.length > 0);
3035
+ syncSoftRemovedGenerated();
2190
3036
  }
2191
3037
 
2192
3038
  // src/useLinkHrefGuardian.ts
@@ -6378,12 +7224,12 @@ var cva = (base, config) => (props) => {
6378
7224
  import { Toggle as TogglePrimitive } from "radix-ui";
6379
7225
  import { jsx as jsx5 } from "react/jsx-runtime";
6380
7226
  var toggleVariants = cva(
6381
- "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
7227
+ "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=on]:bg-bridge-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
6382
7228
  {
6383
7229
  variants: {
6384
7230
  variant: {
6385
7231
  default: "bg-transparent border-0",
6386
- outline: "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground"
7232
+ outline: "border border-input bg-transparent shadow-xs hover:bg-bridge-accent hover:text-accent-foreground"
6387
7233
  },
6388
7234
  size: {
6389
7235
  default: "px-2 py-1",
@@ -6476,10 +7322,10 @@ var DragHandle = React4.forwardRef(
6476
7322
  type,
6477
7323
  "data-slot": "drag-handle",
6478
7324
  className: cn(
6479
- "inline-flex h-7 w-4 shrink-0 items-center justify-center rounded-md transition-all duration-200 max-h-[30px]",
7325
+ "inline-flex h-7 w-4 shrink-0 items-center justify-center rounded-md transition-all duration-200 max-h-7.5",
6480
7326
  "bg-white border border-transparent text-stone-500 shadow-md cursor-grab",
6481
7327
  "enabled:hover:border enabled:hover:border-stone-200 enabled:hover:text-stone-950",
6482
- "enabled:active:border enabled:active:border-primary enabled:active:bg-primary-50 enabled:active:text-stone-950 enabled:active:shadow enabled:active:cursor-grabbing",
7328
+ "enabled:active:border enabled:active:border-bridge-primary enabled:active:bg-primary-50 enabled:active:text-stone-950 enabled:active:shadow enabled:active:cursor-grabbing",
6483
7329
  "disabled:cursor-not-allowed disabled:opacity-40 disabled:text-stone-950 disabled:pointer-events-none",
6484
7330
  className
6485
7331
  ),
@@ -6501,7 +7347,7 @@ var CustomToolbar = React5.forwardRef(({ className, onMouseDown, ...props }, ref
6501
7347
  "data-ohw-toolbar": "",
6502
7348
  className: cn(
6503
7349
  // Figma: bg background, radius 8, gap-1, p-0.5, shadow-md — no border
6504
- "inline-flex h-8 items-center gap-1 rounded-[var(--radius,0.5rem)] bg-background p-0.5 font-sans whitespace-nowrap shadow-md",
7350
+ "inline-flex h-8 items-center gap-1 rounded-(--radius,0.5rem) bg-background p-0.5 font-sans whitespace-nowrap shadow-md",
6505
7351
  className
6506
7352
  ),
6507
7353
  onMouseDown: (e) => {
@@ -6530,7 +7376,7 @@ var CustomToolbarButton = React5.forwardRef(
6530
7376
  type,
6531
7377
  className: cn(
6532
7378
  "inline-flex size-7 shrink-0 items-center justify-center rounded-[calc(var(--radius,0.5rem)-2px)] text-foreground transition-colors",
6533
- active ? "bg-primary text-primary-foreground" : "bg-transparent hover:bg-muted disabled:cursor-not-allowed disabled:text-muted-foreground disabled:opacity-60",
7379
+ active ? "bg-bridge-primary text-primary-foreground" : "bg-transparent hover:bg-muted disabled:cursor-not-allowed disabled:text-muted-foreground disabled:opacity-60",
6534
7380
  className
6535
7381
  ),
6536
7382
  ...props
@@ -6735,9 +7581,7 @@ function ItemActionToolbar({
6735
7581
  ToggleGroup,
6736
7582
  {
6737
7583
  value: dropdownOpen ? "open" : "closed",
6738
- onValueChange: (value) => {
6739
- if (value !== "open" && value !== "closed") return;
6740
- onDropdownOpenChange?.(value === "open");
7584
+ onValueChange: () => {
6741
7585
  },
6742
7586
  className: "h-7 gap-0.5 rounded-[calc(var(--radius,0.5rem)-2px)] bg-muted p-0.5",
6743
7587
  onMouseDown: (e) => {
@@ -6752,6 +7596,11 @@ function ItemActionToolbar({
6752
7596
  size: "sm",
6753
7597
  "aria-label": "Closed",
6754
7598
  className: "h-6 min-w-0 px-2 text-xs font-medium",
7599
+ onMouseDown: (e) => {
7600
+ e.preventDefault();
7601
+ e.stopPropagation();
7602
+ if (dropdownOpen !== false) onDropdownOpenChange?.(false);
7603
+ },
6755
7604
  children: "Closed"
6756
7605
  }
6757
7606
  ),
@@ -6762,6 +7611,11 @@ function ItemActionToolbar({
6762
7611
  size: "sm",
6763
7612
  "aria-label": "Open",
6764
7613
  className: "h-6 min-w-0 px-2 text-xs font-medium",
7614
+ onMouseDown: (e) => {
7615
+ e.preventDefault();
7616
+ e.stopPropagation();
7617
+ if (dropdownOpen !== true) onDropdownOpenChange?.(true);
7618
+ },
6765
7619
  children: "Open"
6766
7620
  }
6767
7621
  )
@@ -7724,13 +8578,13 @@ function FormFieldToolbar({
7724
8578
  ]
7725
8579
  }
7726
8580
  ) }),
7727
- /* @__PURE__ */ jsx13(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-[190px] p-1", children: FIELD_TYPES.map((entry) => {
8581
+ /* @__PURE__ */ jsx13(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-47.5 p-1", children: FIELD_TYPES.map((entry) => {
7728
8582
  const Icon = TYPE_ICONS[entry.type];
7729
8583
  return /* @__PURE__ */ jsxs6(
7730
8584
  DropdownMenuItem,
7731
8585
  {
7732
8586
  onSelect: () => onTypeChange(entry.type),
7733
- className: "rounded-md py-2 text-[13px] " + (entry.type === type ? "bg-primary/10" : ""),
8587
+ className: "rounded-md py-2 text-[13px] " + (entry.type === type ? "bg-bridge-primary/10" : ""),
7734
8588
  children: [
7735
8589
  /* @__PURE__ */ jsx13(Icon, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
7736
8590
  entry.label
@@ -7747,7 +8601,7 @@ function FormFieldToolbar({
7747
8601
  type: "button",
7748
8602
  "aria-pressed": required,
7749
8603
  onClick: onRequiredToggle,
7750
- className: "flex h-7 items-center gap-1.5 whitespace-nowrap rounded-md px-2 text-[13px] font-medium transition-colors " + (required ? "bg-primary/10 text-primary" : "text-foreground hover:bg-muted/70"),
8604
+ className: "flex h-7 items-center gap-1.5 whitespace-nowrap rounded-md px-2 text-[13px] font-medium transition-colors " + (required ? "bg-bridge-primary/10 text-bridge-primary" : "text-foreground hover:bg-muted/70"),
7751
8605
  "data-ohw-field-required-toggle": "",
7752
8606
  children: [
7753
8607
  /* @__PURE__ */ jsx13(Asterisk, { size: 14, strokeWidth: 2, "aria-hidden": true }),
@@ -7767,7 +8621,7 @@ function FormFieldToolbar({
7767
8621
  children: /* @__PURE__ */ jsx13(MoreHorizontal2, { size: 15, "aria-hidden": true })
7768
8622
  }
7769
8623
  ) }),
7770
- /* @__PURE__ */ jsxs6(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-[170px] p-1", children: [
8624
+ /* @__PURE__ */ jsxs6(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-42.5 p-1", children: [
7771
8625
  /* @__PURE__ */ jsxs6(DropdownMenuItem, { onSelect: onDuplicate, className: "rounded-md py-2 text-[13px]", children: [
7772
8626
  /* @__PURE__ */ jsx13(Copy2, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
7773
8627
  "Duplicate"
@@ -7787,7 +8641,7 @@ function FieldTypePicker({ onPick }) {
7787
8641
  "div",
7788
8642
  {
7789
8643
  "data-ohw-field-type-picker": "",
7790
- className: "pointer-events-auto grid w-[420px] grid-cols-3 gap-3 rounded-xl border border-border bg-background p-4 shadow-lg",
8644
+ className: "pointer-events-auto grid w-105 grid-cols-3 gap-3 rounded-xl border border-border bg-background p-4 shadow-lg",
7791
8645
  children: FIELD_TYPES.map((entry) => {
7792
8646
  const Icon = TYPE_ICONS[entry.type];
7793
8647
  return /* @__PURE__ */ jsxs6(
@@ -7795,7 +8649,7 @@ function FieldTypePicker({ onPick }) {
7795
8649
  {
7796
8650
  type: "button",
7797
8651
  onClick: () => onPick(entry.type),
7798
- className: "flex h-[104px] flex-col items-center justify-center gap-3 rounded-xl border border-border text-[15px] font-medium text-foreground transition-colors hover:border-primary hover:bg-primary/5",
8652
+ className: "flex h-26 flex-col items-center justify-center gap-3 rounded-xl border border-border text-[15px] font-medium text-foreground transition-colors hover:border-bridge-primary hover:bg-bridge-primary/5",
7799
8653
  children: [
7800
8654
  /* @__PURE__ */ jsx13(Icon, { size: 26, strokeWidth: 1.5, "aria-hidden": true }),
7801
8655
  entry.label
@@ -7821,7 +8675,7 @@ var buttonVariants = cva(
7821
8675
  {
7822
8676
  variants: {
7823
8677
  variant: {
7824
- default: "bg-primary text-primary-foreground hover:opacity-90",
8678
+ default: "bg-bridge-primary text-primary-foreground hover:opacity-90",
7825
8679
  outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
7826
8680
  ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
7827
8681
  },
@@ -7916,6 +8770,7 @@ function MediaOverlay({
7916
8770
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
7917
8771
  );
7918
8772
  }, [isVideo]);
8773
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
7919
8774
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
7920
8775
  const box = {
7921
8776
  position: "fixed",
@@ -8021,8 +8876,8 @@ function MediaOverlay({
8021
8876
  pointerEvents: hover.hasTextOverlap ? "none" : "auto",
8022
8877
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
8023
8878
  // than hovered. Hover keeps the existing tinted preview.
8024
- boxShadow: selected ? "inset 0 0 0 2px var(--color-primary)" : "inset 0 0 0 1.5px var(--color-primary)",
8025
- background: selected ? "transparent" : "color-mix(in srgb, var(--color-primary) 20%, transparent)"
8879
+ boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
8880
+ background: selected ? "transparent" : "color-mix(in srgb, var(--color-bridge-primary) 20%, transparent)"
8026
8881
  },
8027
8882
  onClick: () => onSelect && !selected ? onSelect(hover.key) : onReplace(hover.key),
8028
8883
  children: [
@@ -8045,17 +8900,17 @@ function MediaOverlay({
8045
8900
  },
8046
8901
  children: [
8047
8902
  isVideo ? /* @__PURE__ */ jsx15(Film, { size: 14 }) : /* @__PURE__ */ jsx15(ImageIcon, { size: 14 }),
8048
- isVideo ? "Replace video" : "Replace image"
8903
+ replaceLabel
8049
8904
  ]
8050
8905
  }
8051
8906
  ),
8052
- replaceMode === "none" ? null : /* @__PURE__ */ jsxs7(
8907
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ jsxs7(
8053
8908
  Button,
8054
8909
  {
8055
8910
  "data-ohw-media-overlay": "",
8056
8911
  variant: "outline",
8057
8912
  size: "sm",
8058
- "aria-label": isVideo ? "Replace video" : "Replace image",
8913
+ "aria-label": replaceLabel,
8059
8914
  className: "gap-1.5 cursor-pointer hover:bg-background",
8060
8915
  style: {
8061
8916
  ...OVERLAY_BUTTON_STYLE,
@@ -8078,7 +8933,7 @@ function MediaOverlay({
8078
8933
  },
8079
8934
  children: [
8080
8935
  isVideo ? /* @__PURE__ */ jsx15(Film, { size: 14 }) : /* @__PURE__ */ jsx15(ImageIcon, { size: 14 }),
8081
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
8936
+ replaceMode === "full" ? replaceLabel : null
8082
8937
  ]
8083
8938
  }
8084
8939
  )
@@ -8113,253 +8968,41 @@ function CarouselOverlay({
8113
8968
  top: rect.top,
8114
8969
  left: rect.left,
8115
8970
  width: rect.width,
8116
- height: rect.height,
8117
- zIndex: 2147483646,
8118
- pointerEvents: "auto",
8119
- boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
8120
- background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
8121
- },
8122
- onClick: () => onEdit(hover.key),
8123
- children: /* @__PURE__ */ jsxs8(
8124
- Button,
8125
- {
8126
- "data-ohw-carousel-overlay": "",
8127
- variant: "outline",
8128
- size: "sm",
8129
- className: "cursor-pointer gap-1.5 hover:bg-background",
8130
- style: OVERLAY_BUTTON_STYLE2,
8131
- onMouseDown: (e) => e.preventDefault(),
8132
- onClick: (e) => {
8133
- e.stopPropagation();
8134
- onEdit(hover.key);
8135
- },
8136
- children: [
8137
- /* @__PURE__ */ jsx16(GalleryHorizontal, { size: 14 }),
8138
- "Edit gallery"
8139
- ]
8140
- }
8141
- )
8142
- }
8143
- );
8144
- }
8145
-
8146
- // src/ui/ai-section/AiSectionOverlay.tsx
8147
- import { useCallback as useCallback2, useEffect as useEffect4, useRef as useRef4, useState as useState5 } from "react";
8148
- import { Check, X } from "lucide-react";
8149
-
8150
- // src/lib/sections.ts
8151
- var LINK_PICKER_EXCLUDED_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
8152
- function isChromeSection(el) {
8153
- return el.matches("header, nav, footer, aside");
8154
- }
8155
- function titleCaseSectionId(id) {
8156
- return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
8157
- }
8158
- function parseSectionsFromRoot(root) {
8159
- const seen = /* @__PURE__ */ new Set();
8160
- const sections = [];
8161
- for (const el of root.querySelectorAll("[data-ohw-section]")) {
8162
- const id = el.getAttribute("data-ohw-section") ?? "";
8163
- if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
8164
- if (el.parentElement?.closest("[data-ohw-section]")) continue;
8165
- if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
8166
- continue;
8167
- seen.add(id);
8168
- const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
8169
- sections.push({ id, label });
8170
- }
8171
- return sections;
8172
- }
8173
- function collectSectionsFromDom() {
8174
- if (typeof document === "undefined") return [];
8175
- return parseSectionsFromRoot(document);
8176
- }
8177
- function parseSectionsFromHtml(html) {
8178
- const doc = new DOMParser().parseFromString(html, "text/html");
8179
- return parseSectionsFromRoot(doc);
8180
- }
8181
-
8182
- // src/lib/section-instances.ts
8183
- var SECTION_ORDER_KEY = "__ohw_section_order";
8184
- var REMOVED_ATTR2 = "data-ohw-section-removed";
8185
- function isRemovedSection(el) {
8186
- return el.hasAttribute(REMOVED_ATTR2);
8187
- }
8188
- function topLevelSections() {
8189
- return Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
8190
- (el) => !el.parentElement?.closest("[data-ohw-section]") && !isChromeSection(el) && !isRemovedSection(el)
8191
- );
8192
- }
8193
- function instanceIdOf(el) {
8194
- return el.dataset.ohwInstance ?? el.dataset.ohwSection ?? "";
8195
- }
8196
- function findByInstanceId(instanceId) {
8197
- const escapedId = CSS.escape(instanceId);
8198
- return document.querySelector(`[data-ohw-instance="${escapedId}"]`) ?? document.querySelector(`[data-ohw-section="${escapedId}"]:not([data-ohw-instance])`);
8199
- }
8200
- function planSectionMove(instanceId, targetIndex, currentPath) {
8201
- const sections = topLevelSections();
8202
- const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
8203
- if (index === -1) return null;
8204
- const dragged = sections[index];
8205
- const others = sections.filter((_, i) => i !== index);
8206
- const clamped = Math.max(0, Math.min(targetIndex, others.length));
8207
- const reordered = [...others.slice(0, clamped), dragged, ...others.slice(clamped)];
8208
- return reordered.map((el, order) => ({
8209
- instanceId: instanceIdOf(el),
8210
- type: el.getAttribute("data-ohw-section") ?? "",
8211
- order,
8212
- pagePath: currentPath
8213
- }));
8214
- }
8215
- function moveSectionInstance(instanceId, direction, currentPath) {
8216
- const sections = topLevelSections();
8217
- const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
8218
- if (index === -1) return null;
8219
- const siblingIndex = direction === "up" ? index - 1 : index + 1;
8220
- if (siblingIndex < 0 || siblingIndex >= sections.length) return null;
8221
- const entries = planSectionMove(instanceId, siblingIndex, currentPath);
8222
- if (!entries) return null;
8223
- applyPersistedOrder(entries);
8224
- return entries;
8225
- }
8226
- function syncRemovedFlags(entries) {
8227
- const removedIds = new Set(entries.filter((e) => e.removed).map((e) => e.instanceId));
8228
- document.querySelectorAll(`[${REMOVED_ATTR2}]`).forEach((el) => {
8229
- if (!removedIds.has(instanceIdOf(el))) {
8230
- el.style.removeProperty("display");
8231
- el.removeAttribute(REMOVED_ATTR2);
8232
- }
8233
- });
8234
- for (const id of removedIds) {
8235
- const el = findByInstanceId(id);
8236
- if (el) {
8237
- el.style.display = "none";
8238
- el.setAttribute(REMOVED_ATTR2, "");
8239
- }
8240
- }
8241
- }
8242
- function applyPersistedOrder(entries) {
8243
- syncRemovedFlags(entries);
8244
- if (entries.length === 0) return;
8245
- const sections = topLevelSections();
8246
- if (sections.length === 0) return;
8247
- const orderIndex = new Map(entries.map((e) => [e.instanceId, e.order]));
8248
- const ordered = [...sections].sort((a, b) => {
8249
- const aOrder = orderIndex.get(instanceIdOf(a));
8250
- const bOrder = orderIndex.get(instanceIdOf(b));
8251
- if (aOrder === void 0 && bOrder === void 0) return 0;
8252
- if (aOrder === void 0) return 1;
8253
- if (bOrder === void 0) return -1;
8254
- return aOrder - bOrder;
8255
- });
8256
- let prev = null;
8257
- for (const el of ordered) {
8258
- if (prev) prev.after(el);
8259
- prev = el;
8260
- }
8261
- }
8262
- function setSectionRemoved(instanceId, currentPath, existingEntries, removed) {
8263
- if (!findByInstanceId(instanceId)) return null;
8264
- const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
8265
- const allSections = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
8266
- (el) => !el.parentElement?.closest("[data-ohw-section]") && !isChromeSection(el)
8267
- );
8268
- allSections.forEach((el, order) => {
8269
- const id = instanceIdOf(el);
8270
- if (!byId.has(id)) {
8271
- byId.set(id, { instanceId: id, type: el.getAttribute("data-ohw-section") ?? "", order, pagePath: currentPath });
8272
- }
8273
- });
8274
- const target = byId.get(instanceId);
8275
- if (!target) return null;
8276
- byId.set(instanceId, { ...target, removed });
8277
- const entries = Array.from(byId.values());
8278
- applyPersistedOrder(entries);
8279
- return entries;
8280
- }
8281
- function deleteSectionInstance(instanceId, currentPath, existingEntries) {
8282
- return setSectionRemoved(instanceId, currentPath, existingEntries, true);
8283
- }
8284
- function restoreSectionInstance(instanceId, currentPath, existingEntries) {
8285
- return setSectionRemoved(instanceId, currentPath, existingEntries, false);
8286
- }
8287
- function duplicateSectionInstance(instanceId, newId, currentPath, existingEntries) {
8288
- const original = findByInstanceId(instanceId);
8289
- if (!original) return null;
8290
- const clone = original.cloneNode(true);
8291
- clone.setAttribute("data-ohw-instance", newId);
8292
- const keyRekeys = rekeySectionSubtree(clone, newId);
8293
- original.insertAdjacentElement("afterend", clone);
8294
- const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
8295
- const entries = topLevelSections().map((el, order) => {
8296
- const id = instanceIdOf(el);
8297
- return {
8298
- instanceId: id,
8299
- type: el.getAttribute("data-ohw-section") ?? "",
8300
- order,
8301
- pagePath: currentPath,
8302
- ...byId.get(id)?.removed ? { removed: true } : {}
8303
- };
8304
- });
8305
- applyPersistedOrder(entries);
8306
- return { entries, keyRekeys };
8307
- }
8308
- function newInstanceId() {
8309
- return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `instance-${Date.now()}-${Math.random().toString(36).slice(2)}`;
8310
- }
8311
- function getPageSectionOrderEntries(raw, currentPath) {
8312
- if (!raw) return [];
8313
- try {
8314
- const entries = JSON.parse(raw);
8315
- return entries.filter((e) => !e.pagePath || e.pagePath === currentPath);
8316
- } catch {
8317
- return [];
8318
- }
8319
- }
8320
- function rekeySectionSubtree(root, instanceId) {
8321
- const suffix = `::${instanceId}`;
8322
- const pairs = [];
8323
- const rekey = (el, attr) => {
8324
- const current = el.getAttribute(attr);
8325
- if (!current) return;
8326
- const base = current.includes("::") ? current.slice(0, current.indexOf("::")) : current;
8327
- const next = `${base}${suffix}`;
8328
- el.setAttribute(attr, next);
8329
- pairs.push({ from: current, to: next });
8330
- };
8331
- if (root.hasAttribute("data-ohw-key")) rekey(root, "data-ohw-key");
8332
- if (root.hasAttribute("data-ohw-href-key")) rekey(root, "data-ohw-href-key");
8333
- root.querySelectorAll("[data-ohw-key]").forEach((el) => rekey(el, "data-ohw-key"));
8334
- root.querySelectorAll("[data-ohw-href-key]").forEach((el) => rekey(el, "data-ohw-href-key"));
8335
- return pairs;
8336
- }
8337
- function initSectionInstancesFromContent(content, currentPath) {
8338
- document.querySelectorAll("[data-ohw-section]:not([data-ohw-instance])").forEach((el) => {
8339
- el.setAttribute("data-ohw-instance", el.getAttribute("data-ohw-section") ?? "");
8340
- });
8341
- const entries = getPageSectionOrderEntries(content[SECTION_ORDER_KEY], currentPath);
8342
- for (const entry of entries) {
8343
- if (entry.instanceId === entry.type) continue;
8344
- if (document.querySelector(`[data-ohw-instance="${CSS.escape(entry.instanceId)}"]`)) continue;
8345
- const original = document.querySelector(
8346
- `[data-ohw-section="${CSS.escape(entry.type)}"][data-ohw-instance="${CSS.escape(entry.type)}"]`
8347
- );
8348
- if (!original) continue;
8349
- const clone = original.cloneNode(true);
8350
- clone.setAttribute("data-ohw-instance", entry.instanceId);
8351
- rekeySectionSubtree(clone, entry.instanceId);
8352
- original.insertAdjacentElement("afterend", clone);
8353
- }
8354
- applyPersistedOrder(entries);
8971
+ height: rect.height,
8972
+ zIndex: 2147483646,
8973
+ pointerEvents: "auto",
8974
+ boxShadow: "inset 0 0 0 1.5px var(--color-bridge-primary)",
8975
+ background: "color-mix(in srgb, var(--color-bridge-primary) 20%, transparent)"
8976
+ },
8977
+ onClick: () => onEdit(hover.key),
8978
+ children: /* @__PURE__ */ jsxs8(
8979
+ Button,
8980
+ {
8981
+ "data-ohw-carousel-overlay": "",
8982
+ variant: "outline",
8983
+ size: "sm",
8984
+ className: "cursor-pointer gap-1.5 hover:bg-background",
8985
+ style: OVERLAY_BUTTON_STYLE2,
8986
+ onMouseDown: (e) => e.preventDefault(),
8987
+ onClick: (e) => {
8988
+ e.stopPropagation();
8989
+ onEdit(hover.key);
8990
+ },
8991
+ children: [
8992
+ /* @__PURE__ */ jsx16(GalleryHorizontal, { size: 14 }),
8993
+ "Edit gallery"
8994
+ ]
8995
+ }
8996
+ )
8997
+ }
8998
+ );
8355
8999
  }
8356
9000
 
8357
9001
  // src/ui/ai-section/AiSectionOverlay.tsx
9002
+ import { useCallback as useCallback2, useEffect as useEffect4, useRef as useRef4, useState as useState5 } from "react";
9003
+ import { Check, X } from "lucide-react";
8358
9004
  import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
8359
- function findSectionElement(instanceId) {
8360
- const escaped = CSS.escape(instanceId);
8361
- return document.querySelector(`[data-ohw-instance="${escaped}"]`) ?? document.querySelector(`[data-ohw-section="${escaped}"]:not([data-ohw-instance])`);
8362
- }
9005
+ var findSectionElement = findByInstanceId;
8363
9006
  function readRect(instanceId) {
8364
9007
  const el = findSectionElement(instanceId);
8365
9008
  if (!el) return null;
@@ -8399,7 +9042,7 @@ function useLiveSectionRect(sectionId) {
8399
9042
  }
8400
9043
  function computeSectionBoundaryFlags(instanceId) {
8401
9044
  const topLevel = topLevelSections();
8402
- const index = topLevel.findIndex((el) => (el.dataset.ohwInstance ?? el.dataset.ohwSection) === instanceId);
9045
+ const index = topLevel.findIndex((el) => instanceIdOf(el) === instanceId);
8403
9046
  if (index === -1) return { isFirst: true, isLast: true };
8404
9047
  return { isFirst: index === 0, isLast: index === topLevel.length - 1 };
8405
9048
  }
@@ -8483,18 +9126,20 @@ function AiSectionOverlay({
8483
9126
  selectedIdRef.current = selectedId;
8484
9127
  const report = useCallback2(
8485
9128
  (el) => {
9129
+ const labelSrc = el ? sectionElementOf(el) : null;
8486
9130
  postToParent2({
8487
9131
  type: "ow:section-selected",
8488
- sectionId: el ? el.dataset.ohwInstance ?? el.dataset.ohwSection ?? null : null,
8489
- sectionLabel: el ? el.dataset.ohwSectionLabel ?? titleCaseSectionId(el.dataset.ohwSection ?? "") : null
9132
+ sectionId: el ? instanceIdOf(el) || null : null,
9133
+ sectionLabel: labelSrc ? labelSrc.dataset.ohwSectionLabel ?? titleCaseSectionId(labelSrc.dataset.ohwSection ?? "") : null
8490
9134
  });
8491
9135
  },
8492
9136
  [postToParent2]
8493
9137
  );
8494
9138
  const selectFromElement = useCallback2(
8495
9139
  (el, options) => {
8496
- const sectionEl = el?.closest("[data-ohw-section]") ?? null;
8497
- const id = sectionEl ? sectionEl.dataset.ohwInstance ?? sectionEl.dataset.ohwSection ?? null : null;
9140
+ const inner = el?.closest("[data-ohw-section]") ?? null;
9141
+ const sectionEl = inner ? movableUnit(inner) : null;
9142
+ const id = sectionEl ? instanceIdOf(sectionEl) || null : null;
8498
9143
  if (id === selectedIdRef.current) return;
8499
9144
  setSelectedId(id);
8500
9145
  if (options?.report !== false) report(sectionEl);
@@ -8560,7 +9205,8 @@ function AiSectionOverlay({
8560
9205
  return;
8561
9206
  }
8562
9207
  const sec = t.closest("[data-ohw-section]");
8563
- setHoveredId(sec ? sec.dataset.ohwInstance ?? sec.dataset.ohwSection ?? null : null);
9208
+ const unit = sec ? movableUnit(sec) : null;
9209
+ setHoveredId(unit ? instanceIdOf(unit) || null : null);
8564
9210
  };
8565
9211
  const onLeave = () => setHoveredId(null);
8566
9212
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -9164,7 +9810,7 @@ function SectionTreeItem({
9164
9810
  /* @__PURE__ */ jsx20(
9165
9811
  "div",
9166
9812
  {
9167
- className: "mr-[-1px] h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
9813
+ className: "-mr-px h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
9168
9814
  "aria-hidden": true
9169
9815
  }
9170
9816
  ),
@@ -9183,7 +9829,7 @@ function SectionTreeItem({
9183
9829
  className: cn(
9184
9830
  "flex h-9 min-w-0 flex-1 items-center gap-2 rounded-md border border-border bg-background p-3",
9185
9831
  interactive && "cursor-pointer hover:bg-muted/30",
9186
- interactive && selected && "border-primary"
9832
+ interactive && selected && "border-bridge-primary"
9187
9833
  ),
9188
9834
  children: [
9189
9835
  /* @__PURE__ */ jsx20(
@@ -9313,7 +9959,7 @@ function UrlOrPageInput({
9313
9959
  };
9314
9960
  const fieldClassName = cn(
9315
9961
  "data-ohw-link-field flex h-[36px] w-full items-center overflow-hidden rounded-md border bg-background pl-3 pr-3 py-2 outline-none transition-[border-color,box-shadow]",
9316
- urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
9962
+ urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-bridge-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
9317
9963
  );
9318
9964
  return /* @__PURE__ */ jsxs13("div", { className: "flex w-full flex-col gap-2 p-0", children: [
9319
9965
  /* @__PURE__ */ jsx23(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
@@ -10357,6 +11003,32 @@ function getNavbarDesktopContainer() {
10357
11003
  function getNavbarDrawerContainer() {
10358
11004
  return document.querySelector("[data-ohw-nav-drawer]");
10359
11005
  }
11006
+ function isNavbarListContainerVisible(el) {
11007
+ if (el.hasAttribute("hidden")) return false;
11008
+ if (el.getClientRects().length === 0) return false;
11009
+ const style = window.getComputedStyle(el);
11010
+ return style.display !== "none" && style.visibility !== "hidden";
11011
+ }
11012
+ function getActiveNavbarListContainer(draggedEl) {
11013
+ const desktop = getNavbarDesktopContainer();
11014
+ const drawer = getNavbarDrawerContainer();
11015
+ if (draggedEl) {
11016
+ if (drawer?.contains(draggedEl)) return drawer;
11017
+ if (desktop?.contains(draggedEl)) return desktop;
11018
+ }
11019
+ if (desktop && isNavbarListContainerVisible(desktop)) return desktop;
11020
+ if (drawer && isNavbarListContainerVisible(drawer)) return drawer;
11021
+ return desktop ?? drawer;
11022
+ }
11023
+ function listActiveNavbarItems(draggedEl) {
11024
+ const container = getActiveNavbarListContainer(draggedEl);
11025
+ if (container) {
11026
+ return Array.from(container.querySelectorAll("[data-ohw-href-key]")).filter(
11027
+ isNavbarLinkItem
11028
+ );
11029
+ }
11030
+ return listNavbarItems();
11031
+ }
10360
11032
  function listNavbarItems() {
10361
11033
  const desktop = getNavbarDesktopContainer();
10362
11034
  if (desktop) {
@@ -11469,20 +12141,23 @@ function isEmptyLabelValue(value) {
11469
12141
  function applyStoredValues(item, content) {
11470
12142
  const hrefKey = socialHrefKey(item);
11471
12143
  const iconKey = socialIconKey(item);
12144
+ const baseKey = hrefKey?.replace(/-href$/, "");
11472
12145
  if (hrefKey && content[hrefKey] !== void 0) item.setAttribute("href", content[hrefKey]);
11473
- if (iconKey) {
12146
+ const glyphKey = iconKey ?? baseKey ?? null;
12147
+ const glyphStored = glyphKey ? content[glyphKey]?.trim() : void 0;
12148
+ if (glyphStored) {
12149
+ ensureIconSlot(item);
11474
12150
  const glyph = item.querySelector(ICON_SELECTOR);
11475
- if (glyph && content[iconKey]) {
11476
- applyIconMarkup(glyph, content[iconKey]);
12151
+ if (glyph) {
12152
+ applyIconMarkup(glyph, glyphStored);
11477
12153
  glyph.removeAttribute(SOCIALS_ICON_PLACEHOLDER_ATTR);
11478
12154
  }
11479
- const label = item.querySelector(`[${SOCIALS_LABEL_ATTR}]`);
11480
- label?.setAttribute("data-ohw-key", socialLabelKey(iconKey));
11481
- const stored = content[socialLabelKey(iconKey)];
11482
- if (label && stored !== void 0) {
11483
- const words = isEmptyLabelValue(stored) ? "" : stored;
11484
- if (label.innerHTML !== words) label.innerHTML = words;
11485
- }
12155
+ }
12156
+ const label = socialLabelElement(item);
12157
+ const labelKey = label?.getAttribute("data-ohw-key");
12158
+ if (label && labelKey && content[labelKey] !== void 0) {
12159
+ const words = isEmptyLabelValue(content[labelKey]) ? "" : content[labelKey];
12160
+ if (label.innerHTML !== words) label.innerHTML = words;
11486
12161
  }
11487
12162
  }
11488
12163
  function socialPlatformKey(iconKey) {
@@ -11571,6 +12246,9 @@ function insertSocialItem(row, after, content = {}, { placeholder = true, keys }
11571
12246
  if (placeholder) {
11572
12247
  const label = socialLabelElement(item);
11573
12248
  if (label) label.textContent = PLACEHOLDER_SOCIAL_LABEL;
12249
+ } else if (keys) {
12250
+ const label = socialLabelElement(item);
12251
+ if (label) label.textContent = "";
11574
12252
  }
11575
12253
  applySocialsDisplayToRow(row, display);
11576
12254
  return { item, hrefKey, iconKey, order: getSocialsOrderFromDom(row.ownerDocument) };
@@ -13071,6 +13749,7 @@ function readLogoSizeState(content, placement) {
13071
13749
  function getLogoElement(el) {
13072
13750
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
13073
13751
  if (marked) return marked;
13752
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
13074
13753
  const root = el.closest("nav, [data-ohw-nav-root], footer");
13075
13754
  if (!root) return null;
13076
13755
  const anchor = el.closest("a");
@@ -13364,7 +14043,7 @@ function DisplaySwitch({
13364
14043
  onClick: () => onChange(!checked),
13365
14044
  className: cn(
13366
14045
  "relative h-5 w-9 shrink-0 rounded-full transition-colors",
13367
- checked ? "bg-primary" : "bg-primary-50",
14046
+ checked ? "bg-bridge-primary" : "bg-primary-50",
13368
14047
  disabled ? "cursor-default opacity-50" : "cursor-pointer"
13369
14048
  ),
13370
14049
  children: /* @__PURE__ */ jsx28(
@@ -13372,7 +14051,7 @@ function DisplaySwitch({
13372
14051
  {
13373
14052
  className: cn(
13374
14053
  "absolute top-0.5 h-4 w-4 rounded-full bg-white transition-all",
13375
- checked ? "left-[1.125rem]" : "left-0.5"
14054
+ checked ? "left-4.5" : "left-0.5"
13376
14055
  )
13377
14056
  }
13378
14057
  )
@@ -13433,8 +14112,8 @@ var lockFooterDuringDrag = lockItemDuringDrag;
13433
14112
  var unlockFooterDragInteraction = unlockItemDragInteraction;
13434
14113
 
13435
14114
  // src/lib/nav-dnd.ts
13436
- function listReorderableNavItems() {
13437
- return listNavbarItems().filter((el) => {
14115
+ function listReorderableNavItems(draggedEl) {
14116
+ return listActiveNavbarItems(draggedEl).filter((el) => {
13438
14117
  const key = el.getAttribute("data-ohw-href-key");
13439
14118
  return isNavbarHrefKey(key) && !isNestedNavChild(el);
13440
14119
  });
@@ -13449,8 +14128,8 @@ function resolveParentNavHrefKey(child) {
13449
14128
  const key = trigger?.getAttribute("data-ohw-href-key");
13450
14129
  return key && isNavbarHrefKey(key) ? key : null;
13451
14130
  }
13452
- function listNavChildren(parentHrefKey) {
13453
- const items = listNavbarItems();
14131
+ function listNavChildren(parentHrefKey, draggedEl) {
14132
+ const items = listActiveNavbarItems(draggedEl);
13454
14133
  const parent = items.find((el) => el.getAttribute("data-ohw-href-key") === parentHrefKey);
13455
14134
  if (!parent) return [];
13456
14135
  const group = parent.closest("[data-ohw-nav-group]");
@@ -13460,20 +14139,83 @@ function listNavChildren(parentHrefKey) {
13460
14139
  (el) => isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))
13461
14140
  );
13462
14141
  }
13463
- function activeNavListContainer() {
13464
- const desktop = getNavbarDesktopContainer();
13465
- if (desktop && desktop.getClientRects().length > 0) {
13466
- const style = window.getComputedStyle(desktop);
13467
- if (style.display !== "none" && style.visibility !== "hidden") return desktop;
14142
+ function isVerticalNavLayout(items, draggedEl) {
14143
+ const visible = items.filter((el) => el.getClientRects().length > 0);
14144
+ if (visible.length >= 2) {
14145
+ const a = visible[0].getBoundingClientRect();
14146
+ const b = visible[1].getBoundingClientRect();
14147
+ const verticalGap = b.top - a.bottom;
14148
+ const horizontalGap = b.left - a.right;
14149
+ return verticalGap > horizontalGap;
14150
+ }
14151
+ const container = getActiveNavbarListContainer(draggedEl);
14152
+ if (!container) return false;
14153
+ const style = window.getComputedStyle(container);
14154
+ return style.flexDirection === "column" || style.flexDirection === "column-reverse";
14155
+ }
14156
+ function buildVerticalRootNavDropSlots(items, draggedEl) {
14157
+ const slots = [];
14158
+ const barThickness = 3;
14159
+ if (items.length === 0) {
14160
+ const container = getActiveNavbarListContainer(draggedEl);
14161
+ if (!container) return slots;
14162
+ const rect = container.getBoundingClientRect();
14163
+ slots.push({
14164
+ insertIndex: 0,
14165
+ parentId: null,
14166
+ left: rect.left,
14167
+ top: rect.top + rect.height / 2 - barThickness / 2,
14168
+ width: Math.max(rect.width, 40),
14169
+ height: barThickness,
14170
+ direction: "horizontal"
14171
+ });
14172
+ return slots;
14173
+ }
14174
+ const edgeGap = (() => {
14175
+ if (items.length < 2) return 16;
14176
+ const a = items[0].getBoundingClientRect();
14177
+ const b = items[1].getBoundingClientRect();
14178
+ return Math.max(0, b.top - a.bottom);
14179
+ })();
14180
+ for (let i = 0; i <= items.length; i++) {
14181
+ let top;
14182
+ let width;
14183
+ let left;
14184
+ if (i === 0) {
14185
+ const first = items[0].getBoundingClientRect();
14186
+ top = first.top - edgeGap / 2 - barThickness / 2;
14187
+ left = first.left;
14188
+ width = first.width;
14189
+ } else if (i === items.length) {
14190
+ const last = items[items.length - 1].getBoundingClientRect();
14191
+ const lastGap = items.length >= 2 ? Math.max(0, last.top - items[items.length - 2].getBoundingClientRect().bottom) : edgeGap;
14192
+ top = last.bottom + lastGap / 2 - barThickness / 2;
14193
+ left = last.left;
14194
+ width = last.width;
14195
+ } else {
14196
+ const prev = items[i - 1].getBoundingClientRect();
14197
+ const next = items[i].getBoundingClientRect();
14198
+ top = (prev.bottom + next.top) / 2 - barThickness / 2;
14199
+ left = Math.min(prev.left, next.left);
14200
+ width = Math.max(prev.right, next.right) - left;
14201
+ }
14202
+ slots.push({
14203
+ insertIndex: i,
14204
+ parentId: null,
14205
+ left,
14206
+ top,
14207
+ width: Math.max(width, 40),
14208
+ height: barThickness,
14209
+ direction: "horizontal"
14210
+ });
13468
14211
  }
13469
- return getNavbarDrawerContainer();
14212
+ return slots;
13470
14213
  }
13471
- function buildRootNavDropSlots() {
13472
- const items = listReorderableNavItems();
14214
+ function buildHorizontalRootNavDropSlots(items, draggedEl) {
13473
14215
  const slots = [];
13474
14216
  const barThickness = 3;
13475
14217
  if (items.length === 0) {
13476
- const container = activeNavListContainer();
14218
+ const container = getActiveNavbarListContainer(draggedEl);
13477
14219
  if (!container) return slots;
13478
14220
  const rect = container.getBoundingClientRect();
13479
14221
  slots.push({
@@ -13504,10 +14246,7 @@ function buildRootNavDropSlots() {
13504
14246
  height = first.height;
13505
14247
  } else if (i === items.length) {
13506
14248
  const last = items[items.length - 1].getBoundingClientRect();
13507
- const lastGap = items.length >= 2 ? Math.max(
13508
- 0,
13509
- last.left - items[items.length - 2].getBoundingClientRect().right
13510
- ) : edgeGap;
14249
+ const lastGap = items.length >= 2 ? Math.max(0, last.left - items[items.length - 2].getBoundingClientRect().right) : edgeGap;
13511
14250
  left = last.right + lastGap / 2 - barThickness / 2;
13512
14251
  top = last.top;
13513
14252
  height = last.height;
@@ -13530,8 +14269,15 @@ function buildRootNavDropSlots() {
13530
14269
  }
13531
14270
  return slots;
13532
14271
  }
13533
- function buildChildNavDropSlots(parentHrefKey) {
13534
- const children = listNavChildren(parentHrefKey);
14272
+ function buildRootNavDropSlots(draggedEl) {
14273
+ const items = listReorderableNavItems(draggedEl);
14274
+ if (isVerticalNavLayout(items, draggedEl)) {
14275
+ return buildVerticalRootNavDropSlots(items, draggedEl);
14276
+ }
14277
+ return buildHorizontalRootNavDropSlots(items, draggedEl);
14278
+ }
14279
+ function buildChildNavDropSlots(parentHrefKey, draggedEl) {
14280
+ const children = listNavChildren(parentHrefKey, draggedEl);
13535
14281
  const slots = [];
13536
14282
  const barThickness = 3;
13537
14283
  if (children.length === 0) return slots;
@@ -13552,10 +14298,7 @@ function buildChildNavDropSlots(parentHrefKey) {
13552
14298
  width = first.width;
13553
14299
  } else if (i === children.length) {
13554
14300
  const last = children[children.length - 1].getBoundingClientRect();
13555
- const lastGap = children.length >= 2 ? Math.max(
13556
- 0,
13557
- last.top - children[children.length - 2].getBoundingClientRect().bottom
13558
- ) : edgeGap;
14301
+ const lastGap = children.length >= 2 ? Math.max(0, last.top - children[children.length - 2].getBoundingClientRect().bottom) : edgeGap;
13559
14302
  top = last.bottom + lastGap / 2 - barThickness / 2;
13560
14303
  left = last.left;
13561
14304
  width = last.width;
@@ -13578,18 +14321,19 @@ function buildChildNavDropSlots(parentHrefKey) {
13578
14321
  }
13579
14322
  return slots;
13580
14323
  }
13581
- function buildAllNavDropSlots(draggedHrefKey) {
13582
- const dragged = listNavbarItems().find((el) => el.getAttribute("data-ohw-href-key") === draggedHrefKey);
13583
- if (!dragged) return buildRootNavDropSlots();
14324
+ function buildAllNavDropSlots(draggedHrefKey, draggedEl) {
14325
+ const activeItems = listActiveNavbarItems(draggedEl);
14326
+ 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);
14327
+ if (!dragged) return buildRootNavDropSlots(draggedEl);
13584
14328
  if (isNestedNavChild(dragged)) {
13585
14329
  const parentKey = resolveParentNavHrefKey(dragged);
13586
14330
  if (!parentKey) return [];
13587
- return buildChildNavDropSlots(parentKey);
14331
+ return buildChildNavDropSlots(parentKey, draggedEl);
13588
14332
  }
13589
- return buildRootNavDropSlots();
14333
+ return buildRootNavDropSlots(draggedEl);
13590
14334
  }
13591
- function hitTestNavDropSlot(clientX, clientY, draggedHrefKey) {
13592
- const slots = buildAllNavDropSlots(draggedHrefKey);
14335
+ function hitTestNavDropSlot(clientX, clientY, draggedHrefKey, draggedEl) {
14336
+ const slots = buildAllNavDropSlots(draggedHrefKey, draggedEl);
13593
14337
  let best = null;
13594
14338
  for (const slot of slots) {
13595
14339
  const cx2 = slot.left + slot.width / 2;
@@ -13640,6 +14384,12 @@ function useNavItemDrag({
13640
14384
  setIsItemDragging(false);
13641
14385
  if (keepOpenEl?.isConnected) {
13642
14386
  setNavGroupForceOpen(keepOpenEl, true);
14387
+ requestAnimationFrame(() => {
14388
+ if (keepOpenEl.isConnected) setNavGroupForceOpen(keepOpenEl, true);
14389
+ requestAnimationFrame(() => {
14390
+ if (keepOpenEl.isConnected) setNavGroupForceOpen(keepOpenEl, true);
14391
+ });
14392
+ });
13643
14393
  } else {
13644
14394
  setNavGroupForceOpen(null, false);
13645
14395
  }
@@ -13655,7 +14405,7 @@ function useNavItemDrag({
13655
14405
  }
13656
14406
  session.activeSlot = activeSlot;
13657
14407
  setSiblingHintRects([]);
13658
- const slots = buildAllNavDropSlots(session.hrefKey);
14408
+ const slots = buildAllNavDropSlots(session.hrefKey, session.draggedEl);
13659
14409
  setNavDropSlots(slots);
13660
14410
  const activeIdx = activeSlot ? slots.findIndex(
13661
14411
  (s) => s.parentId === activeSlot.parentId && s.insertIndex === activeSlot.insertIndex
@@ -13691,7 +14441,12 @@ function useNavItemDrag({
13691
14441
  if (session.wasSelected && selectedElRef.current === session.draggedEl) {
13692
14442
  setToolbarRect(rect);
13693
14443
  }
13694
- const initialSlot = hitTestNavDropSlot(session.lastClientX, session.lastClientY, session.hrefKey);
14444
+ const initialSlot = hitTestNavDropSlot(
14445
+ session.lastClientX,
14446
+ session.lastClientY,
14447
+ session.hrefKey,
14448
+ session.draggedEl
14449
+ );
13695
14450
  refreshNavDragVisuals(session, initialSlot, session.lastClientX, session.lastClientY);
13696
14451
  },
13697
14452
  [
@@ -13713,10 +14468,11 @@ function useNavItemDrag({
13713
14468
  }
13714
14469
  const x = typeof clientX === "number" && (clientX !== 0 || clientY !== 0) ? clientX : session.lastClientX;
13715
14470
  const y = typeof clientY === "number" && (clientX !== 0 || clientY !== 0) ? clientY : session.lastClientY;
13716
- const slot = session.activeSlot ?? hitTestNavDropSlot(x, y, session.hrefKey);
14471
+ const slot = session.activeSlot ?? hitTestNavDropSlot(x, y, session.hrefKey, session.draggedEl);
13717
14472
  const planned = slot != null ? planNavItemMove(session.hrefKey, slot.parentId, slot.insertIndex) : null;
13718
14473
  const wasSelected = session.wasSelected;
13719
14474
  const hrefKey = session.hrefKey;
14475
+ const keepDropdownOpen = Boolean(session.draggedEl.closest("[data-ohw-nav-children]"));
13720
14476
  const applySelectionAfterDrop = () => {
13721
14477
  if (!wasSelected) {
13722
14478
  deselectRef.current();
@@ -13750,6 +14506,14 @@ function useNavItemDrag({
13750
14506
  });
13751
14507
  applySelectionAfterDrop();
13752
14508
  clearNavDragVisuals();
14509
+ if (keepDropdownOpen) {
14510
+ requestAnimationFrame(() => {
14511
+ const desktop = document.querySelector("[data-ohw-nav-container]");
14512
+ const drawer = document.querySelector("[data-ohw-nav-drawer]");
14513
+ 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)}"]`);
14514
+ if (link) setNavGroupForceOpen(link, true);
14515
+ });
14516
+ }
13753
14517
  requestAnimationFrame(() => {
13754
14518
  if (editContentRef.current[NAV_ORDER_KEY] === planned.orderJson) {
13755
14519
  applyNavForest(planned.forest);
@@ -13789,7 +14553,7 @@ function useNavItemDrag({
13789
14553
  if (!session) return false;
13790
14554
  e.preventDefault();
13791
14555
  if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
13792
- const slot = hitTestNavDropSlot(e.clientX, e.clientY, session.hrefKey);
14556
+ const slot = hitTestNavDropSlot(e.clientX, e.clientY, session.hrefKey, session.draggedEl);
13793
14557
  refreshNavDragVisualsRef.current(session, slot, e.clientX, e.clientY);
13794
14558
  return true;
13795
14559
  },
@@ -13835,7 +14599,7 @@ function useNavItemDrag({
13835
14599
  clearTextSelection();
13836
14600
  const session = navDragRef.current;
13837
14601
  if (!session) return;
13838
- const slot = hitTestNavDropSlot(e.clientX, e.clientY, session.hrefKey);
14602
+ const slot = hitTestNavDropSlot(e.clientX, e.clientY, session.hrefKey, session.draggedEl);
13839
14603
  refreshNavDragVisualsRef.current(session, slot, e.clientX, e.clientY);
13840
14604
  return;
13841
14605
  }
@@ -13851,7 +14615,8 @@ function useNavItemDrag({
13851
14615
  } catch {
13852
14616
  }
13853
14617
  if (linkPopoverOpenRef.current) setLinkPopover(null);
13854
- if (activeElRef.current) deactivateRef.current();
14618
+ const isNestedDrag = Boolean(pending.el.closest("[data-ohw-nav-children]"));
14619
+ if (activeElRef.current && !isNestedDrag) deactivateRef.current();
13855
14620
  const key = pending.el.getAttribute("data-ohw-href-key");
13856
14621
  if (!key) return;
13857
14622
  beginNavDragRef.current({
@@ -13862,6 +14627,9 @@ function useNavItemDrag({
13862
14627
  lastClientY: e.clientY,
13863
14628
  activeSlot: null
13864
14629
  });
14630
+ if (activeElRef.current && isNestedDrag) {
14631
+ deactivateRef.current();
14632
+ }
13865
14633
  };
13866
14634
  const endPointerDrag = (e) => {
13867
14635
  const pending = navPointerDragRef.current;
@@ -13939,6 +14707,7 @@ function useNavItemDrag({
13939
14707
  );
13940
14708
  return {
13941
14709
  navDragRef,
14710
+ navPointerDragRef,
13942
14711
  navDropSlots,
13943
14712
  activeNavDropIndex,
13944
14713
  startNavLinkDrag,
@@ -14143,15 +14912,17 @@ function useSectionDrag({
14143
14912
  clearSectionDragVisuals();
14144
14913
  return;
14145
14914
  }
14146
- const orderJson = JSON.stringify(entries);
14915
+ const orderJson = JSON.stringify(
14916
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
14917
+ );
14147
14918
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
14148
14919
  setAiSectionOrder(orderJson, window.location.pathname);
14149
14920
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
14150
- applyPersistedOrder(entries);
14921
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
14151
14922
  clearSectionDragVisuals();
14152
14923
  requestAnimationFrame(() => {
14153
14924
  if (editContentRef.current[SECTION_ORDER_KEY] === orderJson) {
14154
- applyPersistedOrder(entries);
14925
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
14155
14926
  }
14156
14927
  requestAnimationFrame(() => {
14157
14928
  window.dispatchEvent(new Event("resize"));
@@ -14184,8 +14955,9 @@ function useSectionDrag({
14184
14955
  const target = e.target;
14185
14956
  if (!(target instanceof HTMLElement)) return;
14186
14957
  if (target.closest(SECTION_DRAG_EXCLUDED_SELECTOR)) return;
14187
- const sectionEl = target.closest("[data-ohw-section]");
14188
- if (!sectionEl || isChromeSection(sectionEl) || sectionEl.dataset.ohwSection === "footer") return;
14958
+ const inner = target.closest("[data-ohw-section]");
14959
+ if (!inner || isChromeSection(inner) || inner.dataset.ohwSection === "footer") return;
14960
+ const sectionEl = movableUnit(inner);
14189
14961
  if (!topLevelSections().includes(sectionEl)) return;
14190
14962
  startSectionPressDrag(sectionEl, e.clientX, e.clientY, e.pointerId);
14191
14963
  };
@@ -14452,6 +15224,9 @@ function collectEditableNodes(extraContent, root = document) {
14452
15224
  if (el.dataset.ohwEditable === "link") {
14453
15225
  return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref3(el) };
14454
15226
  }
15227
+ if (el.dataset.ohwEditable === "map") {
15228
+ return { key: el.dataset.ohwKey ?? "", type: "map", text: el.dataset.ohwMapQuery ?? "" };
15229
+ }
14455
15230
  return {
14456
15231
  key: el.dataset.ohwKey ?? "",
14457
15232
  type: el.dataset.ohwEditable ?? "text",
@@ -14892,7 +15667,7 @@ var badgeVariants = cva(
14892
15667
  {
14893
15668
  variants: {
14894
15669
  variant: {
14895
- default: "border-transparent bg-primary text-primary-foreground",
15670
+ default: "border-transparent bg-bridge-primary text-primary-foreground",
14896
15671
  secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
14897
15672
  destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
14898
15673
  outline: "text-foreground"
@@ -15014,21 +15789,10 @@ function parseSchedulingInsertAfter(insertAfter) {
15014
15789
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
15015
15790
  };
15016
15791
  }
15017
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
15018
- const parsed = parseSchedulingInsertAfter(insertAfter);
15019
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
15020
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
15021
- return { effectiveInsertAfter, insertBefore };
15022
- }
15023
- function getSchedulingMountPoint(insertAfter) {
15024
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
15025
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
15026
- if (!anchorEl && anchor === "scheduling") {
15027
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
15028
- anchorEl = widgets.at(-1) ?? null;
15029
- }
15030
- if (!anchorEl) return null;
15031
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
15792
+ function resolveEntryAnchor(entry) {
15793
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
15794
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
15795
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
15032
15796
  }
15033
15797
  function schedulingMountDepth(insertAfter) {
15034
15798
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -15045,8 +15809,7 @@ function getPageSchedulingEntries(raw) {
15045
15809
  }
15046
15810
  }
15047
15811
  function isSchedulingWidgetMissing(entry) {
15048
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
15049
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
15812
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
15050
15813
  }
15051
15814
  function hasMissingSchedulingWidgets(entries) {
15052
15815
  return entries.some(isSchedulingWidgetMissing);
@@ -15084,18 +15847,18 @@ function initSectionsFromContent(content, removeExisting = false, currentPath =
15084
15847
  } catch {
15085
15848
  }
15086
15849
  }
15087
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
15088
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
15089
- const sectionId = schedulingSectionId(effectiveInsertAfter);
15850
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
15851
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
15852
+ const sectionId = schedulingSectionId(widgetId);
15090
15853
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
15091
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
15092
- if (!mountPoint) return false;
15854
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
15855
+ if (!anchorEl) return false;
15856
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
15093
15857
  const container = document.createElement("div");
15094
15858
  container.dataset.ohwSectionContainer = "scheduling";
15095
- container.dataset.ohwSection = sectionId;
15096
15859
  container.dataset.ohwInstance = sectionId;
15097
- if (insertBefore) {
15098
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
15860
+ if (beforeId) {
15861
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
15099
15862
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
15100
15863
  if (!beforePoint) return false;
15101
15864
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -15106,20 +15869,26 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
15106
15869
  }
15107
15870
  tail.insertAdjacentElement("afterend", container);
15108
15871
  }
15109
- const root = createRoot2(container);
15110
- schedulingRoots.set(container, root);
15111
- flushSync2(() => {
15112
- root.render(
15113
- /* @__PURE__ */ jsx33(
15114
- SchedulingWidget,
15115
- {
15116
- notifyOnConnect,
15117
- initialScheduleId: scheduleId,
15118
- insertAfter: effectiveInsertAfter
15119
- }
15120
- )
15121
- );
15122
- });
15872
+ try {
15873
+ const root = createRoot2(container);
15874
+ schedulingRoots.set(container, root);
15875
+ flushSync2(() => {
15876
+ root.render(
15877
+ /* @__PURE__ */ jsx33(
15878
+ SchedulingWidget,
15879
+ {
15880
+ notifyOnConnect,
15881
+ initialScheduleId: scheduleId,
15882
+ insertAfter: widgetId
15883
+ }
15884
+ )
15885
+ );
15886
+ });
15887
+ } catch (err) {
15888
+ console.error("[ow:scheduling] render threw", err);
15889
+ container.remove();
15890
+ return false;
15891
+ }
15123
15892
  const tracker = getSectionsTracker();
15124
15893
  let sections = [];
15125
15894
  try {
@@ -15127,10 +15896,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
15127
15896
  } catch {
15128
15897
  }
15129
15898
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
15130
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
15899
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
15131
15900
  sections.push({
15132
15901
  type: "scheduling",
15133
- insertAfter: effectiveInsertAfter,
15902
+ insertAfter: widgetId,
15903
+ anchorId,
15904
+ beforeId: beforeId ?? null,
15134
15905
  pagePath: window.location.pathname,
15135
15906
  ...scheduleId ? { scheduleId } : {}
15136
15907
  });
@@ -15144,7 +15915,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
15144
15915
  for (let i = pending.length - 1; i >= 0; i--) {
15145
15916
  const entry = pending[i];
15146
15917
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
15147
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId ?? null)) {
15918
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
15919
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
15148
15920
  pending.splice(i, 1);
15149
15921
  }
15150
15922
  }
@@ -15236,7 +16008,7 @@ var EDITOR_CHROME_SELECTOR = '[data-ohw-toolbar], [data-ohw-form-toolbar], [data
15236
16008
  function isOverEditorChrome(x, y) {
15237
16009
  return document.elementsFromPoint(x, y).some((el) => el instanceof HTMLElement && el.matches(EDITOR_CHROME_SELECTOR));
15238
16010
  }
15239
- var NON_MEDIA_SELECTOR = '[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]):not([data-ohw-editable="icon"]):not([data-ohw-editable="form"])';
16011
+ var NON_MEDIA_SELECTOR = '[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]):not([data-ohw-editable="icon"]):not([data-ohw-editable="form"]):not([data-ohw-editable="map"])';
15240
16012
  function getVideoEl2(el) {
15241
16013
  return el instanceof HTMLVideoElement ? el : el.querySelector("video");
15242
16014
  }
@@ -15292,6 +16064,12 @@ function applyVideoSettingNode(key, val) {
15292
16064
  });
15293
16065
  return true;
15294
16066
  }
16067
+ function applyMapQuery(el, val) {
16068
+ if (!(el instanceof HTMLIFrameElement)) return;
16069
+ const nextSrc = `https://www.google.com/maps?q=${encodeURIComponent(val)}&output=embed`;
16070
+ if (el.src !== nextSrc) el.src = nextSrc;
16071
+ el.setAttribute("data-ohw-map-query", val);
16072
+ }
15295
16073
  function applyLinkByKey(key, val) {
15296
16074
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
15297
16075
  if (el.dataset.ohwEditable === "link") applyLinkHref(el, val);
@@ -15302,6 +16080,11 @@ function applyLinkByKey(key, val) {
15302
16080
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
15303
16081
  }
15304
16082
  }
16083
+ function isInsideLinkEditor(target) {
16084
+ return Boolean(
16085
+ 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"]')
16086
+ );
16087
+ }
15305
16088
  function isInsideFloatingPanel(target) {
15306
16089
  return Boolean(target.closest("[data-ohw-floating-panel]"));
15307
16090
  }
@@ -15309,11 +16092,6 @@ function isPointOverFloatingPanel(clientX, clientY) {
15309
16092
  const el = document.elementFromPoint(clientX, clientY);
15310
16093
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
15311
16094
  }
15312
- function isInsideLinkEditor(target) {
15313
- return Boolean(
15314
- target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest("[data-ohw-link-page-dropdown]") || target.closest("[data-ohw-section-picker]") || target.closest("[data-ohw-navbar-container-chrome]") || target.closest("[data-ohw-navbar-add-button]") || target.closest("[data-ohw-footer-container-chrome]") || target.closest("[data-ohw-footer-add-button]") || target.closest("[data-ohw-more-menu]") || target.closest('[data-slot="dropdown-menu-content"]') || target.closest('[data-slot="popover-content"]') || target.closest('[data-slot="dialog-content"]') || target.closest('[data-slot="dialog-overlay"]')
15315
- );
15316
- }
15317
16095
  function getHrefKeyFromElement(el) {
15318
16096
  if (!el) return null;
15319
16097
  const anchor = el.closest("[data-ohw-href-key]");
@@ -15326,6 +16104,7 @@ function isNavDropdownPanelOpen(childrenRoot) {
15326
16104
  if (childrenRoot.closest("[data-ohw-nav-drawer]")) return true;
15327
16105
  const group = childrenRoot.closest("[data-ohw-nav-group]");
15328
16106
  if (group?.hasAttribute("data-ohw-nav-force-open")) return true;
16107
+ if (navDropdownsOpenOnClick()) return false;
15329
16108
  if (childrenRoot.clientHeight < 2 || childrenRoot.clientWidth < 2) return false;
15330
16109
  const style = window.getComputedStyle(childrenRoot);
15331
16110
  if (style.display === "none" || style.visibility === "hidden") return false;
@@ -15369,6 +16148,11 @@ function getNavigationItemAnchor(el) {
15369
16148
  function isNavigationItem2(el) {
15370
16149
  return getNavigationItemAnchor(el) !== null;
15371
16150
  }
16151
+ function isNavFooterScopedNavigationItem(el) {
16152
+ return Boolean(
16153
+ el.closest("nav, footer, [data-ohw-nav-container], [data-ohw-nav-drawer]")
16154
+ );
16155
+ }
15372
16156
  function socialWordsClicked(target, item) {
15373
16157
  const text = target.closest('[data-ohw-editable="text"], [data-ohw-editable="plain"]');
15374
16158
  return Boolean(text && item.contains(text));
@@ -15572,13 +16356,14 @@ function getNavigationSelectionParent(el) {
15572
16356
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
15573
16357
  return getFooterLinksContainer();
15574
16358
  }
15575
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
16359
+ if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isFooterLinksContainer(el) || isInferredFooterGroup2(el)) {
15576
16360
  return getNavigationRoot(el);
15577
16361
  }
15578
16362
  return null;
15579
16363
  }
15580
16364
  function collectNavigationItemSiblingHintRects(selected) {
15581
16365
  if (!isNavigationItem2(selected)) return [];
16366
+ if (!isNavFooterScopedNavigationItem(selected)) return [];
15582
16367
  const socialsRow = findSocialsRow(selected);
15583
16368
  if (socialsRow) {
15584
16369
  return listSocialItems(socialsRow).filter((item) => item !== selected).map((item) => item.getBoundingClientRect());
@@ -15787,7 +16572,6 @@ var ICONS = {
15787
16572
  insertUnorderedList: '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>',
15788
16573
  insertOrderedList: '<line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/>'
15789
16574
  };
15790
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
15791
16575
  var SELECTION_CHROME_GAP2 = 4;
15792
16576
  var TOOLBAR_STROKE_GAP2 = 4;
15793
16577
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -16167,6 +16951,7 @@ function StateToggle({
16167
16951
  );
16168
16952
  }
16169
16953
  var contentCache = /* @__PURE__ */ new Map();
16954
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
16170
16955
  var brandingCache = /* @__PURE__ */ new Map();
16171
16956
  var OHW_LOADER_STYLE = {
16172
16957
  position: "fixed",
@@ -16696,13 +17481,6 @@ function OhhwellsBridge() {
16696
17481
  const [isItemDragging, setIsItemDragging] = useState13(false);
16697
17482
  const [isFooterFrameSelection, setIsFooterFrameSelection] = useState13(false);
16698
17483
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
16699
- const [floatingPanel, setFloatingPanel] = useState13(null);
16700
- const floatingPanelOpenRef = useRef10(false);
16701
- floatingPanelOpenRef.current = floatingPanel !== null;
16702
- const [floatingPanelPos, setFloatingPanelPos] = useState13(null);
16703
- const [logoSizeDraft, setLogoSizeDraft] = useState13(null);
16704
- const [editorViewport, setEditorViewport] = useState13("desktop");
16705
- const [parentScrollSnap, setParentScrollSnap] = useState13(null);
16706
17484
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = useState13(null);
16707
17485
  const [footerHeadingVisible, setFooterHeadingVisible] = useState13(null);
16708
17486
  const footerDragRef = useRef10(null);
@@ -16720,6 +17498,13 @@ function OhhwellsBridge() {
16720
17498
  const brandKitRef = useRef10("");
16721
17499
  const stylesRef = useRef10("");
16722
17500
  const pendingDeleteUndoRef = useRef10(null);
17501
+ const [floatingPanel, setFloatingPanel] = useState13(null);
17502
+ const floatingPanelOpenRef = useRef10(false);
17503
+ const setFloatingPanelRef = useRef10(setFloatingPanel);
17504
+ const [floatingPanelPos, setFloatingPanelPos] = useState13(null);
17505
+ const [logoSizeDraft, setLogoSizeDraft] = useState13(null);
17506
+ const [editorViewport, setEditorViewport] = useState13("desktop");
17507
+ const [parentScrollSnap, setParentScrollSnap] = useState13(null);
16723
17508
  const [sitePages, setSitePages] = useState13([]);
16724
17509
  const [sectionsByPath, setSectionsByPath] = useState13({});
16725
17510
  const sectionsPrefetchGenRef = useRef10(0);
@@ -16728,9 +17513,21 @@ function OhhwellsBridge() {
16728
17513
  const linkPopoverOpenRef = useRef10(false);
16729
17514
  const linkPopoverGraceUntilRef = useRef10(0);
16730
17515
  setLinkPopoverRef.current = setLinkPopover;
17516
+ setFloatingPanelRef.current = setFloatingPanel;
16731
17517
  linkPopoverSessionRef.current = linkPopover;
17518
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
17519
+ useEffect13(() => {
17520
+ const syncViewport = () => {
17521
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
17522
+ setEditorViewport((prev) => prev === next ? prev : next);
17523
+ };
17524
+ syncViewport();
17525
+ window.addEventListener("resize", syncViewport);
17526
+ return () => window.removeEventListener("resize", syncViewport);
17527
+ }, []);
16732
17528
  const {
16733
17529
  navDragRef,
17530
+ navPointerDragRef,
16734
17531
  navDropSlots,
16735
17532
  activeNavDropIndex,
16736
17533
  startNavLinkDrag,
@@ -16933,7 +17730,12 @@ function OhhwellsBridge() {
16933
17730
  if (owner) persistFieldsRef.current(owner);
16934
17731
  }
16935
17732
  activeElRef.current = null;
16936
- setNavGroupForceOpen(null, false);
17733
+ const preserveNavDropdown = Boolean(navPointerDragRef.current?.el?.closest("[data-ohw-nav-children]")) || Boolean(navDragRef.current?.draggedEl?.closest("[data-ohw-nav-children]")) || Boolean(
17734
+ selectedElRef.current?.closest("[data-ohw-nav-children]") && selectedElRef.current?.closest("[data-ohw-nav-group]")?.hasAttribute("data-ohw-nav-force-open")
17735
+ );
17736
+ if (!preserveNavDropdown) {
17737
+ setNavGroupForceOpen(null, false);
17738
+ }
16937
17739
  setReorderHrefKey(null);
16938
17740
  setReorderDragDisabled(false);
16939
17741
  if (!selectedElRef.current) {
@@ -17055,7 +17857,10 @@ function OhhwellsBridge() {
17055
17857
  setSelectedIsSocialsRow(false);
17056
17858
  const isDropdownTrigger = !isNestedNavChild(navAnchor) && (navItemHasDropdownChildren(navAnchor) || navItemOwnsDropdownPanel(navAnchor));
17057
17859
  if (isNestedNavChild(navAnchor)) {
17058
- setNavGroupForceOpen(navAnchor, true);
17860
+ const group = navAnchor.closest("[data-ohw-nav-group]");
17861
+ if (group?.hasAttribute("data-ohw-nav-force-open")) {
17862
+ setNavGroupForceOpen(navAnchor, true);
17863
+ }
17059
17864
  setNavDropdownPreviewOpen(null);
17060
17865
  } else if (isDropdownTrigger) {
17061
17866
  const group = navAnchor.closest("[data-ohw-nav-group]");
@@ -17137,6 +17942,10 @@ function OhhwellsBridge() {
17137
17942
  const handleNavDropdownOpenChange = useCallback8((open) => {
17138
17943
  const selected = selectedElRef.current;
17139
17944
  if (!selected || !isNavigationItem2(selected)) return;
17945
+ if (isNestedNavChild(selected)) return;
17946
+ const group = selected.closest("[data-ohw-nav-group]");
17947
+ const domOpen = Boolean(group?.hasAttribute("data-ohw-nav-force-open"));
17948
+ if (open === domOpen) return;
17140
17949
  setNavGroupForceOpen(selected, open);
17141
17950
  setNavDropdownPreviewOpen(open);
17142
17951
  requestAnimationFrame(() => {
@@ -17666,7 +18475,10 @@ function OhhwellsBridge() {
17666
18475
  clearHrefKeyHover(anchor);
17667
18476
  const isDropdownTrigger = !isNestedNavChild(anchor) && (navItemHasDropdownChildren(anchor) || navItemOwnsDropdownPanel(anchor));
17668
18477
  if (isNestedNavChild(anchor)) {
17669
- setNavGroupForceOpen(anchor, true);
18478
+ const group = anchor.closest("[data-ohw-nav-group]");
18479
+ if (group?.hasAttribute("data-ohw-nav-force-open")) {
18480
+ setNavGroupForceOpen(anchor, true);
18481
+ }
17670
18482
  setNavDropdownPreviewOpen(null);
17671
18483
  } else if (isDropdownTrigger) {
17672
18484
  setNavGroupForceOpen(null, false);
@@ -18053,6 +18865,7 @@ function OhhwellsBridge() {
18053
18865
  }
18054
18866
  if (typeof content[STYLE_STORE_KEY] === "string") {
18055
18867
  stylesRef.current = content[STYLE_STORE_KEY];
18868
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
18056
18869
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
18057
18870
  }
18058
18871
  applyBrandChrome(content);
@@ -18060,11 +18873,11 @@ function OhhwellsBridge() {
18060
18873
  for (const [key, val] of Object.entries(content)) {
18061
18874
  if (key === "__ohw_sections") continue;
18062
18875
  if (key === AI_SECTIONS_KEY) continue;
18876
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
18877
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
18063
18878
  if (key === BRAND_KIT_KEY) continue;
18064
18879
  if (key === STYLE_STORE_KEY) continue;
18065
18880
  if (BRAND_CHROME_KEYS.has(key)) continue;
18066
- if (key === LOGO_PLACEHOLDER_KEY) continue;
18067
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
18068
18881
  if (applyVideoSettingNode(key, val)) continue;
18069
18882
  if (applyCarouselNode(key, val)) continue;
18070
18883
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -18092,6 +18905,8 @@ function OhhwellsBridge() {
18092
18905
  }
18093
18906
  } else if (el.dataset.ohwEditable === "link") {
18094
18907
  applyLinkHref(el, val);
18908
+ } else if (el.dataset.ohwEditable === "map") {
18909
+ applyMapQuery(el, val);
18095
18910
  } else if (el.dataset.ohwEditable === "icon") {
18096
18911
  applyIconMarkup(el, val);
18097
18912
  } else if (el.dataset.ohwEditable === "form") {
@@ -18130,7 +18945,9 @@ function OhhwellsBridge() {
18130
18945
  let cancelled = false;
18131
18946
  setFetchState("loading");
18132
18947
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
18133
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18948
+ const initialPath = pathname;
18949
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
18950
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18134
18951
  if (cancelled) return;
18135
18952
  const content = data?.content ?? {};
18136
18953
  const branding = Boolean(data?.showBranding);
@@ -18239,7 +19056,9 @@ function OhhwellsBridge() {
18239
19056
  }, [isEditMode]);
18240
19057
  useEffect13(() => {
18241
19058
  if (isEditMode || fetchState !== "done") return;
19059
+ console.log("env", process.env.NEXT_PUBLIC_FLOWOPS_API_URL);
18242
19060
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19061
+ console.log({ apiUrl, subdomain });
18243
19062
  bindPublishedForms(apiUrl, subdomain, contentCache.get(subdomain) ?? {});
18244
19063
  }, [isEditMode, fetchState, subdomain]);
18245
19064
  useEffect13(() => {
@@ -18249,10 +19068,10 @@ function OhhwellsBridge() {
18249
19068
  const applyFromCache = () => {
18250
19069
  const content = contentCache.get(subdomain);
18251
19070
  if (!content) return;
18252
- retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
18253
- initSectionInstancesFromContent(content, window.location.pathname);
18254
19071
  observer?.disconnect();
18255
19072
  try {
19073
+ retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
19074
+ initSectionInstancesFromContent(content, window.location.pathname);
18256
19075
  applyBrandChrome(content);
18257
19076
  if (typeof content[BRAND_KIT_KEY] === "string") {
18258
19077
  applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
@@ -18264,16 +19083,17 @@ function OhhwellsBridge() {
18264
19083
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
18265
19084
  }
18266
19085
  if (typeof content[STYLE_STORE_KEY] === "string") {
19086
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
18267
19087
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
18268
19088
  }
18269
19089
  for (const [key, val] of Object.entries(content)) {
18270
19090
  if (key === "__ohw_sections") continue;
18271
19091
  if (key === AI_SECTIONS_KEY) continue;
19092
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
19093
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
18272
19094
  if (key === BRAND_KIT_KEY) continue;
18273
19095
  if (key === STYLE_STORE_KEY) continue;
18274
19096
  if (BRAND_CHROME_KEYS.has(key)) continue;
18275
- if (key === LOGO_PLACEHOLDER_KEY) continue;
18276
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
18277
19097
  if (applyVideoSettingNode(key, val)) continue;
18278
19098
  if (applyCarouselNode(key, val)) continue;
18279
19099
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -18288,6 +19108,8 @@ function OhhwellsBridge() {
18288
19108
  if (video && video.src !== val) applyVideoSrc(video, val);
18289
19109
  } else if (el.dataset.ohwEditable === "link") {
18290
19110
  applyLinkHref(el, val);
19111
+ } else if (el.dataset.ohwEditable === "map") {
19112
+ applyMapQuery(el, val);
18291
19113
  } else if (el.dataset.ohwEditable === "form") {
18292
19114
  } else if (isIconMarkupValue(val)) {
18293
19115
  } else if (el.innerHTML !== val) {
@@ -18319,6 +19141,17 @@ function OhhwellsBridge() {
18319
19141
  debounceTimer = setTimeout(applyFromCache, 150);
18320
19142
  };
18321
19143
  applyFromCache();
19144
+ const pathCacheKey = `${subdomain}::${pathname}`;
19145
+ if (!fetchedContentPaths.has(pathCacheKey)) {
19146
+ fetchedContentPaths.add(pathCacheKey);
19147
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
19148
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
19149
+ if (!data?.content) return;
19150
+ contentCache.set(subdomain, data.content);
19151
+ applyFromCache();
19152
+ }).catch(() => {
19153
+ });
19154
+ }
18322
19155
  observer = new MutationObserver(scheduleApply);
18323
19156
  observer.observe(document.body, { childList: true, subtree: true });
18324
19157
  return () => {
@@ -18343,6 +19176,10 @@ function OhhwellsBridge() {
18343
19176
  deselectRef.current();
18344
19177
  deactivateRef.current();
18345
19178
  }, [pathname, isEditMode]);
19179
+ useEffect13(() => {
19180
+ if (!isEditMode) return;
19181
+ initSectionInstancesFromContent(editContentRef.current, pathname);
19182
+ }, [pathname, isEditMode]);
18346
19183
  useEffect13(() => {
18347
19184
  const contentForNav = () => {
18348
19185
  if (isEditMode) return editContentRef.current;
@@ -18434,26 +19271,11 @@ function OhhwellsBridge() {
18434
19271
  const t2 = setTimeout(measure, 500);
18435
19272
  const ro = new ResizeObserver(schedule);
18436
19273
  ro.observe(document.body);
18437
- let lastWidth = window.innerWidth;
18438
- let resizeTimers = [];
18439
- const clearResizeTimers = () => {
18440
- resizeTimers.forEach(clearTimeout);
18441
- resizeTimers = [];
18442
- };
18443
- const handleResize = () => {
18444
- if (window.innerWidth === lastWidth) return;
18445
- lastWidth = window.innerWidth;
18446
- clearResizeTimers();
18447
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
18448
- };
18449
- window.addEventListener("resize", handleResize);
18450
19274
  return () => {
18451
19275
  clearTimeout(t1);
18452
19276
  clearTimeout(t2);
18453
19277
  if (raf != null) cancelAnimationFrame(raf);
18454
19278
  ro.disconnect();
18455
- clearResizeTimers();
18456
- window.removeEventListener("resize", handleResize);
18457
19279
  };
18458
19280
  }, [pathname, isEditMode, postToParent2]);
18459
19281
  useEffect13(() => {
@@ -18484,28 +19306,19 @@ function OhhwellsBridge() {
18484
19306
  return;
18485
19307
  }
18486
19308
  const existing = editStylesRef.current;
18487
- let initialVh = window.innerHeight;
18488
- if (existing?.base.textContent) {
18489
- const match = existing.base.textContent.match(/\.min-h-screen[^{]*\{[^}]*min-height:\s*(\d+)px/);
18490
- if (match) initialVh = parseInt(match[1], 10);
18491
- }
19309
+ const canvasHeight = "var(--ohw-canvas-h, 852px)";
18492
19310
  const baseCss = `
18493
19311
  html { height: auto !important; }
18494
19312
  body { height: auto !important; min-height: 0 !important; overflow: hidden !important; }
18495
- .min-h-screen, .min-h-svh, .min-h-dvh { min-height: ${initialVh}px !important; }
18496
- .h-screen, .h-svh, .h-dvh { height: ${initialVh}px !important; }
18497
- [style*="100vh"] { min-height: ${initialVh}px !important; height: ${initialVh}px !important; }
18498
- [style*="100svh"] { min-height: ${initialVh}px !important; height: ${initialVh}px !important; }
18499
- [style*="100dvh"] { min-height: ${initialVh}px !important; height: ${initialVh}px !important; }
18500
- /* A section written as min-height: var(--ohw-canvas-h, 100svh) \u2014 the documented way to
18501
- build a full-viewport section that still grows for its own content \u2014 matches the three
18502
- rules above on substring alone, since the fallback text contains "100svh" too. Forcing
18503
- a literal height on top of that turns "at least one screen" into "exactly one screen",
18504
- so content taller than one screen (a long mobile hero, say) overflows a centered flex
18505
- column upward, under whatever sits above it. Only the min-height half belongs to it. */
18506
- [style*="min-height"][style*="100vh"],
18507
- [style*="min-height"][style*="100svh"],
18508
- [style*="min-height"][style*="100dvh"] { height: auto !important; }
19313
+ .min-h-screen, .min-h-svh, .min-h-dvh { min-height: ${canvasHeight} !important; }
19314
+ .h-screen, .h-svh, .h-dvh { height: ${canvasHeight} !important; }
19315
+ /* Literal inline viewport units only \u2014 skip R11 sections that already reference --ohw-canvas-h
19316
+ (their style attribute contains "100svh" only as a var() fallback string). */
19317
+ [style*="100vh"]:not([style*="--ohw-canvas-h"]) { min-height: ${canvasHeight} !important; height: ${canvasHeight} !important; }
19318
+ [style*="100svh"]:not([style*="--ohw-canvas-h"]) { min-height: ${canvasHeight} !important; height: ${canvasHeight} !important; }
19319
+ [style*="100dvh"]:not([style*="--ohw-canvas-h"]) { min-height: ${canvasHeight} !important; height: ${canvasHeight} !important; }
19320
+ /* R11: min-height via --ohw-canvas-h; height must grow with content taller than one screen. */
19321
+ [style*="min-height"][style*="--ohw-canvas-h"] { height: auto !important; }
18509
19322
  /* Emptied text keeps somewhere to click. A label typed down to nothing collapses to a
18510
19323
  couple of pixels, and getting back into it meant hunting for the caret with the mouse.
18511
19324
  Edit mode only \u2014 the published page shows nothing where there is nothing (OHH-736). */
@@ -18652,6 +19465,10 @@ function OhhwellsBridge() {
18652
19465
  outline-offset: 0 !important;
18653
19466
  box-shadow: none !important;
18654
19467
  }
19468
+ /* Open nav dropdown panels paint above AI section selection chrome (2147483000). */
19469
+ [data-ohw-nav-group][data-ohw-nav-force-open] > [data-ohw-nav-children] {
19470
+ z-index: 2147483100 !important;
19471
+ }
18655
19472
  /* Text edit wins over grab/default (must beat [data-ohw-can-drag] *). */
18656
19473
  [data-ohw-editing],
18657
19474
  [data-ohw-editing] *,
@@ -18708,9 +19525,6 @@ function OhhwellsBridge() {
18708
19525
  if (target.closest("[data-ohw-state-toggle]")) return;
18709
19526
  if (target.closest("[data-ohw-max-badge]")) return;
18710
19527
  if (isInsideLinkEditor(target)) return;
18711
- if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
18712
- clearMediaSelectionRef.current();
18713
- }
18714
19528
  if (isInsideFloatingPanel(target)) return;
18715
19529
  if (target.closest("[data-ohw-form-toolbar]")) return;
18716
19530
  if (target.closest(
@@ -18718,6 +19532,9 @@ function OhhwellsBridge() {
18718
19532
  )) {
18719
19533
  return;
18720
19534
  }
19535
+ if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
19536
+ clearMediaSelectionRef.current();
19537
+ }
18721
19538
  {
18722
19539
  const formEl = getFormElement(target);
18723
19540
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -18869,14 +19686,6 @@ function OhhwellsBridge() {
18869
19686
  }
18870
19687
  const clickedButton = findClosestButtonLike(target);
18871
19688
  const buttonOnMedia = Boolean(clickedButton && isMediaEditable(editable) && editable.contains(clickedButton));
18872
- console.log("[click-debug]", {
18873
- editableType: editable.dataset.ohwEditable,
18874
- editableTag: editable.tagName,
18875
- targetTag: target.tagName,
18876
- clickedButtonTag: clickedButton?.tagName ?? null,
18877
- buttonOnMedia,
18878
- isMediaEditableEditable: isMediaEditable(editable)
18879
- });
18880
19689
  if (isMediaEditable(editable) && !buttonOnMedia) {
18881
19690
  e.preventDefault();
18882
19691
  e.stopPropagation();
@@ -18903,11 +19712,6 @@ function OhhwellsBridge() {
18903
19712
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
18904
19713
  const hrefCtx = getHrefKeyFromElement(hrefLookupTarget);
18905
19714
  const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
18906
- console.log("[click-debug 2]", {
18907
- hrefLookupTargetTag: hrefLookupTarget.tagName,
18908
- hrefCtx: hrefCtx ? { key: hrefCtx.key } : null,
18909
- navAnchorTag: navAnchor?.tagName ?? null
18910
- });
18911
19715
  if (navAnchor) {
18912
19716
  e.preventDefault();
18913
19717
  e.stopPropagation();
@@ -19077,6 +19881,9 @@ function OhhwellsBridge() {
19077
19881
  setHoveredItemRect(null);
19078
19882
  hoveredNavContainerRef.current = null;
19079
19883
  setHoveredNavContainerRect(null);
19884
+ siblingHintElRef.current = null;
19885
+ setSiblingHintRect(null);
19886
+ setSiblingHintRects([]);
19080
19887
  return;
19081
19888
  }
19082
19889
  {
@@ -19195,7 +20002,6 @@ function OhhwellsBridge() {
19195
20002
  hoveredNavContainerRef.current = null;
19196
20003
  setHoveredNavContainerRect(null);
19197
20004
  hoveredItemElRef.current = editable;
19198
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
19199
20005
  }
19200
20006
  }
19201
20007
  }
@@ -19492,7 +20298,7 @@ function OhhwellsBridge() {
19492
20298
  }
19493
20299
  };
19494
20300
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
19495
- if (linkPopoverOpenRef.current) {
20301
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
19496
20302
  if (hoveredImageRef.current) {
19497
20303
  hoveredImageRef.current = null;
19498
20304
  hoveredImageHasTextOverlapRef.current = false;
@@ -19857,8 +20663,7 @@ function OhhwellsBridge() {
19857
20663
  };
19858
20664
  const handleMouseMove = (e) => {
19859
20665
  const { clientX, clientY } = e;
19860
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
19861
- if (isOverEditorChrome(clientX, clientY)) {
20666
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
19862
20667
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
19863
20668
  formHoverElRef.current = null;
19864
20669
  setFormHoverRect(null);
@@ -19866,6 +20671,12 @@ function OhhwellsBridge() {
19866
20671
  setHoveredItemRect(null);
19867
20672
  hoveredNavContainerRef.current = null;
19868
20673
  setHoveredNavContainerRect(null);
20674
+ siblingHintElRef.current = null;
20675
+ setSiblingHintRect(null);
20676
+ setSiblingHintRects([]);
20677
+ dismissImageHover();
20678
+ clearImageHover();
20679
+ setSectionGap(null);
19869
20680
  return;
19870
20681
  }
19871
20682
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -19877,7 +20688,11 @@ function OhhwellsBridge() {
19877
20688
  if (e.data?.type !== "ow:pointer-sync") return;
19878
20689
  const { clientX, clientY } = e.data;
19879
20690
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
19880
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
20691
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
20692
+ dismissImageHover();
20693
+ clearImageHover();
20694
+ return;
20695
+ }
19881
20696
  if (probeSocialsRowAt(clientX, clientY)) return;
19882
20697
  probeSectionGapAt(clientX, clientY);
19883
20698
  probeImageAt(clientX, clientY);
@@ -19991,7 +20806,11 @@ function OhhwellsBridge() {
19991
20806
  const slotFromItem = hrefKey ? findSocialByHrefKey(hrefKey)?.querySelector('[data-ohw-editable="icon"]') : null;
19992
20807
  const requestedIsIconSlot = Boolean(requestedIconKey) && Boolean(document.querySelector(`[data-ohw-key="${requestedIconKey}"][data-ohw-editable="icon"]`));
19993
20808
  const requestedIsFree = Boolean(requestedIconKey) && !document.querySelector(`[data-ohw-key="${requestedIconKey}"]`);
19994
- const iconKey = requestedIsIconSlot ? requestedIconKey : slotFromItem?.dataset.ohwKey ?? (requestedIsFree ? requestedIconKey : void 0);
20809
+ let iconKey = requestedIsIconSlot ? requestedIconKey : slotFromItem?.dataset.ohwKey ?? (requestedIsFree ? requestedIconKey : void 0);
20810
+ const item = hrefKey ? findSocialByHrefKey(hrefKey) : null;
20811
+ if (item && typeof iconMarkup === "string" && iconMarkup) {
20812
+ iconKey = ensureIconSlot(item) ?? iconKey;
20813
+ }
19995
20814
  if (iconKey && typeof iconMarkup === "string" && iconMarkup) {
19996
20815
  document.querySelectorAll(`[data-ohw-key="${iconKey}"][data-ohw-editable="icon"]`).forEach((el) => {
19997
20816
  applyIconMarkup(el, iconMarkup);
@@ -20001,7 +20820,7 @@ function OhhwellsBridge() {
20001
20820
  }
20002
20821
  if (iconKey && platformId) nodes.push({ key: socialPlatformKey(iconKey), text: platformId });
20003
20822
  if (iconKey && label) {
20004
- const labelKey = socialLabelKey(iconKey);
20823
+ const labelKey = (item ? socialLabelElement(item)?.getAttribute("data-ohw-key") : null) ?? socialLabelKey(iconKey);
20005
20824
  document.querySelectorAll(`[data-ohw-key="${labelKey}"]`).forEach((el) => {
20006
20825
  if (keepLabel && el.textContent?.trim()) return;
20007
20826
  el.textContent = label;
@@ -20156,6 +20975,44 @@ function OhhwellsBridge() {
20156
20975
  if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
20157
20976
  }, 400));
20158
20977
  };
20978
+ const reapCommittedAiSections = (excludeIds) => {
20979
+ const aiState = parseAiSectionsState(aiSectionsRef.current);
20980
+ if (aiState.sections.length === 0) return [];
20981
+ let orderEntries = [];
20982
+ try {
20983
+ const parsed = JSON.parse(editContentRef.current[SECTION_ORDER_KEY] ?? "[]");
20984
+ if (Array.isArray(parsed)) orderEntries = parsed;
20985
+ } catch {
20986
+ return [];
20987
+ }
20988
+ const removedIds = orderEntries.filter((entry) => entry && typeof entry.instanceId === "string" && entry.removed).map((entry) => entry.instanceId);
20989
+ if (removedIds.length === 0) return [];
20990
+ const result = reapRemovedAiSections(aiState, parseStyleStore(stylesRef.current), removedIds, excludeIds);
20991
+ if (!result.changed) return [];
20992
+ const nodes = [];
20993
+ aiSectionsRef.current = serializeAiSectionsState(result.state);
20994
+ nodes.push({ key: AI_SECTIONS_KEY, text: aiSectionsRef.current });
20995
+ const reaped = new Set(result.reapedIds);
20996
+ const nextOrderJson = JSON.stringify(orderEntries.filter((entry) => !reaped.has(entry.instanceId)));
20997
+ editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: nextOrderJson };
20998
+ setAiSectionOrder(nextOrderJson, window.location.pathname);
20999
+ nodes.push({ key: SECTION_ORDER_KEY, text: nextOrderJson });
21000
+ if (result.store) {
21001
+ stylesRef.current = JSON.stringify(result.store);
21002
+ nodes.push({ key: STYLE_STORE_KEY, text: stylesRef.current });
21003
+ }
21004
+ const nextContent = { ...editContentRef.current };
21005
+ for (const key of Object.keys(nextContent)) {
21006
+ if (key.startsWith(AI_SLOT_KEY_PREFIX) && result.slotPrefixes.some((prefix) => key.startsWith(prefix)) && nextContent[key] !== "") {
21007
+ nextContent[key] = "";
21008
+ nodes.push({ key, text: "" });
21009
+ }
21010
+ }
21011
+ editContentRef.current = nextContent;
21012
+ applyAiSectionsToDom(result.state);
21013
+ applyStylesToDom(parseStyleStore(stylesRef.current));
21014
+ return nodes;
21015
+ };
20159
21016
  const handleHydrate = (e) => {
20160
21017
  if (e.data?.type !== "ow:hydrate") return;
20161
21018
  const content = e.data.content;
@@ -20174,6 +21031,7 @@ function OhhwellsBridge() {
20174
21031
  }
20175
21032
  if (typeof content[STYLE_STORE_KEY] === "string") {
20176
21033
  stylesRef.current = content[STYLE_STORE_KEY];
21034
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
20177
21035
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
20178
21036
  }
20179
21037
  applyBrandChrome(content);
@@ -20185,11 +21043,11 @@ function OhhwellsBridge() {
20185
21043
  continue;
20186
21044
  }
20187
21045
  if (key === AI_SECTIONS_KEY) continue;
21046
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
21047
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
20188
21048
  if (key === BRAND_KIT_KEY) continue;
20189
21049
  if (key === STYLE_STORE_KEY) continue;
20190
21050
  if (BRAND_CHROME_KEYS.has(key)) continue;
20191
- if (key === LOGO_PLACEHOLDER_KEY) continue;
20192
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
20193
21051
  if (applyVideoSettingNode(key, val)) continue;
20194
21052
  if (applyCarouselNode(key, val)) continue;
20195
21053
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -20203,6 +21061,8 @@ function OhhwellsBridge() {
20203
21061
  if (video && video.src !== val) applyVideoSrc(video, val);
20204
21062
  } else if (el.dataset.ohwEditable === "link") {
20205
21063
  applyLinkHref(el, val);
21064
+ } else if (el.dataset.ohwEditable === "map") {
21065
+ applyMapQuery(el, val);
20206
21066
  } else if (el.dataset.ohwEditable === "icon") {
20207
21067
  applyIconMarkup(el, val);
20208
21068
  } else if (isIconMarkupValue(val)) {
@@ -20224,8 +21084,16 @@ function OhhwellsBridge() {
20224
21084
  reconcileFooterOrderFromContent(editContentRef.current);
20225
21085
  syncNavigationDragCursorAttrs();
20226
21086
  enforceLinkHrefs();
21087
+ const hydrateReapExclude = /* @__PURE__ */ new Set();
21088
+ const hydratePendingUndo = pendingDeleteUndoRef.current;
21089
+ if (hydratePendingUndo?.sectionInstanceId) hydrateReapExclude.add(hydratePendingUndo.sectionInstanceId);
21090
+ const reapNodes = reapCommittedAiSections(hydrateReapExclude);
21091
+ if (reapNodes.length > 0) postToParentRef.current({ type: "ow:change", nodes: reapNodes });
20227
21092
  const hydratedHeight = document.body.scrollHeight;
20228
21093
  if (hydratedHeight > 50) postToParentRef.current({ type: "ow:height", height: hydratedHeight });
21094
+ if (parseAiSectionsState(aiSectionsRef.current).sections.length > 0) {
21095
+ postAiSectionsChanged();
21096
+ }
20229
21097
  postToParentRef.current({ type: "ow:hydrate-done" });
20230
21098
  };
20231
21099
  const handleUpdateLogoIdentity = (e) => {
@@ -20397,10 +21265,26 @@ function OhhwellsBridge() {
20397
21265
  const handleGetBrand = (e) => {
20398
21266
  if (e.data?.type !== "ow:get-brand") return;
20399
21267
  const template = deriveTemplateBrand();
20400
- const value = brandKitRef.current || (template ? JSON.stringify(template) : "");
21268
+ const fallback = template ?? (() => {
21269
+ const fonts = deriveTemplateFonts();
21270
+ return fonts ? { palette: AI_DEFAULT_BRAND.palette, fonts } : null;
21271
+ })();
21272
+ const value = brandKitRef.current || (fallback ? JSON.stringify(fallback) : "");
20401
21273
  postToParentRef.current({ type: "ow:brand-value", value });
20402
21274
  };
20403
21275
  window.addEventListener("message", handleGetBrand);
21276
+ const handleGetTemplateFonts = (e) => {
21277
+ if (e.data?.type !== "ow:get-template-fonts") return;
21278
+ const fonts = deriveTemplateFonts();
21279
+ postToParentRef.current({ type: "ow:template-fonts-value", value: fonts ? JSON.stringify(fonts) : "" });
21280
+ };
21281
+ window.addEventListener("message", handleGetTemplateFonts);
21282
+ const handleGetTemplateBrand = (e) => {
21283
+ if (e.data?.type !== "ow:get-template-brand") return;
21284
+ const brand = deriveTemplateBrand();
21285
+ postToParentRef.current({ type: "ow:template-brand-value", value: brand ? JSON.stringify(brand) : "" });
21286
+ };
21287
+ window.addEventListener("message", handleGetTemplateBrand);
20404
21288
  const handleMoveSection = (e) => {
20405
21289
  if (e.data?.type !== "ow:move-section") return;
20406
21290
  const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
@@ -20408,8 +21292,11 @@ function OhhwellsBridge() {
20408
21292
  if (!instanceId || !direction) return;
20409
21293
  const entries = moveSectionInstance(instanceId, direction, window.location.pathname);
20410
21294
  if (!entries) return;
20411
- const orderJson = JSON.stringify(entries);
21295
+ const orderJson = JSON.stringify(
21296
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
21297
+ );
20412
21298
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
21299
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
20413
21300
  setAiSectionOrder(orderJson, window.location.pathname);
20414
21301
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20415
21302
  window.dispatchEvent(new Event("resize"));
@@ -20455,8 +21342,11 @@ function OhhwellsBridge() {
20455
21342
  const currentEntries = getPageSectionOrderEntries(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname);
20456
21343
  const entries = deleteSectionInstance(instanceId, window.location.pathname, currentEntries);
20457
21344
  if (!entries) return;
20458
- const orderJson = JSON.stringify(entries);
21345
+ const orderJson = JSON.stringify(
21346
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
21347
+ );
20459
21348
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
21349
+ setAiSectionOrder(orderJson, window.location.pathname);
20460
21350
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20461
21351
  aiSectionApiRef.current?.clear();
20462
21352
  window.dispatchEvent(new Event("resize"));
@@ -20465,6 +21355,7 @@ function OhhwellsBridge() {
20465
21355
  const actionId = newInstanceId();
20466
21356
  pendingDeleteUndoRef.current = {
20467
21357
  actionId,
21358
+ sectionInstanceId: instanceId,
20468
21359
  restore: () => {
20469
21360
  const restoredEntries = getPageSectionOrderEntries(
20470
21361
  editContentRef.current[SECTION_ORDER_KEY],
@@ -20472,8 +21363,11 @@ function OhhwellsBridge() {
20472
21363
  );
20473
21364
  const restored = restoreSectionInstance(instanceId, window.location.pathname, restoredEntries);
20474
21365
  if (!restored) return;
20475
- const restoredJson = JSON.stringify(restored);
21366
+ const restoredJson = JSON.stringify(
21367
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, restored)
21368
+ );
20476
21369
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: restoredJson };
21370
+ setAiSectionOrder(restoredJson, window.location.pathname);
20477
21371
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: restoredJson }] });
20478
21372
  window.dispatchEvent(new Event("resize"));
20479
21373
  const restoreHeight = document.body.scrollHeight;
@@ -20499,7 +21393,9 @@ function OhhwellsBridge() {
20499
21393
  const result = duplicateSectionInstance(instanceId, newId, window.location.pathname, currentEntries);
20500
21394
  if (!result) return;
20501
21395
  const { entries, keyRekeys } = result;
20502
- const orderJson = JSON.stringify(entries);
21396
+ const orderJson = JSON.stringify(
21397
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
21398
+ );
20503
21399
  const nodes = [{ key: SECTION_ORDER_KEY, text: orderJson }];
20504
21400
  for (const { from, to } of keyRekeys) {
20505
21401
  const inherited = editContentRef.current[from];
@@ -20509,6 +21405,7 @@ function OhhwellsBridge() {
20509
21405
  ...editContentRef.current,
20510
21406
  ...Object.fromEntries(nodes.map(({ key, text }) => [key, text]))
20511
21407
  };
21408
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
20512
21409
  setAiSectionOrder(orderJson, window.location.pathname);
20513
21410
  postToParentRef.current({ type: "ow:change", nodes });
20514
21411
  window.dispatchEvent(new Event("resize"));
@@ -20527,6 +21424,12 @@ function OhhwellsBridge() {
20527
21424
  closeLinkPopoverRef.current();
20528
21425
  return;
20529
21426
  }
21427
+ if (floatingPanelOpenRef.current) {
21428
+ setFloatingPanelRef.current(null);
21429
+ deselectRef.current();
21430
+ deactivateRef.current();
21431
+ return;
21432
+ }
20530
21433
  deselectRef.current();
20531
21434
  deactivateRef.current();
20532
21435
  clearMediaSelectionRef.current();
@@ -20759,7 +21662,12 @@ function OhhwellsBridge() {
20759
21662
  }
20760
21663
  if (navDragRef.current) {
20761
21664
  const session = navDragRef.current;
20762
- const slot = hitTestNavDropSlot(session.lastClientX, session.lastClientY, session.hrefKey);
21665
+ const slot = hitTestNavDropSlot(
21666
+ session.lastClientX,
21667
+ session.lastClientY,
21668
+ session.hrefKey,
21669
+ session.draggedEl
21670
+ );
20763
21671
  refreshNavDragVisualsRef.current(session, slot, session.lastClientX, session.lastClientY);
20764
21672
  }
20765
21673
  if (hoveredImageRef.current) {
@@ -20772,6 +21680,10 @@ function OhhwellsBridge() {
20772
21680
  };
20773
21681
  const handleSave = (e) => {
20774
21682
  if (e.data?.type !== "ow:save") return;
21683
+ const pendingUndo = pendingDeleteUndoRef.current;
21684
+ const reapExclude = /* @__PURE__ */ new Set();
21685
+ if (pendingUndo?.sectionInstanceId) reapExclude.add(pendingUndo.sectionInstanceId);
21686
+ const reapNodes = reapCommittedAiSections(reapExclude);
20775
21687
  const nodes = collectEditableNodes(editContentRef.current);
20776
21688
  const tracker = document.querySelector("[data-ohw-sections-tracker]");
20777
21689
  if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
@@ -20791,6 +21703,11 @@ function OhhwellsBridge() {
20791
21703
  const text = specs.length ? JSON.stringify(specs) : editContentRef.current[fieldsKey(formKey)];
20792
21704
  if (text) nodes.push({ key: fieldsKey(formKey), type: "text", text });
20793
21705
  });
21706
+ for (const reapNode of reapNodes) {
21707
+ if (reapNode.key.startsWith(AI_SLOT_KEY_PREFIX) && !nodes.some((n) => n.key === reapNode.key)) {
21708
+ nodes.push({ key: reapNode.key, type: "text", text: reapNode.text });
21709
+ }
21710
+ }
20794
21711
  postToParentRef.current({ type: "ow:save-result", nodes });
20795
21712
  };
20796
21713
  const handleInsertSection = (e) => {
@@ -20801,8 +21718,12 @@ function OhhwellsBridge() {
20801
21718
  if (inserted) {
20802
21719
  const tracker = getSectionsTracker();
20803
21720
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
20804
- const h = document.body.scrollHeight;
20805
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
21721
+ const reportHeight = () => {
21722
+ const h = document.body.scrollHeight;
21723
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
21724
+ };
21725
+ reportHeight();
21726
+ setTimeout(reportHeight, 500);
20806
21727
  }
20807
21728
  };
20808
21729
  const handleSwitchSchedule = (e) => {
@@ -20952,18 +21873,21 @@ function OhhwellsBridge() {
20952
21873
  }
20953
21874
  return null;
20954
21875
  };
21876
+ const isPointInRect = (el, clientX, clientY) => {
21877
+ const r2 = el.getBoundingClientRect();
21878
+ return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
21879
+ };
20955
21880
  const isPointOverEditable = (scope, clientX, clientY) => {
20956
21881
  const editables = scope.querySelectorAll("[data-ohw-editable], [data-ohw-href-key]");
20957
21882
  for (const el of editables) {
20958
- const r2 = el.getBoundingClientRect();
20959
- if (clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom) return true;
21883
+ if (isPointInRect(el, clientX, clientY)) return true;
20960
21884
  }
20961
21885
  return false;
20962
21886
  };
20963
21887
  const handleCarouselHover = (e) => {
20964
21888
  const container = findCarouselAtPoint(e.clientX, e.clientY);
20965
21889
  const scope = container?.closest("[data-ohw-section]") ?? container?.parentElement ?? null;
20966
- if (!container || !scope || isPointOverEditable(scope, e.clientX, e.clientY)) {
21890
+ if (!container || !scope || isPointOverEditable(scope, e.clientX, e.clientY) || isPointOverNavigation(e.clientX, e.clientY) || isPointOverBridgeChrome(e.clientX, e.clientY)) {
20967
21891
  setCarouselHover((prev) => prev ? null : prev);
20968
21892
  return;
20969
21893
  }
@@ -21201,15 +22125,17 @@ function OhhwellsBridge() {
21201
22125
  window.removeEventListener("message", handleAiSetBrand);
21202
22126
  window.removeEventListener("message", handleAiSetStyles);
21203
22127
  window.removeEventListener("message", handleGetBrand);
22128
+ window.removeEventListener("message", handleGetTemplateFonts);
22129
+ window.removeEventListener("message", handleGetTemplateBrand);
21204
22130
  window.removeEventListener("message", handleMoveSection);
21205
22131
  window.removeEventListener("message", handlePanelDragging);
21206
22132
  window.removeEventListener("message", handleDeleteSection);
21207
22133
  window.removeEventListener("message", handleDuplicateSection);
21208
22134
  window.removeEventListener("message", handleDeactivate);
21209
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
21210
22135
  window.removeEventListener("message", handleToastAction);
21211
22136
  window.removeEventListener("message", handleFormCount);
21212
22137
  window.removeEventListener("message", handleUiEscape);
22138
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
21213
22139
  autoSaveTimers.current.forEach(clearTimeout);
21214
22140
  autoSaveTimers.current.clear();
21215
22141
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -21412,7 +22338,7 @@ function OhhwellsBridge() {
21412
22338
  postToParent2({
21413
22339
  type: "ow:ready",
21414
22340
  version: "1",
21415
- bridgeVersion: "0.1.91",
22341
+ bridgeVersion: "0.1.93",
21416
22342
  path: pathname,
21417
22343
  nodes: collectEditableNodes(editContentRef.current),
21418
22344
  sections
@@ -22028,7 +22954,7 @@ function OhhwellsBridge() {
22028
22954
  "span",
22029
22955
  {
22030
22956
  "data-ohw-form-count": "",
22031
- className: "ml-0.5 inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-primary px-1.5 text-[11px] font-semibold text-primary-foreground",
22957
+ className: "ml-0.5 inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-bridge-primary px-1.5 text-[11px] font-semibold text-primary-foreground",
22032
22958
  children: formPickCount
22033
22959
  }
22034
22960
  )
@@ -22265,11 +23191,11 @@ function OhhwellsBridge() {
22265
23191
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
22266
23192
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
22267
23193
  children: [
22268
- /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
23194
+ /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } }),
22269
23195
  /* @__PURE__ */ jsx33(
22270
23196
  Badge,
22271
23197
  {
22272
- className: "px-8 py-1 bg-primary hover:bg-primary text-primary-foreground text-xs font-medium shrink-0 rounded-full cursor-pointer pointer-events-auto",
23198
+ className: "px-8 py-1 bg-bridge-primary hover:bg-bridge-primary text-primary-foreground text-xs font-medium shrink-0 rounded-full cursor-pointer pointer-events-auto",
22273
23199
  onClick: () => {
22274
23200
  window.parent.postMessage(
22275
23201
  {
@@ -22283,7 +23209,7 @@ function OhhwellsBridge() {
22283
23209
  children: "Add Section"
22284
23210
  }
22285
23211
  ),
22286
- /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-primary", style: { height: 3 } })
23212
+ /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } })
22287
23213
  ]
22288
23214
  }
22289
23215
  ),
@@ -22331,6 +23257,59 @@ function OhhwellsBridge() {
22331
23257
  ) : null
22332
23258
  ] });
22333
23259
  }
23260
+
23261
+ // src/ui/EmptySection.tsx
23262
+ import Link3 from "next/link";
23263
+ import { Fragment as Fragment9, jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
23264
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
23265
+ return /* @__PURE__ */ jsxs21(Fragment9, { children: [
23266
+ /* @__PURE__ */ jsx34(
23267
+ "p",
23268
+ {
23269
+ style: {
23270
+ fontFamily: "var(--brand-font-body)",
23271
+ fontSize: "0.75rem",
23272
+ fontWeight: 500,
23273
+ letterSpacing: "0.15em",
23274
+ textTransform: "uppercase",
23275
+ color: "var(--brand-accent)",
23276
+ marginBottom: "1.5rem"
23277
+ },
23278
+ children: /* @__PURE__ */ jsx34(Link3, { href: homeHref, style: { color: "inherit" }, children: /* @__PURE__ */ jsx34("span", { ...eyebrowKey ? { "data-ohw-editable": "text", "data-ohw-key": eyebrowKey } : {}, children: "Home" }) })
23279
+ }
23280
+ ),
23281
+ /* @__PURE__ */ jsx34(
23282
+ "h1",
23283
+ {
23284
+ style: {
23285
+ fontFamily: "var(--brand-font-heading)",
23286
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
23287
+ lineHeight: 1.1,
23288
+ letterSpacing: "-0.025em",
23289
+ color: "var(--brand-text)",
23290
+ marginBottom: "1rem"
23291
+ },
23292
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
23293
+ children: title
23294
+ }
23295
+ ),
23296
+ /* @__PURE__ */ jsx34(
23297
+ "p",
23298
+ {
23299
+ style: {
23300
+ fontFamily: "var(--brand-font-body)",
23301
+ fontSize: "1rem",
23302
+ lineHeight: 1.7,
23303
+ fontWeight: 300,
23304
+ color: "var(--brand-text-muted)",
23305
+ maxWidth: "340px"
23306
+ },
23307
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
23308
+ children: "This page doesn't have any content yet."
23309
+ }
23310
+ )
23311
+ ] });
23312
+ }
22334
23313
  export {
22335
23314
  AI_DEFAULT_BRAND,
22336
23315
  AI_TREE_SCHEMA_VERSIONS,
@@ -22347,6 +23326,7 @@ export {
22347
23326
  DropdownMenuItem,
22348
23327
  DropdownMenuSeparator,
22349
23328
  DropdownMenuTrigger,
23329
+ EmptySection,
22350
23330
  ItemActionToolbar,
22351
23331
  ItemInteractionLayer,
22352
23332
  LinkEditorPanel,