@ohhwells/bridge 0.1.91 → 0.1.92-next.267

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -46,6 +46,7 @@ __export(index_exports, {
46
46
  DropdownMenuItem: () => DropdownMenuItem,
47
47
  DropdownMenuSeparator: () => DropdownMenuSeparator,
48
48
  DropdownMenuTrigger: () => DropdownMenuTrigger,
49
+ EmptySection: () => EmptySection,
49
50
  ItemActionToolbar: () => ItemActionToolbar,
50
51
  ItemInteractionLayer: () => ItemInteractionLayer,
51
52
  LinkEditorPanel: () => LinkEditorPanel,
@@ -142,6 +143,10 @@ function isRenderableTree(value) {
142
143
 
143
144
  // src/lib/ai-sections-store.ts
144
145
  var AI_SECTIONS_KEY = "__ohw_ai_sections";
146
+ var AI_SLOT_KEY_PREFIX = "ai.";
147
+ function aiSlotKeyPrefixFor(sectionId) {
148
+ return `${AI_SLOT_KEY_PREFIX}${sectionId}.`;
149
+ }
145
150
  var EMPTY_AI_SECTIONS = { v: 1, sections: [] };
146
151
  function parseAiSectionsState(raw) {
147
152
  if (!raw) return EMPTY_AI_SECTIONS;
@@ -185,6 +190,63 @@ function applyTreeToState(state, payload) {
185
190
  const others = state.sections.filter((existing) => existing.id !== entry.id);
186
191
  return { ...state, v: 1, sections: [...others, entry] };
187
192
  }
193
+ function foldAlignIntoTrees(state, store) {
194
+ const byId = new Map(state.sections.map((entry) => [entry.id, entry]));
195
+ const nextTrees = /* @__PURE__ */ new Map();
196
+ const treeFor = (id) => {
197
+ const cloned = nextTrees.get(id);
198
+ if (cloned) return cloned;
199
+ const entry = byId.get(id);
200
+ if (!entry) return void 0;
201
+ const fresh = {
202
+ ...entry.tree,
203
+ rows: entry.tree.rows.map((row) => ({ ...row, blocks: row.blocks.map((block) => ({ ...block })) }))
204
+ };
205
+ nextTrees.set(id, fresh);
206
+ return fresh;
207
+ };
208
+ const nodes = {};
209
+ for (const [key, override] of Object.entries(store.nodes)) {
210
+ const match = override.align !== void 0 && key.startsWith(AI_SLOT_KEY_PREFIX) ? key.match(/^ai\.(.+?)\.r(\d+)\.b(\d+)(?:\.|$)/) : null;
211
+ const tree = match ? treeFor(match[1]) : void 0;
212
+ const block = match && tree ? tree.rows[Number(match[2])]?.blocks[Number(match[3])] : void 0;
213
+ if (!block) {
214
+ nodes[key] = override;
215
+ continue;
216
+ }
217
+ block.align = override.align;
218
+ const rest = { ...override };
219
+ delete rest.align;
220
+ if (Object.keys(rest).length > 0) nodes[key] = rest;
221
+ }
222
+ const sections = {};
223
+ for (const [sectionId, override] of Object.entries(store.sections)) {
224
+ const tree = override.align !== void 0 ? treeFor(sectionId) : void 0;
225
+ if (!tree) {
226
+ sections[sectionId] = override;
227
+ continue;
228
+ }
229
+ for (const row of tree.rows) {
230
+ for (const block of row.blocks) block.align = override.align;
231
+ }
232
+ const rest = { ...override };
233
+ delete rest.align;
234
+ if (Object.keys(rest).length > 0) sections[sectionId] = rest;
235
+ }
236
+ if (nextTrees.size === 0) return { state, store, changed: false };
237
+ return {
238
+ state: {
239
+ ...state,
240
+ v: 1,
241
+ sections: state.sections.map((entry) => {
242
+ const tree = nextTrees.get(entry.id);
243
+ return tree ? { ...entry, tree } : entry;
244
+ })
245
+ },
246
+ store: { v: 1, sections, nodes },
247
+ changed: true
248
+ };
249
+ }
188
250
  function removeFromState(state, id) {
189
251
  return { ...state, v: 1, sections: state.sections.filter((entry) => entry.id !== id) };
190
252
  }
@@ -196,6 +258,33 @@ function deleteSectionFromState(state, sectionId) {
196
258
  if (removed.includes(sectionId)) return state;
197
259
  return { ...state, v: 1, sections: state.sections, removed: [...removed, sectionId] };
198
260
  }
261
+ function reapRemovedAiSections(state, store, removedIds, excludeIds = /* @__PURE__ */ new Set()) {
262
+ const generated = new Set(state.sections.map((entry) => entry.id));
263
+ const reapedIds = [...new Set(removedIds)].filter((id) => generated.has(id) && !excludeIds.has(id));
264
+ if (reapedIds.length === 0) {
265
+ return { state, store, reapedIds: [], slotPrefixes: [], changed: false };
266
+ }
267
+ const reaped = new Set(reapedIds);
268
+ const slotPrefixes = reapedIds.map(aiSlotKeyPrefixFor);
269
+ const nextState = {
270
+ ...state,
271
+ v: 1,
272
+ sections: state.sections.filter((entry) => !reaped.has(entry.id))
273
+ };
274
+ let nextStore = store;
275
+ if (store) {
276
+ const sections = {};
277
+ for (const [key, override] of Object.entries(store.sections)) {
278
+ if (!reaped.has(key)) sections[key] = override;
279
+ }
280
+ const nodes = {};
281
+ for (const [key, override] of Object.entries(store.nodes)) {
282
+ if (!slotPrefixes.some((prefix) => key.startsWith(prefix))) nodes[key] = override;
283
+ }
284
+ nextStore = { v: 1, sections, nodes };
285
+ }
286
+ return { state: nextState, store: nextStore, reapedIds, slotPrefixes, changed: true };
287
+ }
199
288
 
200
289
  // src/lib/brand-chrome.ts
201
290
  var BRAND_NAME_KEY = "__ohw_brand_name";
@@ -403,6 +492,13 @@ function styleSheetCss() {
403
492
  `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px}px !important; padding-bottom: ${px}px !important; }`
404
493
  );
405
494
  }
495
+ rules.push(
496
+ `[data-ohw-style-corners="sharp"] :is(.card, [data-ohw-card], img, picture, figure, [data-ohw-editable="bg-image"]) { border-radius: 0 !important; }`,
497
+ `[data-ohw-style-corners="sharp"] :has(> img) { border-radius: 0 !important; }`
498
+ );
499
+ for (const align of ["left", "center", "right"]) {
500
+ rules.push(`[data-ohw-style-align="${align}"] { text-align: ${align} !important; }`);
501
+ }
406
502
  return rules.join("\n");
407
503
  }
408
504
  var STYLE_FONT_LINK_ID = "ohw-style-fonts";
@@ -429,10 +525,25 @@ var SECTION_ATTRS = {
429
525
  textDistribution: "data-ohw-style-distribution",
430
526
  headlineScale: "data-ohw-style-headline",
431
527
  imageAspect: "data-ohw-style-aspect",
432
- spacing: "data-ohw-style-spacing"
528
+ spacing: "data-ohw-style-spacing",
529
+ cornerStyle: "data-ohw-style-corners",
530
+ align: "data-ohw-style-align"
433
531
  };
434
532
  var NODE_WROTE_ATTR = "data-ohw-style-node";
435
- var NODE_PROPS = ["color", "font-family", "font-size", "background"];
533
+ var NODE_PROPS = [
534
+ "color",
535
+ "font-family",
536
+ "font-size",
537
+ "background",
538
+ "text-align",
539
+ "justify-content",
540
+ "align-items"
541
+ ];
542
+ var ALIGN_JUSTIFY = {
543
+ left: "flex-start",
544
+ center: "center",
545
+ right: "flex-end"
546
+ };
436
547
  function saveInline(el, prop) {
437
548
  const attr = `data-ohw-style-prev-${prop}`;
438
549
  if (el.hasAttribute(attr)) return;
@@ -476,6 +587,10 @@ function clearNodeProps(root) {
476
587
  function buttonSurfaceOf(el) {
477
588
  return el.closest("a, button") ?? el;
478
589
  }
590
+ function alignSubjectOf(el) {
591
+ const button = el.closest('[data-ohw-role="button"]');
592
+ return button?.parentElement ?? el;
593
+ }
479
594
  function applyStylesToDom(store) {
480
595
  ensureStyleSheet();
481
596
  clearSectionAttrs(document);
@@ -521,6 +636,18 @@ function applyStylesToDom(store) {
521
636
  el.style.setProperty("font-size", `${override.fontSize}px`, "important");
522
637
  el.setAttribute(NODE_WROTE_ATTR, "");
523
638
  }
639
+ if (override.align !== void 0) {
640
+ const subject = alignSubjectOf(el);
641
+ saveInline(subject, "text-align");
642
+ saveInline(subject, "justify-content");
643
+ subject.style.setProperty("text-align", override.align, "important");
644
+ subject.style.setProperty(
645
+ "justify-content",
646
+ ALIGN_JUSTIFY[override.align] ?? "flex-start",
647
+ "important"
648
+ );
649
+ subject.setAttribute(NODE_WROTE_ATTR, "");
650
+ }
524
651
  if (override.buttonBackground !== void 0 || override.buttonText !== void 0) {
525
652
  const surface = buttonSurfaceOf(el);
526
653
  if (override.buttonBackground !== void 0) {
@@ -541,9 +668,535 @@ function applyStylesToDom(store) {
541
668
  var import_react_dom = require("react-dom");
542
669
  var import_client = require("react-dom/client");
543
670
 
671
+ // src/lib/sections.ts
672
+ var LINK_PICKER_EXCLUDED_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
673
+ function isChromeSection(el) {
674
+ return el.matches("header, nav, footer, aside");
675
+ }
676
+ function titleCaseSectionId(id) {
677
+ return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
678
+ }
679
+ function parseSectionsFromRoot(root) {
680
+ const seen = /* @__PURE__ */ new Set();
681
+ const sections = [];
682
+ for (const el of root.querySelectorAll("[data-ohw-section]")) {
683
+ const id = el.getAttribute("data-ohw-section") ?? "";
684
+ if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
685
+ if (el.parentElement?.closest("[data-ohw-section]")) continue;
686
+ if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
687
+ continue;
688
+ seen.add(id);
689
+ const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
690
+ sections.push({ id, label });
691
+ }
692
+ return sections;
693
+ }
694
+ function collectSectionsFromDom() {
695
+ if (typeof document === "undefined") return [];
696
+ return parseSectionsFromRoot(document);
697
+ }
698
+ function parseSectionsFromHtml(html) {
699
+ const doc = new DOMParser().parseFromString(html, "text/html");
700
+ return parseSectionsFromRoot(doc);
701
+ }
702
+
703
+ // src/lib/section-instances.ts
704
+ var SECTION_ORDER_KEY = "__ohw_section_order";
705
+ var REMOVED_ATTR = "data-ohw-section-removed";
706
+ function isRemovedSection(el) {
707
+ return el.hasAttribute(REMOVED_ATTR);
708
+ }
709
+ function movableUnit(el) {
710
+ return el.closest("[data-ohw-section-container]") ?? el;
711
+ }
712
+ function sectionTypeOf(el) {
713
+ return el.getAttribute("data-ohw-section") ?? el.querySelector("[data-ohw-section]")?.getAttribute("data-ohw-section") ?? "";
714
+ }
715
+ function sectionElementOf(el) {
716
+ return el.hasAttribute("data-ohw-section") ? el : el.querySelector("[data-ohw-section]") ?? el;
717
+ }
718
+ function collectTopLevelUnits(predicate) {
719
+ const seen = /* @__PURE__ */ new Set();
720
+ const result = [];
721
+ document.querySelectorAll("[data-ohw-section]").forEach((el) => {
722
+ if (!predicate(el)) return;
723
+ const unit = movableUnit(el);
724
+ if (unit.parentElement?.closest("[data-ohw-section],[data-ohw-section-container]")) return;
725
+ if (seen.has(unit)) return;
726
+ seen.add(unit);
727
+ result.push(unit);
728
+ });
729
+ return result;
730
+ }
731
+ function topLevelSections() {
732
+ return collectTopLevelUnits((el) => !isChromeSection(el) && !isRemovedSection(movableUnit(el)));
733
+ }
734
+ function instanceIdOf(el) {
735
+ return el.dataset.ohwInstance ?? el.dataset.ohwSection ?? "";
736
+ }
737
+ function findByInstanceId(instanceId) {
738
+ const escapedId = CSS.escape(instanceId);
739
+ const direct = document.querySelector(`[data-ohw-instance="${escapedId}"]`);
740
+ if (direct) return movableUnit(direct);
741
+ const bare = document.querySelector(`[data-ohw-section="${escapedId}"]:not([data-ohw-instance])`);
742
+ return bare ? movableUnit(bare) : null;
743
+ }
744
+ function planSectionMove(instanceId, targetIndex, currentPath) {
745
+ const sections = topLevelSections();
746
+ const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
747
+ if (index === -1) return null;
748
+ const dragged = sections[index];
749
+ const others = sections.filter((_, i) => i !== index);
750
+ const clamped = Math.max(0, Math.min(targetIndex, others.length));
751
+ const reordered = [...others.slice(0, clamped), dragged, ...others.slice(clamped)];
752
+ return reordered.map((el, order) => ({
753
+ instanceId: instanceIdOf(el),
754
+ type: sectionTypeOf(el),
755
+ order,
756
+ pagePath: currentPath
757
+ }));
758
+ }
759
+ function moveSectionInstance(instanceId, direction, currentPath) {
760
+ const sections = topLevelSections();
761
+ const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
762
+ if (index === -1) return null;
763
+ const siblingIndex = direction === "up" ? index - 1 : index + 1;
764
+ if (siblingIndex < 0 || siblingIndex >= sections.length) return null;
765
+ const entries = planSectionMove(instanceId, siblingIndex, currentPath);
766
+ if (!entries) return null;
767
+ applyPersistedOrder(entries);
768
+ return entries;
769
+ }
770
+ function syncRemovedFlags(entries) {
771
+ const removedIds = new Set(entries.filter((e) => e.removed).map((e) => e.instanceId));
772
+ document.querySelectorAll(`[${REMOVED_ATTR}]`).forEach((el) => {
773
+ if (!removedIds.has(instanceIdOf(el))) {
774
+ el.style.removeProperty("display");
775
+ el.removeAttribute(REMOVED_ATTR);
776
+ }
777
+ });
778
+ for (const id of removedIds) {
779
+ const el = findByInstanceId(id);
780
+ if (el) {
781
+ el.style.display = "none";
782
+ el.setAttribute(REMOVED_ATTR, "");
783
+ }
784
+ }
785
+ }
786
+ function applyPersistedOrder(entries) {
787
+ syncRemovedFlags(entries);
788
+ if (entries.length === 0) return;
789
+ const sections = topLevelSections();
790
+ if (sections.length === 0) return;
791
+ const orderIndex = new Map(entries.map((e) => [e.instanceId, e.order]));
792
+ const ordered = [...sections].sort((a, b) => {
793
+ const aOrder = orderIndex.get(instanceIdOf(a));
794
+ const bOrder = orderIndex.get(instanceIdOf(b));
795
+ if (aOrder === void 0 && bOrder === void 0) return 0;
796
+ if (aOrder === void 0) return 1;
797
+ if (bOrder === void 0) return -1;
798
+ return aOrder - bOrder;
799
+ });
800
+ let prev = null;
801
+ for (const el of ordered) {
802
+ if (prev) prev.after(el);
803
+ prev = el;
804
+ }
805
+ }
806
+ function setSectionRemoved(instanceId, currentPath, existingEntries, removed) {
807
+ if (!findByInstanceId(instanceId)) return null;
808
+ const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
809
+ const allSections = collectTopLevelUnits((el) => !isChromeSection(el));
810
+ allSections.forEach((el, order) => {
811
+ const id = instanceIdOf(el);
812
+ if (!byId.has(id)) {
813
+ byId.set(id, { instanceId: id, type: sectionTypeOf(el), order, pagePath: currentPath });
814
+ }
815
+ });
816
+ const target = byId.get(instanceId);
817
+ if (!target) return null;
818
+ byId.set(instanceId, { ...target, removed });
819
+ const entries = Array.from(byId.values());
820
+ applyPersistedOrder(entries);
821
+ return entries;
822
+ }
823
+ function deleteSectionInstance(instanceId, currentPath, existingEntries) {
824
+ return setSectionRemoved(instanceId, currentPath, existingEntries, true);
825
+ }
826
+ function restoreSectionInstance(instanceId, currentPath, existingEntries) {
827
+ return setSectionRemoved(instanceId, currentPath, existingEntries, false);
828
+ }
829
+ function duplicateSectionInstance(instanceId, newId, currentPath, existingEntries) {
830
+ const original = findByInstanceId(instanceId);
831
+ if (!original) return null;
832
+ const clone = original.cloneNode(true);
833
+ clone.setAttribute("data-ohw-instance", newId);
834
+ const keyRekeys = rekeySectionSubtree(clone, newId);
835
+ original.insertAdjacentElement("afterend", clone);
836
+ const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
837
+ const entries = topLevelSections().map((el, order) => {
838
+ const id = instanceIdOf(el);
839
+ return {
840
+ instanceId: id,
841
+ type: sectionTypeOf(el),
842
+ order,
843
+ pagePath: currentPath,
844
+ ...byId.get(id)?.removed ? { removed: true } : {}
845
+ };
846
+ });
847
+ applyPersistedOrder(entries);
848
+ return { entries, keyRekeys };
849
+ }
850
+ function newInstanceId() {
851
+ return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `instance-${Date.now()}-${Math.random().toString(36).slice(2)}`;
852
+ }
853
+ function getPageSectionOrderEntries(raw, currentPath) {
854
+ if (!raw) return [];
855
+ try {
856
+ const entries = JSON.parse(raw);
857
+ return entries.filter((e) => !e.pagePath || e.pagePath === currentPath);
858
+ } catch {
859
+ return [];
860
+ }
861
+ }
862
+ function mergePageSectionOrder(raw, currentPath, pageEntries) {
863
+ let all = [];
864
+ if (raw) {
865
+ try {
866
+ const parsed = JSON.parse(raw);
867
+ if (Array.isArray(parsed)) all = parsed;
868
+ } catch {
869
+ }
870
+ }
871
+ const otherPages = all.filter((e) => e && e.pagePath && e.pagePath !== currentPath);
872
+ const pageIds = new Set(pageEntries.map((e) => e.instanceId));
873
+ const removedHere = all.filter(
874
+ (e) => e && (!e.pagePath || e.pagePath === currentPath) && e.removed && !pageIds.has(e.instanceId)
875
+ );
876
+ return [...otherPages, ...removedHere, ...pageEntries];
877
+ }
878
+ function rekeySectionSubtree(root, instanceId) {
879
+ const suffix = `::${instanceId}`;
880
+ const pairs = [];
881
+ const rekey = (el, attr) => {
882
+ const current = el.getAttribute(attr);
883
+ if (!current) return;
884
+ const base = current.includes("::") ? current.slice(0, current.indexOf("::")) : current;
885
+ const next = `${base}${suffix}`;
886
+ el.setAttribute(attr, next);
887
+ pairs.push({ from: current, to: next });
888
+ };
889
+ if (root.hasAttribute("data-ohw-key")) rekey(root, "data-ohw-key");
890
+ if (root.hasAttribute("data-ohw-href-key")) rekey(root, "data-ohw-href-key");
891
+ root.querySelectorAll("[data-ohw-key]").forEach((el) => rekey(el, "data-ohw-key"));
892
+ root.querySelectorAll("[data-ohw-href-key]").forEach((el) => rekey(el, "data-ohw-href-key"));
893
+ return pairs;
894
+ }
895
+ function initSectionInstancesFromContent(content, currentPath) {
896
+ document.querySelectorAll("[data-ohw-section]:not([data-ohw-instance])").forEach((el) => {
897
+ el.setAttribute("data-ohw-instance", el.getAttribute("data-ohw-section") ?? "");
898
+ });
899
+ document.querySelectorAll("[data-ohw-section-container]:not([data-ohw-instance])").forEach((el) => {
900
+ const type = sectionTypeOf(el);
901
+ if (type) el.setAttribute("data-ohw-instance", type);
902
+ });
903
+ const entries = getPageSectionOrderEntries(content[SECTION_ORDER_KEY], currentPath);
904
+ for (const entry of entries) {
905
+ if (entry.instanceId === entry.type) continue;
906
+ if (document.querySelector(`[data-ohw-instance="${CSS.escape(entry.instanceId)}"]`)) continue;
907
+ const original = document.querySelector(
908
+ `[data-ohw-section="${CSS.escape(entry.type)}"][data-ohw-instance="${CSS.escape(entry.type)}"]`
909
+ );
910
+ if (!original) continue;
911
+ const clone = original.cloneNode(true);
912
+ clone.setAttribute("data-ohw-instance", entry.instanceId);
913
+ rekeySectionSubtree(clone, entry.instanceId);
914
+ original.insertAdjacentElement("afterend", clone);
915
+ }
916
+ applyPersistedOrder(entries);
917
+ }
918
+
544
919
  // src/ui/ai-tree/AiTreeRenderer.tsx
545
920
  var import_react = __toESM(require("react"), 1);
546
921
  var import_lucide_react = require("lucide-react");
922
+
923
+ // src/lib/placeholder-imagery.ts
924
+ var U = (id) => `https://images.unsplash.com/photo-${id}?auto=format&fit=crop&w=1600&q=80`;
925
+ var GENERIC = [
926
+ U("1441986300917-64674bd600d8"),
927
+ U("1486406146926-c627a92ad1ab"),
928
+ U("1497032628192-86f99bcd76bc"),
929
+ U("1521737604893-d14cc237f11d"),
930
+ U("1522071820081-009f0129c71c"),
931
+ U("1519389950473-47ba0277781c"),
932
+ U("1460925895917-afdab827c52f"),
933
+ U("1504384308090-c894fdcc538d")
934
+ ];
935
+ var PEOPLE = [
936
+ U("1500648767791-00dcc994a43e"),
937
+ U("1494790108377-be9c29b29330"),
938
+ U("1507003211169-0a1dd7228f2d"),
939
+ U("1438761681033-6461ffad8d80"),
940
+ U("1544005313-94ddf0286df2"),
941
+ U("1472099645785-5658abf4ff4e"),
942
+ U("1519085360753-af0119f7cbe7"),
943
+ U("1534528741775-53994a69daeb")
944
+ ];
945
+ var THEMED = [
946
+ {
947
+ keywords: ["portrait", "headshot", "person", "people", "team", "staff", "avatar", "founder", "face"],
948
+ pool: PEOPLE
949
+ },
950
+ {
951
+ keywords: ["pet", "dog", "cat", "puppy", "kitten", "vet", "animal"],
952
+ pool: [
953
+ U("1548199973-03cce0bbc87b"),
954
+ U("1450778869180-41d0601e046e"),
955
+ U("1583511655857-d19b40a7a54e"),
956
+ U("1587300003388-59208cc962cb"),
957
+ U("1517849845537-4d257902454a"),
958
+ U("1601758228041-f3b2795255f1")
959
+ ]
960
+ },
961
+ {
962
+ keywords: [
963
+ "baker",
964
+ "bakery",
965
+ "cafe",
966
+ "coffee",
967
+ "latte",
968
+ "restaurant",
969
+ "pastr",
970
+ "bread",
971
+ "cake",
972
+ "cater",
973
+ "chef",
974
+ "kitchen",
975
+ "food",
976
+ "pizza",
977
+ "dessert",
978
+ "brunch",
979
+ "bistro",
980
+ "deli",
981
+ "dish",
982
+ "menu"
983
+ ],
984
+ pool: [
985
+ U("1509440159596-0249088772ff"),
986
+ U("1555507036-ab1f4038808a"),
987
+ U("1517433670267-08bbd4be890f"),
988
+ U("1486427944299-d1955d23e34d"),
989
+ U("1504754524776-8f4f37790ca0"),
990
+ U("1495474472287-4d71bcdd2085"),
991
+ U("1521017432531-fbd92d768814"),
992
+ U("1556909114-f6e7ad7d3136")
993
+ ]
994
+ },
995
+ {
996
+ keywords: [
997
+ "shop",
998
+ "store",
999
+ "boutique",
1000
+ "retail",
1001
+ "clothing",
1002
+ "fashion",
1003
+ "jewel",
1004
+ "gift",
1005
+ "florist",
1006
+ "market",
1007
+ "grocer",
1008
+ "product",
1009
+ "storefront"
1010
+ ],
1011
+ pool: [
1012
+ U("1441984904996-e0b6ba687e04"),
1013
+ U("1472851294608-062f824d29cc"),
1014
+ U("1523381210434-271e8be1f52b"),
1015
+ U("1534452203293-494d7ddbf7e0"),
1016
+ U("1445205170230-053b83016050"),
1017
+ U("1560243563-062bfc001d68")
1018
+ ]
1019
+ },
1020
+ {
1021
+ keywords: [
1022
+ "yoga",
1023
+ "pilates",
1024
+ "fitness",
1025
+ "gym",
1026
+ "workout",
1027
+ "trainer",
1028
+ "wellness",
1029
+ "meditat",
1030
+ "massage",
1031
+ "therap",
1032
+ "physio",
1033
+ "chiro",
1034
+ "nutrition",
1035
+ "spa",
1036
+ "studio"
1037
+ ],
1038
+ pool: [
1039
+ U("1544367567-0f2fcb009e0b"),
1040
+ U("1506126613408-eca07ce68773"),
1041
+ U("1545205597-3d9d02c29597"),
1042
+ U("1552196563-55cd4e45efb3"),
1043
+ U("1518611012118-696072aa579a"),
1044
+ U("1571019613454-1cb2f99b2d8b"),
1045
+ U("1540555700478-4be289fbecef"),
1046
+ U("1519824145371-296894a0daa9")
1047
+ ]
1048
+ },
1049
+ {
1050
+ keywords: [
1051
+ "salon",
1052
+ "hairdress",
1053
+ "haircut",
1054
+ "barber",
1055
+ "manicure",
1056
+ "pedicure",
1057
+ "nails",
1058
+ "beauty",
1059
+ "makeup",
1060
+ "cosmetic",
1061
+ "eyelash",
1062
+ "eyebrow",
1063
+ "skincare",
1064
+ "esthetic",
1065
+ "waxing",
1066
+ "hair"
1067
+ ],
1068
+ pool: [
1069
+ U("1560066984-138dadb4c035"),
1070
+ U("1522337660859-02fbefca4702"),
1071
+ U("1562322140-8baeececf3df"),
1072
+ U("1521590832167-7bcbfaa6381f"),
1073
+ U("1487412947147-5cebf100ffc2"),
1074
+ U("1526045478516-99145907023c")
1075
+ ]
1076
+ },
1077
+ {
1078
+ keywords: [
1079
+ "cleaning",
1080
+ "plumb",
1081
+ "electric",
1082
+ "landscap",
1083
+ "contractor",
1084
+ "handyman",
1085
+ "renov",
1086
+ "hvac",
1087
+ "roofing",
1088
+ "painting",
1089
+ "carpentry",
1090
+ "flooring",
1091
+ "movers",
1092
+ "construction",
1093
+ "tools"
1094
+ ],
1095
+ pool: [
1096
+ U("1581578731548-c64695cc6952"),
1097
+ U("1504307651254-35680f356dfd"),
1098
+ U("1581092160562-40aa08e78837"),
1099
+ U("1621905251189-08b45d6a269e"),
1100
+ U("1558618666-fcd25c85cd64"),
1101
+ U("1585128792020-803d29415281")
1102
+ ]
1103
+ },
1104
+ {
1105
+ keywords: [
1106
+ "legal",
1107
+ "attorney",
1108
+ "lawyer",
1109
+ "account",
1110
+ "bookkeep",
1111
+ "consult",
1112
+ "coaching",
1113
+ "financ",
1114
+ "insurance",
1115
+ "realtor",
1116
+ "estate",
1117
+ "marketing",
1118
+ "agency",
1119
+ "office",
1120
+ "business",
1121
+ "desk"
1122
+ ],
1123
+ pool: [
1124
+ U("1497366216548-37526070297c"),
1125
+ U("1497366811353-6870744d04b2"),
1126
+ U("1454165804606-c3d57bc86b40"),
1127
+ U("1521791136064-7986c2920216"),
1128
+ U("1556761175-b413da4baf72"),
1129
+ U("1542744173-8e7e53415bb0")
1130
+ ]
1131
+ },
1132
+ {
1133
+ keywords: [
1134
+ "wedding",
1135
+ "event",
1136
+ "party",
1137
+ "celebrat",
1138
+ "venue",
1139
+ "community",
1140
+ "nonprofit",
1141
+ "charity",
1142
+ "workshop",
1143
+ "photograph",
1144
+ "concert"
1145
+ ],
1146
+ pool: [
1147
+ U("1511578314322-379afb476865"),
1148
+ U("1501281668745-f7f57925c3b4"),
1149
+ U("1523580494863-6f3031224c94"),
1150
+ U("1540575467063-178a50c2df87"),
1151
+ U("1505236858219-8359eb29e329"),
1152
+ U("1528605248644-14dd04022da1")
1153
+ ]
1154
+ }
1155
+ ];
1156
+ function poolForSubject(subject) {
1157
+ for (const theme of THEMED) {
1158
+ if (theme.keywords.some((k) => subject.includes(k))) {
1159
+ return theme.pool;
1160
+ }
1161
+ }
1162
+ return GENERIC;
1163
+ }
1164
+ function mixedHash(text) {
1165
+ let hash = 2166136261;
1166
+ for (let i = 0; i < text.length; i++) {
1167
+ hash ^= text.charCodeAt(i);
1168
+ hash = Math.imul(hash, 16777619);
1169
+ }
1170
+ return hash >>> 16 & 65535;
1171
+ }
1172
+ function resolvePlaceholderRef(ref) {
1173
+ const match = /^placeholder:([a-z0-9-]+)$/.exec(ref);
1174
+ if (!match) return null;
1175
+ const subject = match[1];
1176
+ const pool = poolForSubject(subject.replace(/-\d+$/, ""));
1177
+ return pool[mixedHash(ref) % pool.length];
1178
+ }
1179
+ function collectPlaceholderRefs(tree) {
1180
+ const seen = /* @__PURE__ */ new Set();
1181
+ for (const match of JSON.stringify(tree ?? null).matchAll(/"(placeholder:[a-z0-9-]+)"/gu)) {
1182
+ seen.add(match[1]);
1183
+ }
1184
+ return [...seen];
1185
+ }
1186
+ function buildPlaceholderMap(tree) {
1187
+ const map = {};
1188
+ const cursor = /* @__PURE__ */ new Map();
1189
+ for (const ref of collectPlaceholderRefs(tree)) {
1190
+ const subject = ref.slice("placeholder:".length).replace(/-\d+$/, "");
1191
+ const pool = poolForSubject(subject);
1192
+ const start = cursor.get(pool) ?? mixedHash(ref) % pool.length;
1193
+ map[ref] = pool[start % pool.length];
1194
+ cursor.set(pool, start + 1);
1195
+ }
1196
+ return map;
1197
+ }
1198
+
1199
+ // src/ui/ai-tree/AiTreeRenderer.tsx
547
1200
  var import_jsx_runtime = require("react/jsx-runtime");
548
1201
  function lucideByName(name) {
549
1202
  const pascal = name.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
@@ -557,6 +1210,7 @@ var typeStyle = (spec, font) => ({
557
1210
  fontWeight: spec.weight
558
1211
  });
559
1212
  var str = (value) => typeof value === "string" ? value : "";
1213
+ var cardRadius = (slots) => slots.cornerStyle === "sharp" ? 0 : AI_TREE_TOKENS.radiusCard;
560
1214
  var featureLines = (value) => value.split(/\n|<br\s*\/?>/i).map((line) => line.trim()).filter(Boolean);
561
1215
  var CHECK_MASK = `data:image/svg+xml,${encodeURIComponent(
562
1216
  '<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>'
@@ -583,6 +1237,25 @@ var FEATURE_LINE_CSS = [
583
1237
  `background-color:currentColor;-webkit-mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px;`,
584
1238
  `mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px}`
585
1239
  ].join("");
1240
+ function buttonShellStyle(ctx, fullWidth) {
1241
+ const bs = ctx.buttonStyle;
1242
+ if (bs) {
1243
+ return {
1244
+ borderRadius: bs.radius,
1245
+ ...bs.padding ? { padding: bs.padding } : {},
1246
+ ...bs.fontFamily ? { fontFamily: bs.fontFamily } : { fontFamily: ctx.brand.fonts.body },
1247
+ ...bs.fontSize ? { fontSize: bs.fontSize } : {},
1248
+ ...bs.fontWeight ? { fontWeight: bs.fontWeight } : {},
1249
+ ...bs.letterSpacing && bs.letterSpacing !== "normal" ? { letterSpacing: bs.letterSpacing } : {},
1250
+ ...bs.textTransform && bs.textTransform !== "none" ? { textTransform: bs.textTransform } : {}
1251
+ };
1252
+ }
1253
+ return {
1254
+ borderRadius: AI_TREE_TOKENS.radiusButton,
1255
+ padding: fullWidth ? `${AI_TREE_TOKENS.spacing2}px ${AI_TREE_TOKENS.spacing4}px` : `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`,
1256
+ ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
1257
+ };
1258
+ }
586
1259
  function hexLuminance(color) {
587
1260
  const m = /^#([0-9a-f]{6})$/i.exec(color.trim());
588
1261
  if (!m) return null;
@@ -599,6 +1272,12 @@ function hexContrast(a, b) {
599
1272
  const [hi, lo] = la > lb ? [la, lb] : [lb, la];
600
1273
  return (hi + 0.05) / (lo + 0.05);
601
1274
  }
1275
+ function primaryButtonLabel(brand) {
1276
+ const darkC = hexContrast(brand.palette.primary, brand.palette.dark);
1277
+ const lightC = hexContrast(brand.palette.primary, AI_TREE_TOKENS.textPrimaryForeground);
1278
+ if (darkC === null || lightC === null) return AI_TREE_TOKENS.textPrimaryForeground;
1279
+ return darkC > lightC ? brand.palette.dark : AI_TREE_TOKENS.textPrimaryForeground;
1280
+ }
602
1281
  function accentBandContext(brand) {
603
1282
  const p = brand.palette;
604
1283
  const lightWins = (hexContrast(p.light, p.primary) ?? 99) >= (hexContrast(p.dark, p.primary) ?? 0);
@@ -616,6 +1295,22 @@ function accentBandContext(brand) {
616
1295
  function textAttrs(ctx, path) {
617
1296
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
618
1297
  }
1298
+ var AI_RESPONSIVE_CSS = [
1299
+ "@media (max-width: 960px) {",
1300
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
1301
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
1302
+ "}",
1303
+ "@media (max-width: 640px) {",
1304
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
1305
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
1306
+ // Group containers flatten to a column on phones; span placements come along for free.
1307
+ " [data-ai-responsive] [data-ai-group] { display: flex !important; flex-direction: column !important; }",
1308
+ " [data-ai-responsive] [data-ai-group] > * { grid-column: auto !important; }",
1309
+ " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
1310
+ " [data-ai-responsive] { overflow-x: hidden; }",
1311
+ " [data-ai-responsive] img { max-width: 100%; }",
1312
+ "}"
1313
+ ].join("\n");
619
1314
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
620
1315
  function MediaBox({
621
1316
  refValue,
@@ -628,13 +1323,17 @@ function MediaBox({
628
1323
  const url = refValue ? ctx.resolveMedia(refValue) : null;
629
1324
  const isIcon = /^(lucide|simple):/.test(refValue);
630
1325
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
631
- const editAttrs = ctx.keyFor && editPath && !isIcon ? { "data-ohw-key": ctx.keyFor(editPath), "data-ohw-editable": "image" } : {};
1326
+ const editAttrs = ctx.keyFor && editPath ? {
1327
+ "data-ohw-key": ctx.keyFor(editPath),
1328
+ "data-ohw-editable": isIcon ? "icon" : "image"
1329
+ } : {};
632
1330
  if (isIcon) {
633
1331
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
634
1332
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
635
1333
  "span",
636
1334
  {
637
1335
  "data-ai-icon": refValue,
1336
+ ...editAttrs,
638
1337
  style: {
639
1338
  display: "inline-flex",
640
1339
  width: 48,
@@ -698,12 +1397,10 @@ function ButtonEl({
698
1397
  width: fullWidth ? "100%" : void 0,
699
1398
  alignItems: "center",
700
1399
  justifyContent: "center",
701
- padding: fullWidth ? `${AI_TREE_TOKENS.spacing2}px ${AI_TREE_TOKENS.spacing4}px` : `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`,
702
- borderRadius: AI_TREE_TOKENS.radiusButton,
703
1400
  textDecoration: "none",
704
1401
  cursor: "pointer",
705
- ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body),
706
- ...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 }
1402
+ ...buttonShellStyle(ctx, fullWidth),
1403
+ ...secondary ? { border: `1px solid ${ctx.brand.palette.dark}`, color: ctx.brand.palette.dark } : { background: ctx.brand.palette.primary, color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand) }
707
1404
  },
708
1405
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
709
1406
  }
@@ -729,7 +1426,7 @@ function TextBlock({ slots, ctx, path }) {
729
1426
  }
730
1427
  function SectionHeaderBlock({ node, ctx, path }) {
731
1428
  const slots = node.slots ?? {};
732
- const align = slots.alignment === "center" ? "center" : "left";
1429
+ const align = node.align ?? (slots.alignment === "center" ? "center" : "left");
733
1430
  const children = node.children ?? [];
734
1431
  const buttonRowIdx = children.findIndex((c) => c.type === "button-row");
735
1432
  const buttonRow = buttonRowIdx >= 0 ? children[buttonRowIdx] : void 0;
@@ -773,7 +1470,7 @@ function SectionHeaderBlock({ node, ctx, path }) {
773
1470
  display: "flex",
774
1471
  gap: AI_TREE_TOKENS.spacing6,
775
1472
  marginTop: AI_TREE_TOKENS.spacing8,
776
- justifyContent: align === "center" ? "center" : "flex-start"
1473
+ justifyContent: align === "center" ? "center" : align === "right" ? "flex-end" : "flex-start"
777
1474
  },
778
1475
  children: (buttonRow.children ?? []).map((button, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
779
1476
  ButtonEl,
@@ -879,10 +1576,11 @@ function PricingCard({ node, ctx, path }) {
879
1576
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
880
1577
  "div",
881
1578
  {
1579
+ "data-ohw-card": "",
882
1580
  style: {
883
1581
  background: hasBg ? ctx.brand.palette.light : "transparent",
884
1582
  border: `1px solid ${dark}`,
885
- borderRadius: AI_TREE_TOKENS.radiusCard,
1583
+ borderRadius: cardRadius(slots),
886
1584
  padding: AI_TREE_TOKENS.paddingBlock,
887
1585
  display: "flex",
888
1586
  flexDirection: "column",
@@ -987,10 +1685,11 @@ function TestimonialCard({ node, ctx, path }) {
987
1685
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
988
1686
  "div",
989
1687
  {
1688
+ "data-ohw-card": "",
990
1689
  "data-ai-avatar-pos": avatarPos ?? void 0,
991
1690
  style: {
992
1691
  background: hasBg ? ctx.cardSurface : "transparent",
993
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1692
+ borderRadius: hasBg ? cardRadius(slots) : 0,
994
1693
  overflow: "hidden",
995
1694
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
996
1695
  minWidth: 0
@@ -1025,10 +1724,11 @@ function TeamCard({ node, ctx, path }) {
1025
1724
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1026
1725
  "div",
1027
1726
  {
1727
+ "data-ohw-card": "",
1028
1728
  "data-ai-avatar-pos": avatarPos ?? void 0,
1029
1729
  style: {
1030
1730
  background: hasBg ? ctx.cardSurface : "transparent",
1031
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1731
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1032
1732
  overflow: "hidden",
1033
1733
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1034
1734
  minWidth: 0,
@@ -1106,7 +1806,7 @@ function CardBlock({ node, ctx, path }) {
1106
1806
  editPath: `${path}.media`
1107
1807
  }
1108
1808
  ) : null;
1109
- const centered = slots.alignment === "center";
1809
+ const centered = (node.align ?? slots.alignment) === "center";
1110
1810
  const content = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1111
1811
  "div",
1112
1812
  {
@@ -1199,9 +1899,10 @@ function CardBlock({ node, ctx, path }) {
1199
1899
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1200
1900
  "div",
1201
1901
  {
1902
+ "data-ohw-card": "",
1202
1903
  style: {
1203
1904
  background: hasBg ? ctx.cardSurface : "transparent",
1204
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1905
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1205
1906
  overflow: "hidden",
1206
1907
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1207
1908
  display: horizontal ? "flex" : "block",
@@ -1229,7 +1930,7 @@ function CardBlock({ node, ctx, path }) {
1229
1930
  ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1230
1931
  "div",
1231
1932
  {
1232
- 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" },
1933
+ 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" },
1233
1934
  children: media
1234
1935
  }
1235
1936
  )),
@@ -1520,7 +2221,7 @@ function CollectionBlock({ node, ctx, path }) {
1520
2221
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1521
2222
  "div",
1522
2223
  {
1523
- "data-ai-grid": "",
2224
+ "data-ai-grid": String(itemsPerRow),
1524
2225
  style: {
1525
2226
  display: "grid",
1526
2227
  gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
@@ -1640,6 +2341,32 @@ function renderNode(node, ctx, path) {
1640
2341
  if (child) {
1641
2342
  return renderNode(child, ctx, `${path}.c0`);
1642
2343
  }
2344
+ if (str(slots.provider) === "map" && str(slots.query)) {
2345
+ const query = str(slots.query);
2346
+ const mapAttrs = ctx.keyFor ? {
2347
+ "data-ohw-key": ctx.keyFor(`${path}.query`),
2348
+ "data-ohw-editable": "map",
2349
+ "data-ohw-map-query": query
2350
+ } : {};
2351
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2352
+ "iframe",
2353
+ {
2354
+ ...mapAttrs,
2355
+ "data-ai-embed": "map",
2356
+ title: str(slots.title) || "Map",
2357
+ src: `https://www.google.com/maps?q=${encodeURIComponent(query)}&output=embed`,
2358
+ loading: "lazy",
2359
+ referrerPolicy: "no-referrer-when-downgrade",
2360
+ style: {
2361
+ width: "100%",
2362
+ minHeight: 320,
2363
+ border: 0,
2364
+ borderRadius: AI_TREE_TOKENS.radiusCard,
2365
+ display: "block"
2366
+ }
2367
+ }
2368
+ );
2369
+ }
1643
2370
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1644
2371
  "div",
1645
2372
  {
@@ -1743,15 +2470,12 @@ function renderNode(node, ctx, path) {
1743
2470
  alignSelf: submitAlign,
1744
2471
  border: "none",
1745
2472
  cursor: "pointer",
1746
- padding: "12px 24px",
1747
- // Corner radius follows the host template's own buttons (measured from a template
1748
- // CTA); 8px only when the page has no template button to match.
1749
- borderRadius: ctx.buttonRadius ?? 8,
2473
+ // Shape/padding/typography follow the host template's own buttons.
2474
+ ...buttonShellStyle(ctx),
1750
2475
  // Brand-styled: primary fill, brand-derived label colour (not a fixed token) so it
1751
2476
  // reads correctly on custom palettes.
1752
2477
  background: ctx.brand.palette.primary,
1753
- color: ctx.buttonLabel ?? ctx.brand.palette.light,
1754
- ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
2478
+ color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand)
1755
2479
  },
1756
2480
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
1757
2481
  },
@@ -1786,7 +2510,7 @@ function renderNode(node, ctx, path) {
1786
2510
  function AiTreeRenderer({
1787
2511
  tree,
1788
2512
  brand,
1789
- buttonRadius,
2513
+ buttonStyle,
1790
2514
  resolveMedia,
1791
2515
  editKeyPrefix
1792
2516
  }) {
@@ -1796,13 +2520,18 @@ function AiTreeRenderer({
1796
2520
  const resolvedBrand = brand ?? AI_DEFAULT_BRAND;
1797
2521
  const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
1798
2522
  const blockBrand = band?.brand ?? resolvedBrand;
2523
+ const placeholderMap = buildPlaceholderMap(tree);
1799
2524
  const ctx = {
1800
2525
  brand: blockBrand,
1801
- resolveMedia: resolveMedia ?? (() => null),
2526
+ // An owner/library ref resolves through the host resolver; a `placeholder:<subject>` ref the
2527
+ // host cannot resolve falls back to real stock photography (the per-section map first, then a
2528
+ // standalone resolve), so generated galleries, image rows, and overlay backgrounds arrive with
2529
+ // photos instead of grey boxes.
2530
+ resolveMedia: (ref) => resolveMedia?.(ref) ?? placeholderMap[ref] ?? resolvePlaceholderRef(ref),
1802
2531
  cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
1803
2532
  keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
1804
2533
  sectionAlignment: (tree.settings ?? {}).alignment === "center" ? "center" : "left",
1805
- buttonRadius,
2534
+ buttonStyle,
1806
2535
  ...band ? { buttonLabel: band.buttonLabel } : {}
1807
2536
  };
1808
2537
  const settings = tree.settings ?? {};
@@ -1825,11 +2554,25 @@ function AiTreeRenderer({
1825
2554
  }
1826
2555
  })();
1827
2556
  const distributed = !isOverlay && settings.textDistribution;
2557
+ const rowAlignItems = (rowAlign) => {
2558
+ if (rowAlign === "top") return "start";
2559
+ if (rowAlign === "bottom") return "end";
2560
+ if (rowAlign === "center" || rowAlign === "stretch") return rowAlign;
2561
+ if (distributed === "space-between") return "stretch";
2562
+ return (distributed ?? settings.verticalPosition) === "top" ? "start" : "center";
2563
+ };
2564
+ const cellAlignStyle = (blockAlign) => blockAlign ? {
2565
+ display: "flex",
2566
+ flexDirection: "column",
2567
+ alignItems: blockAlign === "left" ? "flex-start" : blockAlign === "right" ? "flex-end" : "center",
2568
+ textAlign: blockAlign
2569
+ } : {};
1828
2570
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1829
2571
  "section",
1830
2572
  {
1831
2573
  "data-ai-section": tree.tag ?? "",
1832
2574
  ...bgAttrs,
2575
+ "data-ai-responsive": "",
1833
2576
  style: {
1834
2577
  position: "relative",
1835
2578
  padding: `${pad}px 0`,
@@ -1840,12 +2583,13 @@ function AiTreeRenderer({
1840
2583
  color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
1841
2584
  },
1842
2585
  children: [
1843
- isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
2586
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
1844
2587
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
2588
+ isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
1845
2589
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1846
2590
  "div",
1847
2591
  {
1848
- "data-ai-container": "",
2592
+ "data-ai-section-inner": "",
1849
2593
  style: {
1850
2594
  position: "relative",
1851
2595
  maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
@@ -1856,12 +2600,12 @@ function AiTreeRenderer({
1856
2600
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1857
2601
  "div",
1858
2602
  {
1859
- "data-ai-row": "",
2603
+ "data-ai-columns": "",
1860
2604
  style: {
1861
2605
  display: "grid",
1862
2606
  gridTemplateColumns: "repeat(12, 1fr)",
1863
2607
  gap: AI_TREE_TOKENS.spacing6,
1864
- alignItems: distributed === "space-between" ? "stretch" : (distributed ?? settings.verticalPosition) === "top" ? "start" : "center",
2608
+ alignItems: rowAlignItems(row.align),
1865
2609
  marginTop: r2 > 0 ? AI_TREE_TOKENS.spacing8 : 0
1866
2610
  },
1867
2611
  children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -1871,6 +2615,8 @@ function AiTreeRenderer({
1871
2615
  style: {
1872
2616
  gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`,
1873
2617
  minWidth: 0,
2618
+ // Horizontal placement of the block's content within its column.
2619
+ ...cellAlignStyle(block.align),
1874
2620
  // space-between: each column becomes a flex column whose content spreads over
1875
2621
  // the full row height instead of clumping at the top.
1876
2622
  ...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
@@ -1893,7 +2639,7 @@ function AiTreeRenderer({
1893
2639
  var import_jsx_runtime2 = require("react/jsx-runtime");
1894
2640
  var CONTAINER_ATTR = "data-ohw-ai-generated";
1895
2641
  var REPLACED_ATTR = "data-ohw-ai-replaced-by";
1896
- var REMOVED_ATTR = "data-ohw-ai-removed";
2642
+ var REMOVED_ATTR2 = "data-ohw-ai-removed";
1897
2643
  var TEMPLATE_HIDDEN_ATTR = "data-ohw-ai-template-hidden";
1898
2644
  var CHROME_SECTION_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
1899
2645
  function readRootVar(name) {
@@ -1917,13 +2663,13 @@ function deriveBrandOverride() {
1917
2663
  };
1918
2664
  }
1919
2665
  function deriveTemplateBrand() {
1920
- const dark = readRootVar("--color-dark");
1921
- const primary = readRootVar("--color-primary");
1922
- const light = readRootVar("--color-light");
2666
+ const primary = readRootVar("--brand-primary") || readRootVar("--color-primary");
2667
+ const dark = readRootVar("--brand-text") || readRootVar("--color-dark");
2668
+ const light = readRootVar("--brand-background") || readRootVar("--color-light");
1923
2669
  if (!dark || !primary || !light) return null;
1924
- const accent = readRootVar("--color-accent");
1925
- const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1926
- const body = readRootVar("--font-body");
2670
+ const accent = readRootVar("--brand-accent") || readRootVar("--color-accent");
2671
+ const heading = readRootVar("--brand-font-heading") || readRootVar("--font-heading") || readRootVar("--font-display");
2672
+ const body = readRootVar("--brand-font-body") || readRootVar("--font-body");
1927
2673
  return {
1928
2674
  palette: { dark, primary, accent: accent || dark, light },
1929
2675
  fonts: {
@@ -1932,12 +2678,32 @@ function deriveTemplateBrand() {
1932
2678
  }
1933
2679
  };
1934
2680
  }
1935
- function deriveTemplateButtonRadius() {
2681
+ function deriveTemplateButtonStyle() {
1936
2682
  if (typeof document === "undefined") return null;
1937
- const btn = document.querySelector('[data-ohw-role="button"]');
2683
+ const btn = Array.from(document.querySelectorAll('[data-ohw-role="button"]')).find(
2684
+ (el) => !el.closest(`[${CONTAINER_ATTR}]`)
2685
+ );
1938
2686
  if (!btn) return null;
1939
- const radius = getComputedStyle(btn).borderTopLeftRadius;
1940
- return radius || null;
2687
+ const cs = getComputedStyle(btn);
2688
+ const corners = [
2689
+ cs.borderTopLeftRadius,
2690
+ cs.borderTopRightRadius,
2691
+ cs.borderBottomRightRadius,
2692
+ cs.borderBottomLeftRadius
2693
+ ].map((v) => v || "0px");
2694
+ const radius = corners.every((v) => v === corners[0]) ? corners[0] : corners.join(" ");
2695
+ const px = (v) => parseFloat(v) || 0;
2696
+ const padY = Math.max(px(cs.paddingTop), px(cs.paddingBottom));
2697
+ const padX = Math.max(px(cs.paddingLeft), px(cs.paddingRight));
2698
+ return {
2699
+ radius: radius || "10px",
2700
+ padding: `${padY}px ${padX}px`,
2701
+ fontFamily: cs.fontFamily || "",
2702
+ fontSize: cs.fontSize || "",
2703
+ fontWeight: cs.fontWeight || "",
2704
+ letterSpacing: cs.letterSpacing || "",
2705
+ textTransform: cs.textTransform || ""
2706
+ };
1941
2707
  }
1942
2708
  var mounted = /* @__PURE__ */ new Map();
1943
2709
  function findTemplateSection(id) {
@@ -1988,18 +2754,18 @@ function placeContainer(container, entry) {
1988
2754
  }
1989
2755
  function syncRemovedSections(state) {
1990
2756
  const removed = new Set(state.removed ?? []);
1991
- for (const el of document.querySelectorAll(`[${REMOVED_ATTR}]`)) {
2757
+ for (const el of document.querySelectorAll(`[${REMOVED_ATTR2}]`)) {
1992
2758
  const id = el.getAttribute("data-ohw-section") ?? "";
1993
2759
  if (!removed.has(id)) {
1994
2760
  el.style.removeProperty("display");
1995
- el.removeAttribute(REMOVED_ATTR);
2761
+ el.removeAttribute(REMOVED_ATTR2);
1996
2762
  }
1997
2763
  }
1998
2764
  for (const id of removed) {
1999
2765
  const section = findTemplateSection(id);
2000
2766
  if (section && !section.hasAttribute(REPLACED_ATTR)) {
2001
2767
  section.style.display = "none";
2002
- section.setAttribute(REMOVED_ATTR, "");
2768
+ section.setAttribute(REMOVED_ATTR2, "");
2003
2769
  }
2004
2770
  }
2005
2771
  }
@@ -2016,7 +2782,7 @@ function syncTemplateHidden(state, pageHasSections) {
2016
2782
  if (el.hasAttribute(CONTAINER_ATTR)) continue;
2017
2783
  if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) continue;
2018
2784
  if (el.parentElement?.closest("[data-ohw-section]")) continue;
2019
- if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR)) continue;
2785
+ if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR2)) continue;
2020
2786
  el.style.display = "none";
2021
2787
  el.setAttribute(TEMPLATE_HIDDEN_ATTR, "");
2022
2788
  }
@@ -2040,18 +2806,23 @@ function syncReplacedOriginals(state) {
2040
2806
  }
2041
2807
  }
2042
2808
  var sectionOrderIndex = /* @__PURE__ */ new Map();
2809
+ var removedSectionIds = /* @__PURE__ */ new Set();
2043
2810
  function setAiSectionOrder(raw, currentPath) {
2044
2811
  const next = /* @__PURE__ */ new Map();
2812
+ const removed = /* @__PURE__ */ new Set();
2045
2813
  if (raw) {
2046
2814
  try {
2047
2815
  const entries = JSON.parse(raw);
2048
2816
  for (const entry of entries) {
2049
- if (!entry.pagePath || entry.pagePath === currentPath) next.set(entry.instanceId, entry.order);
2817
+ if (entry.pagePath && entry.pagePath !== currentPath) continue;
2818
+ next.set(entry.instanceId, entry.order);
2819
+ if (entry.removed) removed.add(entry.instanceId);
2050
2820
  }
2051
2821
  } catch {
2052
2822
  }
2053
2823
  }
2054
2824
  sectionOrderIndex = next;
2825
+ removedSectionIds = removed;
2055
2826
  }
2056
2827
  function applyExplicitOrder(entries) {
2057
2828
  if (sectionOrderIndex.size === 0) return entries;
@@ -2087,11 +2858,23 @@ function orderByChain(sections) {
2087
2858
  for (const root of roots) visit(root);
2088
2859
  return out.length === sections.length ? out : sections;
2089
2860
  }
2861
+ function syncSoftRemovedGenerated() {
2862
+ for (const [id, section] of mounted) {
2863
+ const el = section.container;
2864
+ if (removedSectionIds.has(id)) {
2865
+ el.style.display = "none";
2866
+ el.setAttribute(REMOVED_ATTR, "");
2867
+ } else if (el.hasAttribute(REMOVED_ATTR)) {
2868
+ el.style.removeProperty("display");
2869
+ el.removeAttribute(REMOVED_ATTR);
2870
+ }
2871
+ }
2872
+ }
2090
2873
  function applyAiSectionsToDom(state, options) {
2091
2874
  if (typeof document === "undefined") return;
2092
2875
  const brandOverride = deriveBrandOverride();
2093
2876
  const templateBrand = deriveTemplateBrand();
2094
- const templateButtonRadius = deriveTemplateButtonRadius();
2877
+ const templateButtonStyle = deriveTemplateButtonStyle();
2095
2878
  const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
2096
2879
  const pagePath = window.location.pathname;
2097
2880
  const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
@@ -2131,7 +2914,7 @@ function applyAiSectionsToDom(state, options) {
2131
2914
  {
2132
2915
  tree: entry.tree,
2133
2916
  brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
2134
- buttonRadius: templateButtonRadius,
2917
+ buttonStyle: templateButtonStyle,
2135
2918
  resolveMedia,
2136
2919
  editKeyPrefix: `ai.${entry.id}`
2137
2920
  }
@@ -2154,6 +2937,7 @@ function applyAiSectionsToDom(state, options) {
2154
2937
  syncReplacedOriginals(state);
2155
2938
  syncRemovedSections(state);
2156
2939
  syncTemplateHidden(state, pageSections.length > 0);
2940
+ syncSoftRemovedGenerated();
2157
2941
  }
2158
2942
 
2159
2943
  // src/useLinkHrefGuardian.ts
@@ -7883,6 +8667,7 @@ function MediaOverlay({
7883
8667
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
7884
8668
  );
7885
8669
  }, [isVideo]);
8670
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
7886
8671
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
7887
8672
  const box = {
7888
8673
  position: "fixed",
@@ -8012,17 +8797,17 @@ function MediaOverlay({
8012
8797
  },
8013
8798
  children: [
8014
8799
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
8015
- isVideo ? "Replace video" : "Replace image"
8800
+ replaceLabel
8016
8801
  ]
8017
8802
  }
8018
8803
  ),
8019
- replaceMode === "none" ? null : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8804
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8020
8805
  Button,
8021
8806
  {
8022
8807
  "data-ohw-media-overlay": "",
8023
8808
  variant: "outline",
8024
8809
  size: "sm",
8025
- "aria-label": isVideo ? "Replace video" : "Replace image",
8810
+ "aria-label": replaceLabel,
8026
8811
  className: "gap-1.5 cursor-pointer hover:bg-background",
8027
8812
  style: {
8028
8813
  ...OVERLAY_BUTTON_STYLE,
@@ -8045,7 +8830,7 @@ function MediaOverlay({
8045
8830
  },
8046
8831
  children: [
8047
8832
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
8048
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
8833
+ replaceMode === "full" ? replaceLabel : null
8049
8834
  ]
8050
8835
  }
8051
8836
  )
@@ -8098,235 +8883,23 @@ function CarouselOverlay({
8098
8883
  onMouseDown: (e) => e.preventDefault(),
8099
8884
  onClick: (e) => {
8100
8885
  e.stopPropagation();
8101
- onEdit(hover.key);
8102
- },
8103
- children: [
8104
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react6.GalleryHorizontal, { size: 14 }),
8105
- "Edit gallery"
8106
- ]
8107
- }
8108
- )
8109
- }
8110
- );
8111
- }
8112
-
8113
- // src/ui/ai-section/AiSectionOverlay.tsx
8114
- var import_react8 = require("react");
8115
- var import_lucide_react7 = require("lucide-react");
8116
-
8117
- // src/lib/sections.ts
8118
- var LINK_PICKER_EXCLUDED_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
8119
- function isChromeSection(el) {
8120
- return el.matches("header, nav, footer, aside");
8121
- }
8122
- function titleCaseSectionId(id) {
8123
- return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
8124
- }
8125
- function parseSectionsFromRoot(root) {
8126
- const seen = /* @__PURE__ */ new Set();
8127
- const sections = [];
8128
- for (const el of root.querySelectorAll("[data-ohw-section]")) {
8129
- const id = el.getAttribute("data-ohw-section") ?? "";
8130
- if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
8131
- if (el.parentElement?.closest("[data-ohw-section]")) continue;
8132
- if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
8133
- continue;
8134
- seen.add(id);
8135
- const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
8136
- sections.push({ id, label });
8137
- }
8138
- return sections;
8139
- }
8140
- function collectSectionsFromDom() {
8141
- if (typeof document === "undefined") return [];
8142
- return parseSectionsFromRoot(document);
8143
- }
8144
- function parseSectionsFromHtml(html) {
8145
- const doc = new DOMParser().parseFromString(html, "text/html");
8146
- return parseSectionsFromRoot(doc);
8147
- }
8148
-
8149
- // src/lib/section-instances.ts
8150
- var SECTION_ORDER_KEY = "__ohw_section_order";
8151
- var REMOVED_ATTR2 = "data-ohw-section-removed";
8152
- function isRemovedSection(el) {
8153
- return el.hasAttribute(REMOVED_ATTR2);
8154
- }
8155
- function topLevelSections() {
8156
- return Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
8157
- (el) => !el.parentElement?.closest("[data-ohw-section]") && !isChromeSection(el) && !isRemovedSection(el)
8158
- );
8159
- }
8160
- function instanceIdOf(el) {
8161
- return el.dataset.ohwInstance ?? el.dataset.ohwSection ?? "";
8162
- }
8163
- function findByInstanceId(instanceId) {
8164
- const escapedId = CSS.escape(instanceId);
8165
- return document.querySelector(`[data-ohw-instance="${escapedId}"]`) ?? document.querySelector(`[data-ohw-section="${escapedId}"]:not([data-ohw-instance])`);
8166
- }
8167
- function planSectionMove(instanceId, targetIndex, currentPath) {
8168
- const sections = topLevelSections();
8169
- const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
8170
- if (index === -1) return null;
8171
- const dragged = sections[index];
8172
- const others = sections.filter((_, i) => i !== index);
8173
- const clamped = Math.max(0, Math.min(targetIndex, others.length));
8174
- const reordered = [...others.slice(0, clamped), dragged, ...others.slice(clamped)];
8175
- return reordered.map((el, order) => ({
8176
- instanceId: instanceIdOf(el),
8177
- type: el.getAttribute("data-ohw-section") ?? "",
8178
- order,
8179
- pagePath: currentPath
8180
- }));
8181
- }
8182
- function moveSectionInstance(instanceId, direction, currentPath) {
8183
- const sections = topLevelSections();
8184
- const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
8185
- if (index === -1) return null;
8186
- const siblingIndex = direction === "up" ? index - 1 : index + 1;
8187
- if (siblingIndex < 0 || siblingIndex >= sections.length) return null;
8188
- const entries = planSectionMove(instanceId, siblingIndex, currentPath);
8189
- if (!entries) return null;
8190
- applyPersistedOrder(entries);
8191
- return entries;
8192
- }
8193
- function syncRemovedFlags(entries) {
8194
- const removedIds = new Set(entries.filter((e) => e.removed).map((e) => e.instanceId));
8195
- document.querySelectorAll(`[${REMOVED_ATTR2}]`).forEach((el) => {
8196
- if (!removedIds.has(instanceIdOf(el))) {
8197
- el.style.removeProperty("display");
8198
- el.removeAttribute(REMOVED_ATTR2);
8199
- }
8200
- });
8201
- for (const id of removedIds) {
8202
- const el = findByInstanceId(id);
8203
- if (el) {
8204
- el.style.display = "none";
8205
- el.setAttribute(REMOVED_ATTR2, "");
8886
+ onEdit(hover.key);
8887
+ },
8888
+ children: [
8889
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react6.GalleryHorizontal, { size: 14 }),
8890
+ "Edit gallery"
8891
+ ]
8892
+ }
8893
+ )
8206
8894
  }
8207
- }
8208
- }
8209
- function applyPersistedOrder(entries) {
8210
- syncRemovedFlags(entries);
8211
- if (entries.length === 0) return;
8212
- const sections = topLevelSections();
8213
- if (sections.length === 0) return;
8214
- const orderIndex = new Map(entries.map((e) => [e.instanceId, e.order]));
8215
- const ordered = [...sections].sort((a, b) => {
8216
- const aOrder = orderIndex.get(instanceIdOf(a));
8217
- const bOrder = orderIndex.get(instanceIdOf(b));
8218
- if (aOrder === void 0 && bOrder === void 0) return 0;
8219
- if (aOrder === void 0) return 1;
8220
- if (bOrder === void 0) return -1;
8221
- return aOrder - bOrder;
8222
- });
8223
- let prev = null;
8224
- for (const el of ordered) {
8225
- if (prev) prev.after(el);
8226
- prev = el;
8227
- }
8228
- }
8229
- function setSectionRemoved(instanceId, currentPath, existingEntries, removed) {
8230
- if (!findByInstanceId(instanceId)) return null;
8231
- const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
8232
- const allSections = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
8233
- (el) => !el.parentElement?.closest("[data-ohw-section]") && !isChromeSection(el)
8234
8895
  );
8235
- allSections.forEach((el, order) => {
8236
- const id = instanceIdOf(el);
8237
- if (!byId.has(id)) {
8238
- byId.set(id, { instanceId: id, type: el.getAttribute("data-ohw-section") ?? "", order, pagePath: currentPath });
8239
- }
8240
- });
8241
- const target = byId.get(instanceId);
8242
- if (!target) return null;
8243
- byId.set(instanceId, { ...target, removed });
8244
- const entries = Array.from(byId.values());
8245
- applyPersistedOrder(entries);
8246
- return entries;
8247
- }
8248
- function deleteSectionInstance(instanceId, currentPath, existingEntries) {
8249
- return setSectionRemoved(instanceId, currentPath, existingEntries, true);
8250
- }
8251
- function restoreSectionInstance(instanceId, currentPath, existingEntries) {
8252
- return setSectionRemoved(instanceId, currentPath, existingEntries, false);
8253
- }
8254
- function duplicateSectionInstance(instanceId, newId, currentPath, existingEntries) {
8255
- const original = findByInstanceId(instanceId);
8256
- if (!original) return null;
8257
- const clone = original.cloneNode(true);
8258
- clone.setAttribute("data-ohw-instance", newId);
8259
- const keyRekeys = rekeySectionSubtree(clone, newId);
8260
- original.insertAdjacentElement("afterend", clone);
8261
- const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
8262
- const entries = topLevelSections().map((el, order) => {
8263
- const id = instanceIdOf(el);
8264
- return {
8265
- instanceId: id,
8266
- type: el.getAttribute("data-ohw-section") ?? "",
8267
- order,
8268
- pagePath: currentPath,
8269
- ...byId.get(id)?.removed ? { removed: true } : {}
8270
- };
8271
- });
8272
- applyPersistedOrder(entries);
8273
- return { entries, keyRekeys };
8274
- }
8275
- function newInstanceId() {
8276
- return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `instance-${Date.now()}-${Math.random().toString(36).slice(2)}`;
8277
- }
8278
- function getPageSectionOrderEntries(raw, currentPath) {
8279
- if (!raw) return [];
8280
- try {
8281
- const entries = JSON.parse(raw);
8282
- return entries.filter((e) => !e.pagePath || e.pagePath === currentPath);
8283
- } catch {
8284
- return [];
8285
- }
8286
- }
8287
- function rekeySectionSubtree(root, instanceId) {
8288
- const suffix = `::${instanceId}`;
8289
- const pairs = [];
8290
- const rekey = (el, attr) => {
8291
- const current = el.getAttribute(attr);
8292
- if (!current) return;
8293
- const base = current.includes("::") ? current.slice(0, current.indexOf("::")) : current;
8294
- const next = `${base}${suffix}`;
8295
- el.setAttribute(attr, next);
8296
- pairs.push({ from: current, to: next });
8297
- };
8298
- if (root.hasAttribute("data-ohw-key")) rekey(root, "data-ohw-key");
8299
- if (root.hasAttribute("data-ohw-href-key")) rekey(root, "data-ohw-href-key");
8300
- root.querySelectorAll("[data-ohw-key]").forEach((el) => rekey(el, "data-ohw-key"));
8301
- root.querySelectorAll("[data-ohw-href-key]").forEach((el) => rekey(el, "data-ohw-href-key"));
8302
- return pairs;
8303
- }
8304
- function initSectionInstancesFromContent(content, currentPath) {
8305
- document.querySelectorAll("[data-ohw-section]:not([data-ohw-instance])").forEach((el) => {
8306
- el.setAttribute("data-ohw-instance", el.getAttribute("data-ohw-section") ?? "");
8307
- });
8308
- const entries = getPageSectionOrderEntries(content[SECTION_ORDER_KEY], currentPath);
8309
- for (const entry of entries) {
8310
- if (entry.instanceId === entry.type) continue;
8311
- if (document.querySelector(`[data-ohw-instance="${CSS.escape(entry.instanceId)}"]`)) continue;
8312
- const original = document.querySelector(
8313
- `[data-ohw-section="${CSS.escape(entry.type)}"][data-ohw-instance="${CSS.escape(entry.type)}"]`
8314
- );
8315
- if (!original) continue;
8316
- const clone = original.cloneNode(true);
8317
- clone.setAttribute("data-ohw-instance", entry.instanceId);
8318
- rekeySectionSubtree(clone, entry.instanceId);
8319
- original.insertAdjacentElement("afterend", clone);
8320
- }
8321
- applyPersistedOrder(entries);
8322
8896
  }
8323
8897
 
8324
8898
  // src/ui/ai-section/AiSectionOverlay.tsx
8899
+ var import_react8 = require("react");
8900
+ var import_lucide_react7 = require("lucide-react");
8325
8901
  var import_jsx_runtime17 = require("react/jsx-runtime");
8326
- function findSectionElement(instanceId) {
8327
- const escaped = CSS.escape(instanceId);
8328
- return document.querySelector(`[data-ohw-instance="${escaped}"]`) ?? document.querySelector(`[data-ohw-section="${escaped}"]:not([data-ohw-instance])`);
8329
- }
8902
+ var findSectionElement = findByInstanceId;
8330
8903
  function readRect(instanceId) {
8331
8904
  const el = findSectionElement(instanceId);
8332
8905
  if (!el) return null;
@@ -8366,7 +8939,7 @@ function useLiveSectionRect(sectionId) {
8366
8939
  }
8367
8940
  function computeSectionBoundaryFlags(instanceId) {
8368
8941
  const topLevel = topLevelSections();
8369
- const index = topLevel.findIndex((el) => (el.dataset.ohwInstance ?? el.dataset.ohwSection) === instanceId);
8942
+ const index = topLevel.findIndex((el) => instanceIdOf(el) === instanceId);
8370
8943
  if (index === -1) return { isFirst: true, isLast: true };
8371
8944
  return { isFirst: index === 0, isLast: index === topLevel.length - 1 };
8372
8945
  }
@@ -8450,18 +9023,20 @@ function AiSectionOverlay({
8450
9023
  selectedIdRef.current = selectedId;
8451
9024
  const report = (0, import_react8.useCallback)(
8452
9025
  (el) => {
9026
+ const labelSrc = el ? sectionElementOf(el) : null;
8453
9027
  postToParent2({
8454
9028
  type: "ow:section-selected",
8455
- sectionId: el ? el.dataset.ohwInstance ?? el.dataset.ohwSection ?? null : null,
8456
- sectionLabel: el ? el.dataset.ohwSectionLabel ?? titleCaseSectionId(el.dataset.ohwSection ?? "") : null
9029
+ sectionId: el ? instanceIdOf(el) || null : null,
9030
+ sectionLabel: labelSrc ? labelSrc.dataset.ohwSectionLabel ?? titleCaseSectionId(labelSrc.dataset.ohwSection ?? "") : null
8457
9031
  });
8458
9032
  },
8459
9033
  [postToParent2]
8460
9034
  );
8461
9035
  const selectFromElement = (0, import_react8.useCallback)(
8462
9036
  (el, options) => {
8463
- const sectionEl = el?.closest("[data-ohw-section]") ?? null;
8464
- const id = sectionEl ? sectionEl.dataset.ohwInstance ?? sectionEl.dataset.ohwSection ?? null : null;
9037
+ const inner = el?.closest("[data-ohw-section]") ?? null;
9038
+ const sectionEl = inner ? movableUnit(inner) : null;
9039
+ const id = sectionEl ? instanceIdOf(sectionEl) || null : null;
8465
9040
  if (id === selectedIdRef.current) return;
8466
9041
  setSelectedId(id);
8467
9042
  if (options?.report !== false) report(sectionEl);
@@ -8527,7 +9102,8 @@ function AiSectionOverlay({
8527
9102
  return;
8528
9103
  }
8529
9104
  const sec = t.closest("[data-ohw-section]");
8530
- setHoveredId(sec ? sec.dataset.ohwInstance ?? sec.dataset.ohwSection ?? null : null);
9105
+ const unit = sec ? movableUnit(sec) : null;
9106
+ setHoveredId(unit ? instanceIdOf(unit) || null : null);
8531
9107
  };
8532
9108
  const onLeave = () => setHoveredId(null);
8533
9109
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -13038,6 +13614,7 @@ function readLogoSizeState(content, placement) {
13038
13614
  function getLogoElement(el) {
13039
13615
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
13040
13616
  if (marked) return marked;
13617
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
13041
13618
  const root = el.closest("nav, [data-ohw-nav-root], footer");
13042
13619
  if (!root) return null;
13043
13620
  const anchor = el.closest("a");
@@ -14104,15 +14681,17 @@ function useSectionDrag({
14104
14681
  clearSectionDragVisuals();
14105
14682
  return;
14106
14683
  }
14107
- const orderJson = JSON.stringify(entries);
14684
+ const orderJson = JSON.stringify(
14685
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
14686
+ );
14108
14687
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
14109
14688
  setAiSectionOrder(orderJson, window.location.pathname);
14110
14689
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
14111
- applyPersistedOrder(entries);
14690
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
14112
14691
  clearSectionDragVisuals();
14113
14692
  requestAnimationFrame(() => {
14114
14693
  if (editContentRef.current[SECTION_ORDER_KEY] === orderJson) {
14115
- applyPersistedOrder(entries);
14694
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
14116
14695
  }
14117
14696
  requestAnimationFrame(() => {
14118
14697
  window.dispatchEvent(new Event("resize"));
@@ -14145,8 +14724,9 @@ function useSectionDrag({
14145
14724
  const target = e.target;
14146
14725
  if (!(target instanceof HTMLElement)) return;
14147
14726
  if (target.closest(SECTION_DRAG_EXCLUDED_SELECTOR)) return;
14148
- const sectionEl = target.closest("[data-ohw-section]");
14149
- if (!sectionEl || isChromeSection(sectionEl) || sectionEl.dataset.ohwSection === "footer") return;
14727
+ const inner = target.closest("[data-ohw-section]");
14728
+ if (!inner || isChromeSection(inner) || inner.dataset.ohwSection === "footer") return;
14729
+ const sectionEl = movableUnit(inner);
14150
14730
  if (!topLevelSections().includes(sectionEl)) return;
14151
14731
  startSectionPressDrag(sectionEl, e.clientX, e.clientY, e.pointerId);
14152
14732
  };
@@ -14413,6 +14993,9 @@ function collectEditableNodes(extraContent, root = document) {
14413
14993
  if (el.dataset.ohwEditable === "link") {
14414
14994
  return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref3(el) };
14415
14995
  }
14996
+ if (el.dataset.ohwEditable === "map") {
14997
+ return { key: el.dataset.ohwKey ?? "", type: "map", text: el.dataset.ohwMapQuery ?? "" };
14998
+ }
14416
14999
  return {
14417
15000
  key: el.dataset.ohwKey ?? "",
14418
15001
  type: el.dataset.ohwEditable ?? "text",
@@ -14975,21 +15558,10 @@ function parseSchedulingInsertAfter(insertAfter) {
14975
15558
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
14976
15559
  };
14977
15560
  }
14978
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
14979
- const parsed = parseSchedulingInsertAfter(insertAfter);
14980
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
14981
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
14982
- return { effectiveInsertAfter, insertBefore };
14983
- }
14984
- function getSchedulingMountPoint(insertAfter) {
14985
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
14986
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
14987
- if (!anchorEl && anchor === "scheduling") {
14988
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
14989
- anchorEl = widgets.at(-1) ?? null;
14990
- }
14991
- if (!anchorEl) return null;
14992
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
15561
+ function resolveEntryAnchor(entry) {
15562
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
15563
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
15564
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
14993
15565
  }
14994
15566
  function schedulingMountDepth(insertAfter) {
14995
15567
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -15006,8 +15578,7 @@ function getPageSchedulingEntries(raw) {
15006
15578
  }
15007
15579
  }
15008
15580
  function isSchedulingWidgetMissing(entry) {
15009
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
15010
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
15581
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
15011
15582
  }
15012
15583
  function hasMissingSchedulingWidgets(entries) {
15013
15584
  return entries.some(isSchedulingWidgetMissing);
@@ -15045,18 +15616,18 @@ function initSectionsFromContent(content, removeExisting = false, currentPath =
15045
15616
  } catch {
15046
15617
  }
15047
15618
  }
15048
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
15049
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
15050
- const sectionId = schedulingSectionId(effectiveInsertAfter);
15619
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
15620
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
15621
+ const sectionId = schedulingSectionId(widgetId);
15051
15622
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
15052
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
15053
- if (!mountPoint) return false;
15623
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
15624
+ if (!anchorEl) return false;
15625
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
15054
15626
  const container = document.createElement("div");
15055
15627
  container.dataset.ohwSectionContainer = "scheduling";
15056
- container.dataset.ohwSection = sectionId;
15057
15628
  container.dataset.ohwInstance = sectionId;
15058
- if (insertBefore) {
15059
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
15629
+ if (beforeId) {
15630
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
15060
15631
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
15061
15632
  if (!beforePoint) return false;
15062
15633
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -15067,20 +15638,26 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
15067
15638
  }
15068
15639
  tail.insertAdjacentElement("afterend", container);
15069
15640
  }
15070
- const root = (0, import_client2.createRoot)(container);
15071
- schedulingRoots.set(container, root);
15072
- (0, import_react_dom3.flushSync)(() => {
15073
- root.render(
15074
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
15075
- SchedulingWidget,
15076
- {
15077
- notifyOnConnect,
15078
- initialScheduleId: scheduleId,
15079
- insertAfter: effectiveInsertAfter
15080
- }
15081
- )
15082
- );
15083
- });
15641
+ try {
15642
+ const root = (0, import_client2.createRoot)(container);
15643
+ schedulingRoots.set(container, root);
15644
+ (0, import_react_dom3.flushSync)(() => {
15645
+ root.render(
15646
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
15647
+ SchedulingWidget,
15648
+ {
15649
+ notifyOnConnect,
15650
+ initialScheduleId: scheduleId,
15651
+ insertAfter: widgetId
15652
+ }
15653
+ )
15654
+ );
15655
+ });
15656
+ } catch (err) {
15657
+ console.error("[ow:scheduling] render threw", err);
15658
+ container.remove();
15659
+ return false;
15660
+ }
15084
15661
  const tracker = getSectionsTracker();
15085
15662
  let sections = [];
15086
15663
  try {
@@ -15088,10 +15665,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
15088
15665
  } catch {
15089
15666
  }
15090
15667
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
15091
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
15668
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
15092
15669
  sections.push({
15093
15670
  type: "scheduling",
15094
- insertAfter: effectiveInsertAfter,
15671
+ insertAfter: widgetId,
15672
+ anchorId,
15673
+ beforeId: beforeId ?? null,
15095
15674
  pagePath: window.location.pathname,
15096
15675
  ...scheduleId ? { scheduleId } : {}
15097
15676
  });
@@ -15105,7 +15684,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
15105
15684
  for (let i = pending.length - 1; i >= 0; i--) {
15106
15685
  const entry = pending[i];
15107
15686
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
15108
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId ?? null)) {
15687
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
15688
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
15109
15689
  pending.splice(i, 1);
15110
15690
  }
15111
15691
  }
@@ -15197,7 +15777,7 @@ var EDITOR_CHROME_SELECTOR = '[data-ohw-toolbar], [data-ohw-form-toolbar], [data
15197
15777
  function isOverEditorChrome(x, y) {
15198
15778
  return document.elementsFromPoint(x, y).some((el) => el instanceof HTMLElement && el.matches(EDITOR_CHROME_SELECTOR));
15199
15779
  }
15200
- 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"])';
15780
+ 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"])';
15201
15781
  function getVideoEl2(el) {
15202
15782
  return el instanceof HTMLVideoElement ? el : el.querySelector("video");
15203
15783
  }
@@ -15253,6 +15833,12 @@ function applyVideoSettingNode(key, val) {
15253
15833
  });
15254
15834
  return true;
15255
15835
  }
15836
+ function applyMapQuery(el, val) {
15837
+ if (!(el instanceof HTMLIFrameElement)) return;
15838
+ const nextSrc = `https://www.google.com/maps?q=${encodeURIComponent(val)}&output=embed`;
15839
+ if (el.src !== nextSrc) el.src = nextSrc;
15840
+ el.setAttribute("data-ohw-map-query", val);
15841
+ }
15256
15842
  function applyLinkByKey(key, val) {
15257
15843
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
15258
15844
  if (el.dataset.ohwEditable === "link") applyLinkHref(el, val);
@@ -15263,6 +15849,11 @@ function applyLinkByKey(key, val) {
15263
15849
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
15264
15850
  }
15265
15851
  }
15852
+ function isInsideLinkEditor(target) {
15853
+ return Boolean(
15854
+ 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"]')
15855
+ );
15856
+ }
15266
15857
  function isInsideFloatingPanel(target) {
15267
15858
  return Boolean(target.closest("[data-ohw-floating-panel]"));
15268
15859
  }
@@ -15270,11 +15861,6 @@ function isPointOverFloatingPanel(clientX, clientY) {
15270
15861
  const el = document.elementFromPoint(clientX, clientY);
15271
15862
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
15272
15863
  }
15273
- function isInsideLinkEditor(target) {
15274
- return Boolean(
15275
- 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"]')
15276
- );
15277
- }
15278
15864
  function getHrefKeyFromElement(el) {
15279
15865
  if (!el) return null;
15280
15866
  const anchor = el.closest("[data-ohw-href-key]");
@@ -15533,7 +16119,7 @@ function getNavigationSelectionParent(el) {
15533
16119
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
15534
16120
  return getFooterLinksContainer();
15535
16121
  }
15536
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
16122
+ 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)) {
15537
16123
  return getNavigationRoot(el);
15538
16124
  }
15539
16125
  return null;
@@ -15748,7 +16334,6 @@ var ICONS = {
15748
16334
  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"/>',
15749
16335
  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"/>'
15750
16336
  };
15751
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
15752
16337
  var SELECTION_CHROME_GAP2 = 4;
15753
16338
  var TOOLBAR_STROKE_GAP2 = 4;
15754
16339
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -16128,6 +16713,7 @@ function StateToggle({
16128
16713
  );
16129
16714
  }
16130
16715
  var contentCache = /* @__PURE__ */ new Map();
16716
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
16131
16717
  var brandingCache = /* @__PURE__ */ new Map();
16132
16718
  var OHW_LOADER_STYLE = {
16133
16719
  position: "fixed",
@@ -16657,13 +17243,6 @@ function OhhwellsBridge() {
16657
17243
  const [isItemDragging, setIsItemDragging] = (0, import_react17.useState)(false);
16658
17244
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react17.useState)(false);
16659
17245
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
16660
- const [floatingPanel, setFloatingPanel] = (0, import_react17.useState)(null);
16661
- const floatingPanelOpenRef = (0, import_react17.useRef)(false);
16662
- floatingPanelOpenRef.current = floatingPanel !== null;
16663
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react17.useState)(null);
16664
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react17.useState)(null);
16665
- const [editorViewport, setEditorViewport] = (0, import_react17.useState)("desktop");
16666
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react17.useState)(null);
16667
17246
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react17.useState)(null);
16668
17247
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react17.useState)(null);
16669
17248
  const footerDragRef = (0, import_react17.useRef)(null);
@@ -16681,6 +17260,13 @@ function OhhwellsBridge() {
16681
17260
  const brandKitRef = (0, import_react17.useRef)("");
16682
17261
  const stylesRef = (0, import_react17.useRef)("");
16683
17262
  const pendingDeleteUndoRef = (0, import_react17.useRef)(null);
17263
+ const [floatingPanel, setFloatingPanel] = (0, import_react17.useState)(null);
17264
+ const floatingPanelOpenRef = (0, import_react17.useRef)(false);
17265
+ const setFloatingPanelRef = (0, import_react17.useRef)(setFloatingPanel);
17266
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react17.useState)(null);
17267
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react17.useState)(null);
17268
+ const [editorViewport, setEditorViewport] = (0, import_react17.useState)("desktop");
17269
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react17.useState)(null);
16684
17270
  const [sitePages, setSitePages] = (0, import_react17.useState)([]);
16685
17271
  const [sectionsByPath, setSectionsByPath] = (0, import_react17.useState)({});
16686
17272
  const sectionsPrefetchGenRef = (0, import_react17.useRef)(0);
@@ -16689,7 +17275,18 @@ function OhhwellsBridge() {
16689
17275
  const linkPopoverOpenRef = (0, import_react17.useRef)(false);
16690
17276
  const linkPopoverGraceUntilRef = (0, import_react17.useRef)(0);
16691
17277
  setLinkPopoverRef.current = setLinkPopover;
17278
+ setFloatingPanelRef.current = setFloatingPanel;
16692
17279
  linkPopoverSessionRef.current = linkPopover;
17280
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
17281
+ (0, import_react17.useEffect)(() => {
17282
+ const syncViewport = () => {
17283
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
17284
+ setEditorViewport((prev) => prev === next ? prev : next);
17285
+ };
17286
+ syncViewport();
17287
+ window.addEventListener("resize", syncViewport);
17288
+ return () => window.removeEventListener("resize", syncViewport);
17289
+ }, []);
16693
17290
  const {
16694
17291
  navDragRef,
16695
17292
  navDropSlots,
@@ -18014,6 +18611,7 @@ function OhhwellsBridge() {
18014
18611
  }
18015
18612
  if (typeof content[STYLE_STORE_KEY] === "string") {
18016
18613
  stylesRef.current = content[STYLE_STORE_KEY];
18614
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
18017
18615
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
18018
18616
  }
18019
18617
  applyBrandChrome(content);
@@ -18021,11 +18619,11 @@ function OhhwellsBridge() {
18021
18619
  for (const [key, val] of Object.entries(content)) {
18022
18620
  if (key === "__ohw_sections") continue;
18023
18621
  if (key === AI_SECTIONS_KEY) continue;
18622
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
18623
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
18024
18624
  if (key === BRAND_KIT_KEY) continue;
18025
18625
  if (key === STYLE_STORE_KEY) continue;
18026
18626
  if (BRAND_CHROME_KEYS.has(key)) continue;
18027
- if (key === LOGO_PLACEHOLDER_KEY) continue;
18028
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
18029
18627
  if (applyVideoSettingNode(key, val)) continue;
18030
18628
  if (applyCarouselNode(key, val)) continue;
18031
18629
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -18053,6 +18651,8 @@ function OhhwellsBridge() {
18053
18651
  }
18054
18652
  } else if (el.dataset.ohwEditable === "link") {
18055
18653
  applyLinkHref(el, val);
18654
+ } else if (el.dataset.ohwEditable === "map") {
18655
+ applyMapQuery(el, val);
18056
18656
  } else if (el.dataset.ohwEditable === "icon") {
18057
18657
  applyIconMarkup(el, val);
18058
18658
  } else if (el.dataset.ohwEditable === "form") {
@@ -18091,7 +18691,9 @@ function OhhwellsBridge() {
18091
18691
  let cancelled = false;
18092
18692
  setFetchState("loading");
18093
18693
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
18094
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18694
+ const initialPath = pathname;
18695
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
18696
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18095
18697
  if (cancelled) return;
18096
18698
  const content = data?.content ?? {};
18097
18699
  const branding = Boolean(data?.showBranding);
@@ -18210,10 +18812,10 @@ function OhhwellsBridge() {
18210
18812
  const applyFromCache = () => {
18211
18813
  const content = contentCache.get(subdomain);
18212
18814
  if (!content) return;
18213
- retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
18214
- initSectionInstancesFromContent(content, window.location.pathname);
18215
18815
  observer?.disconnect();
18216
18816
  try {
18817
+ retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
18818
+ initSectionInstancesFromContent(content, window.location.pathname);
18217
18819
  applyBrandChrome(content);
18218
18820
  if (typeof content[BRAND_KIT_KEY] === "string") {
18219
18821
  applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
@@ -18225,16 +18827,17 @@ function OhhwellsBridge() {
18225
18827
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
18226
18828
  }
18227
18829
  if (typeof content[STYLE_STORE_KEY] === "string") {
18830
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
18228
18831
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
18229
18832
  }
18230
18833
  for (const [key, val] of Object.entries(content)) {
18231
18834
  if (key === "__ohw_sections") continue;
18232
18835
  if (key === AI_SECTIONS_KEY) continue;
18836
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
18837
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
18233
18838
  if (key === BRAND_KIT_KEY) continue;
18234
18839
  if (key === STYLE_STORE_KEY) continue;
18235
18840
  if (BRAND_CHROME_KEYS.has(key)) continue;
18236
- if (key === LOGO_PLACEHOLDER_KEY) continue;
18237
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
18238
18841
  if (applyVideoSettingNode(key, val)) continue;
18239
18842
  if (applyCarouselNode(key, val)) continue;
18240
18843
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -18249,6 +18852,8 @@ function OhhwellsBridge() {
18249
18852
  if (video && video.src !== val) applyVideoSrc(video, val);
18250
18853
  } else if (el.dataset.ohwEditable === "link") {
18251
18854
  applyLinkHref(el, val);
18855
+ } else if (el.dataset.ohwEditable === "map") {
18856
+ applyMapQuery(el, val);
18252
18857
  } else if (el.dataset.ohwEditable === "form") {
18253
18858
  } else if (isIconMarkupValue(val)) {
18254
18859
  } else if (el.innerHTML !== val) {
@@ -18280,6 +18885,17 @@ function OhhwellsBridge() {
18280
18885
  debounceTimer = setTimeout(applyFromCache, 150);
18281
18886
  };
18282
18887
  applyFromCache();
18888
+ const pathCacheKey = `${subdomain}::${pathname}`;
18889
+ if (!fetchedContentPaths.has(pathCacheKey)) {
18890
+ fetchedContentPaths.add(pathCacheKey);
18891
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
18892
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18893
+ if (!data?.content) return;
18894
+ contentCache.set(subdomain, data.content);
18895
+ applyFromCache();
18896
+ }).catch(() => {
18897
+ });
18898
+ }
18283
18899
  observer = new MutationObserver(scheduleApply);
18284
18900
  observer.observe(document.body, { childList: true, subtree: true });
18285
18901
  return () => {
@@ -18304,6 +18920,10 @@ function OhhwellsBridge() {
18304
18920
  deselectRef.current();
18305
18921
  deactivateRef.current();
18306
18922
  }, [pathname, isEditMode]);
18923
+ (0, import_react17.useEffect)(() => {
18924
+ if (!isEditMode) return;
18925
+ initSectionInstancesFromContent(editContentRef.current, pathname);
18926
+ }, [pathname, isEditMode]);
18307
18927
  (0, import_react17.useEffect)(() => {
18308
18928
  const contentForNav = () => {
18309
18929
  if (isEditMode) return editContentRef.current;
@@ -18395,26 +19015,11 @@ function OhhwellsBridge() {
18395
19015
  const t2 = setTimeout(measure, 500);
18396
19016
  const ro = new ResizeObserver(schedule);
18397
19017
  ro.observe(document.body);
18398
- let lastWidth = window.innerWidth;
18399
- let resizeTimers = [];
18400
- const clearResizeTimers = () => {
18401
- resizeTimers.forEach(clearTimeout);
18402
- resizeTimers = [];
18403
- };
18404
- const handleResize = () => {
18405
- if (window.innerWidth === lastWidth) return;
18406
- lastWidth = window.innerWidth;
18407
- clearResizeTimers();
18408
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
18409
- };
18410
- window.addEventListener("resize", handleResize);
18411
19018
  return () => {
18412
19019
  clearTimeout(t1);
18413
19020
  clearTimeout(t2);
18414
19021
  if (raf != null) cancelAnimationFrame(raf);
18415
19022
  ro.disconnect();
18416
- clearResizeTimers();
18417
- window.removeEventListener("resize", handleResize);
18418
19023
  };
18419
19024
  }, [pathname, isEditMode, postToParent2]);
18420
19025
  (0, import_react17.useEffect)(() => {
@@ -18669,9 +19274,6 @@ function OhhwellsBridge() {
18669
19274
  if (target.closest("[data-ohw-state-toggle]")) return;
18670
19275
  if (target.closest("[data-ohw-max-badge]")) return;
18671
19276
  if (isInsideLinkEditor(target)) return;
18672
- if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
18673
- clearMediaSelectionRef.current();
18674
- }
18675
19277
  if (isInsideFloatingPanel(target)) return;
18676
19278
  if (target.closest("[data-ohw-form-toolbar]")) return;
18677
19279
  if (target.closest(
@@ -18679,6 +19281,9 @@ function OhhwellsBridge() {
18679
19281
  )) {
18680
19282
  return;
18681
19283
  }
19284
+ if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
19285
+ clearMediaSelectionRef.current();
19286
+ }
18682
19287
  {
18683
19288
  const formEl = getFormElement(target);
18684
19289
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -18830,14 +19435,6 @@ function OhhwellsBridge() {
18830
19435
  }
18831
19436
  const clickedButton = findClosestButtonLike(target);
18832
19437
  const buttonOnMedia = Boolean(clickedButton && isMediaEditable(editable) && editable.contains(clickedButton));
18833
- console.log("[click-debug]", {
18834
- editableType: editable.dataset.ohwEditable,
18835
- editableTag: editable.tagName,
18836
- targetTag: target.tagName,
18837
- clickedButtonTag: clickedButton?.tagName ?? null,
18838
- buttonOnMedia,
18839
- isMediaEditableEditable: isMediaEditable(editable)
18840
- });
18841
19438
  if (isMediaEditable(editable) && !buttonOnMedia) {
18842
19439
  e.preventDefault();
18843
19440
  e.stopPropagation();
@@ -18864,11 +19461,6 @@ function OhhwellsBridge() {
18864
19461
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
18865
19462
  const hrefCtx = getHrefKeyFromElement(hrefLookupTarget);
18866
19463
  const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
18867
- console.log("[click-debug 2]", {
18868
- hrefLookupTargetTag: hrefLookupTarget.tagName,
18869
- hrefCtx: hrefCtx ? { key: hrefCtx.key } : null,
18870
- navAnchorTag: navAnchor?.tagName ?? null
18871
- });
18872
19464
  if (navAnchor) {
18873
19465
  e.preventDefault();
18874
19466
  e.stopPropagation();
@@ -19038,6 +19630,9 @@ function OhhwellsBridge() {
19038
19630
  setHoveredItemRect(null);
19039
19631
  hoveredNavContainerRef.current = null;
19040
19632
  setHoveredNavContainerRect(null);
19633
+ siblingHintElRef.current = null;
19634
+ setSiblingHintRect(null);
19635
+ setSiblingHintRects([]);
19041
19636
  return;
19042
19637
  }
19043
19638
  {
@@ -19156,7 +19751,6 @@ function OhhwellsBridge() {
19156
19751
  hoveredNavContainerRef.current = null;
19157
19752
  setHoveredNavContainerRect(null);
19158
19753
  hoveredItemElRef.current = editable;
19159
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
19160
19754
  }
19161
19755
  }
19162
19756
  }
@@ -19453,7 +20047,7 @@ function OhhwellsBridge() {
19453
20047
  }
19454
20048
  };
19455
20049
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
19456
- if (linkPopoverOpenRef.current) {
20050
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
19457
20051
  if (hoveredImageRef.current) {
19458
20052
  hoveredImageRef.current = null;
19459
20053
  hoveredImageHasTextOverlapRef.current = false;
@@ -19818,8 +20412,7 @@ function OhhwellsBridge() {
19818
20412
  };
19819
20413
  const handleMouseMove = (e) => {
19820
20414
  const { clientX, clientY } = e;
19821
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
19822
- if (isOverEditorChrome(clientX, clientY)) {
20415
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
19823
20416
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
19824
20417
  formHoverElRef.current = null;
19825
20418
  setFormHoverRect(null);
@@ -19827,6 +20420,12 @@ function OhhwellsBridge() {
19827
20420
  setHoveredItemRect(null);
19828
20421
  hoveredNavContainerRef.current = null;
19829
20422
  setHoveredNavContainerRect(null);
20423
+ siblingHintElRef.current = null;
20424
+ setSiblingHintRect(null);
20425
+ setSiblingHintRects([]);
20426
+ dismissImageHover();
20427
+ clearImageHover();
20428
+ setSectionGap(null);
19830
20429
  return;
19831
20430
  }
19832
20431
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -19838,7 +20437,11 @@ function OhhwellsBridge() {
19838
20437
  if (e.data?.type !== "ow:pointer-sync") return;
19839
20438
  const { clientX, clientY } = e.data;
19840
20439
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
19841
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
20440
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
20441
+ dismissImageHover();
20442
+ clearImageHover();
20443
+ return;
20444
+ }
19842
20445
  if (probeSocialsRowAt(clientX, clientY)) return;
19843
20446
  probeSectionGapAt(clientX, clientY);
19844
20447
  probeImageAt(clientX, clientY);
@@ -20117,6 +20720,44 @@ function OhhwellsBridge() {
20117
20720
  if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
20118
20721
  }, 400));
20119
20722
  };
20723
+ const reapCommittedAiSections = (excludeIds) => {
20724
+ const aiState = parseAiSectionsState(aiSectionsRef.current);
20725
+ if (aiState.sections.length === 0) return [];
20726
+ let orderEntries = [];
20727
+ try {
20728
+ const parsed = JSON.parse(editContentRef.current[SECTION_ORDER_KEY] ?? "[]");
20729
+ if (Array.isArray(parsed)) orderEntries = parsed;
20730
+ } catch {
20731
+ return [];
20732
+ }
20733
+ const removedIds = orderEntries.filter((entry) => entry && typeof entry.instanceId === "string" && entry.removed).map((entry) => entry.instanceId);
20734
+ if (removedIds.length === 0) return [];
20735
+ const result = reapRemovedAiSections(aiState, parseStyleStore(stylesRef.current), removedIds, excludeIds);
20736
+ if (!result.changed) return [];
20737
+ const nodes = [];
20738
+ aiSectionsRef.current = serializeAiSectionsState(result.state);
20739
+ nodes.push({ key: AI_SECTIONS_KEY, text: aiSectionsRef.current });
20740
+ const reaped = new Set(result.reapedIds);
20741
+ const nextOrderJson = JSON.stringify(orderEntries.filter((entry) => !reaped.has(entry.instanceId)));
20742
+ editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: nextOrderJson };
20743
+ setAiSectionOrder(nextOrderJson, window.location.pathname);
20744
+ nodes.push({ key: SECTION_ORDER_KEY, text: nextOrderJson });
20745
+ if (result.store) {
20746
+ stylesRef.current = JSON.stringify(result.store);
20747
+ nodes.push({ key: STYLE_STORE_KEY, text: stylesRef.current });
20748
+ }
20749
+ const nextContent = { ...editContentRef.current };
20750
+ for (const key of Object.keys(nextContent)) {
20751
+ if (key.startsWith(AI_SLOT_KEY_PREFIX) && result.slotPrefixes.some((prefix) => key.startsWith(prefix)) && nextContent[key] !== "") {
20752
+ nextContent[key] = "";
20753
+ nodes.push({ key, text: "" });
20754
+ }
20755
+ }
20756
+ editContentRef.current = nextContent;
20757
+ applyAiSectionsToDom(result.state);
20758
+ applyStylesToDom(parseStyleStore(stylesRef.current));
20759
+ return nodes;
20760
+ };
20120
20761
  const handleHydrate = (e) => {
20121
20762
  if (e.data?.type !== "ow:hydrate") return;
20122
20763
  const content = e.data.content;
@@ -20135,6 +20776,7 @@ function OhhwellsBridge() {
20135
20776
  }
20136
20777
  if (typeof content[STYLE_STORE_KEY] === "string") {
20137
20778
  stylesRef.current = content[STYLE_STORE_KEY];
20779
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
20138
20780
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
20139
20781
  }
20140
20782
  applyBrandChrome(content);
@@ -20146,11 +20788,11 @@ function OhhwellsBridge() {
20146
20788
  continue;
20147
20789
  }
20148
20790
  if (key === AI_SECTIONS_KEY) continue;
20791
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
20792
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
20149
20793
  if (key === BRAND_KIT_KEY) continue;
20150
20794
  if (key === STYLE_STORE_KEY) continue;
20151
20795
  if (BRAND_CHROME_KEYS.has(key)) continue;
20152
- if (key === LOGO_PLACEHOLDER_KEY) continue;
20153
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
20154
20796
  if (applyVideoSettingNode(key, val)) continue;
20155
20797
  if (applyCarouselNode(key, val)) continue;
20156
20798
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -20164,6 +20806,8 @@ function OhhwellsBridge() {
20164
20806
  if (video && video.src !== val) applyVideoSrc(video, val);
20165
20807
  } else if (el.dataset.ohwEditable === "link") {
20166
20808
  applyLinkHref(el, val);
20809
+ } else if (el.dataset.ohwEditable === "map") {
20810
+ applyMapQuery(el, val);
20167
20811
  } else if (el.dataset.ohwEditable === "icon") {
20168
20812
  applyIconMarkup(el, val);
20169
20813
  } else if (isIconMarkupValue(val)) {
@@ -20185,6 +20829,11 @@ function OhhwellsBridge() {
20185
20829
  reconcileFooterOrderFromContent(editContentRef.current);
20186
20830
  syncNavigationDragCursorAttrs();
20187
20831
  enforceLinkHrefs();
20832
+ const hydrateReapExclude = /* @__PURE__ */ new Set();
20833
+ const hydratePendingUndo = pendingDeleteUndoRef.current;
20834
+ if (hydratePendingUndo?.sectionInstanceId) hydrateReapExclude.add(hydratePendingUndo.sectionInstanceId);
20835
+ const reapNodes = reapCommittedAiSections(hydrateReapExclude);
20836
+ if (reapNodes.length > 0) postToParentRef.current({ type: "ow:change", nodes: reapNodes });
20188
20837
  const hydratedHeight = document.body.scrollHeight;
20189
20838
  if (hydratedHeight > 50) postToParentRef.current({ type: "ow:height", height: hydratedHeight });
20190
20839
  postToParentRef.current({ type: "ow:hydrate-done" });
@@ -20324,12 +20973,35 @@ function OhhwellsBridge() {
20324
20973
  window.addEventListener("message", handleAiSetBrand);
20325
20974
  const handleAiSetStyles = (e) => {
20326
20975
  if (e.data?.type !== "ow:ai-set-styles") return;
20327
- const value = typeof e.data.value === "string" ? e.data.value : "";
20976
+ let value = typeof e.data.value === "string" ? e.data.value : "";
20328
20977
  const previous = stylesRef.current;
20978
+ let previousSections;
20979
+ const store = parseStyleStore(value);
20980
+ if (store) {
20981
+ const folded = foldAlignIntoTrees(parseAiSectionsState(aiSectionsRef.current), store);
20982
+ if (folded.changed) {
20983
+ const nextSections = serializeAiSectionsState(folded.state);
20984
+ if (nextSections !== aiSectionsRef.current) {
20985
+ previousSections = aiSectionsRef.current;
20986
+ aiSectionsRef.current = nextSections;
20987
+ applyAiSectionsToDom(folded.state);
20988
+ postToParentRef.current({
20989
+ type: "ow:change",
20990
+ nodes: [{ key: AI_SECTIONS_KEY, text: nextSections }]
20991
+ });
20992
+ }
20993
+ value = JSON.stringify(folded.store);
20994
+ }
20995
+ }
20329
20996
  stylesRef.current = value;
20330
20997
  applyStylesToDom(parseStyleStore(value));
20331
20998
  postToParentRef.current({ type: "ow:change", nodes: [{ key: STYLE_STORE_KEY, text: value }] });
20332
- postToParentRef.current({ type: "ow:ai-styles-applied", previous, value });
20999
+ postToParentRef.current({
21000
+ type: "ow:ai-styles-applied",
21001
+ previous,
21002
+ value,
21003
+ ...previousSections !== void 0 ? { previousSections } : {}
21004
+ });
20333
21005
  };
20334
21006
  window.addEventListener("message", handleAiSetStyles);
20335
21007
  const handleGetBrand = (e) => {
@@ -20346,8 +21018,11 @@ function OhhwellsBridge() {
20346
21018
  if (!instanceId || !direction) return;
20347
21019
  const entries = moveSectionInstance(instanceId, direction, window.location.pathname);
20348
21020
  if (!entries) return;
20349
- const orderJson = JSON.stringify(entries);
21021
+ const orderJson = JSON.stringify(
21022
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
21023
+ );
20350
21024
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
21025
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
20351
21026
  setAiSectionOrder(orderJson, window.location.pathname);
20352
21027
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20353
21028
  window.dispatchEvent(new Event("resize"));
@@ -20393,8 +21068,11 @@ function OhhwellsBridge() {
20393
21068
  const currentEntries = getPageSectionOrderEntries(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname);
20394
21069
  const entries = deleteSectionInstance(instanceId, window.location.pathname, currentEntries);
20395
21070
  if (!entries) return;
20396
- const orderJson = JSON.stringify(entries);
21071
+ const orderJson = JSON.stringify(
21072
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
21073
+ );
20397
21074
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
21075
+ setAiSectionOrder(orderJson, window.location.pathname);
20398
21076
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20399
21077
  aiSectionApiRef.current?.clear();
20400
21078
  window.dispatchEvent(new Event("resize"));
@@ -20403,6 +21081,7 @@ function OhhwellsBridge() {
20403
21081
  const actionId = newInstanceId();
20404
21082
  pendingDeleteUndoRef.current = {
20405
21083
  actionId,
21084
+ sectionInstanceId: instanceId,
20406
21085
  restore: () => {
20407
21086
  const restoredEntries = getPageSectionOrderEntries(
20408
21087
  editContentRef.current[SECTION_ORDER_KEY],
@@ -20410,8 +21089,11 @@ function OhhwellsBridge() {
20410
21089
  );
20411
21090
  const restored = restoreSectionInstance(instanceId, window.location.pathname, restoredEntries);
20412
21091
  if (!restored) return;
20413
- const restoredJson = JSON.stringify(restored);
21092
+ const restoredJson = JSON.stringify(
21093
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, restored)
21094
+ );
20414
21095
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: restoredJson };
21096
+ setAiSectionOrder(restoredJson, window.location.pathname);
20415
21097
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: restoredJson }] });
20416
21098
  window.dispatchEvent(new Event("resize"));
20417
21099
  const restoreHeight = document.body.scrollHeight;
@@ -20437,7 +21119,9 @@ function OhhwellsBridge() {
20437
21119
  const result = duplicateSectionInstance(instanceId, newId, window.location.pathname, currentEntries);
20438
21120
  if (!result) return;
20439
21121
  const { entries, keyRekeys } = result;
20440
- const orderJson = JSON.stringify(entries);
21122
+ const orderJson = JSON.stringify(
21123
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
21124
+ );
20441
21125
  const nodes = [{ key: SECTION_ORDER_KEY, text: orderJson }];
20442
21126
  for (const { from, to } of keyRekeys) {
20443
21127
  const inherited = editContentRef.current[from];
@@ -20447,6 +21131,7 @@ function OhhwellsBridge() {
20447
21131
  ...editContentRef.current,
20448
21132
  ...Object.fromEntries(nodes.map(({ key, text }) => [key, text]))
20449
21133
  };
21134
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
20450
21135
  setAiSectionOrder(orderJson, window.location.pathname);
20451
21136
  postToParentRef.current({ type: "ow:change", nodes });
20452
21137
  window.dispatchEvent(new Event("resize"));
@@ -20465,6 +21150,12 @@ function OhhwellsBridge() {
20465
21150
  closeLinkPopoverRef.current();
20466
21151
  return;
20467
21152
  }
21153
+ if (floatingPanelOpenRef.current) {
21154
+ setFloatingPanelRef.current(null);
21155
+ deselectRef.current();
21156
+ deactivateRef.current();
21157
+ return;
21158
+ }
20468
21159
  deselectRef.current();
20469
21160
  deactivateRef.current();
20470
21161
  clearMediaSelectionRef.current();
@@ -20710,6 +21401,10 @@ function OhhwellsBridge() {
20710
21401
  };
20711
21402
  const handleSave = (e) => {
20712
21403
  if (e.data?.type !== "ow:save") return;
21404
+ const pendingUndo = pendingDeleteUndoRef.current;
21405
+ const reapExclude = /* @__PURE__ */ new Set();
21406
+ if (pendingUndo?.sectionInstanceId) reapExclude.add(pendingUndo.sectionInstanceId);
21407
+ const reapNodes = reapCommittedAiSections(reapExclude);
20713
21408
  const nodes = collectEditableNodes(editContentRef.current);
20714
21409
  const tracker = document.querySelector("[data-ohw-sections-tracker]");
20715
21410
  if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
@@ -20729,6 +21424,11 @@ function OhhwellsBridge() {
20729
21424
  const text = specs.length ? JSON.stringify(specs) : editContentRef.current[fieldsKey(formKey)];
20730
21425
  if (text) nodes.push({ key: fieldsKey(formKey), type: "text", text });
20731
21426
  });
21427
+ for (const reapNode of reapNodes) {
21428
+ if (reapNode.key.startsWith(AI_SLOT_KEY_PREFIX) && !nodes.some((n) => n.key === reapNode.key)) {
21429
+ nodes.push({ key: reapNode.key, type: "text", text: reapNode.text });
21430
+ }
21431
+ }
20732
21432
  postToParentRef.current({ type: "ow:save-result", nodes });
20733
21433
  };
20734
21434
  const handleInsertSection = (e) => {
@@ -20739,8 +21439,12 @@ function OhhwellsBridge() {
20739
21439
  if (inserted) {
20740
21440
  const tracker = getSectionsTracker();
20741
21441
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
20742
- const h = document.body.scrollHeight;
20743
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
21442
+ const reportHeight = () => {
21443
+ const h = document.body.scrollHeight;
21444
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
21445
+ };
21446
+ reportHeight();
21447
+ setTimeout(reportHeight, 500);
20744
21448
  }
20745
21449
  };
20746
21450
  const handleSwitchSchedule = (e) => {
@@ -21144,10 +21848,10 @@ function OhhwellsBridge() {
21144
21848
  window.removeEventListener("message", handleDeleteSection);
21145
21849
  window.removeEventListener("message", handleDuplicateSection);
21146
21850
  window.removeEventListener("message", handleDeactivate);
21147
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
21148
21851
  window.removeEventListener("message", handleToastAction);
21149
21852
  window.removeEventListener("message", handleFormCount);
21150
21853
  window.removeEventListener("message", handleUiEscape);
21854
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
21151
21855
  autoSaveTimers.current.forEach(clearTimeout);
21152
21856
  autoSaveTimers.current.clear();
21153
21857
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -21350,7 +22054,7 @@ function OhhwellsBridge() {
21350
22054
  postToParent2({
21351
22055
  type: "ow:ready",
21352
22056
  version: "1",
21353
- bridgeVersion: "0.1.90",
22057
+ bridgeVersion: "0.1.92",
21354
22058
  path: pathname,
21355
22059
  nodes: collectEditableNodes(editContentRef.current),
21356
22060
  sections
@@ -22269,6 +22973,59 @@ function OhhwellsBridge() {
22269
22973
  ) : null
22270
22974
  ] });
22271
22975
  }
22976
+
22977
+ // src/ui/EmptySection.tsx
22978
+ var import_link = __toESM(require("next/link"), 1);
22979
+ var import_jsx_runtime34 = require("react/jsx-runtime");
22980
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
22981
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
22982
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
22983
+ "p",
22984
+ {
22985
+ style: {
22986
+ fontFamily: "var(--brand-font-body)",
22987
+ fontSize: "0.75rem",
22988
+ fontWeight: 500,
22989
+ letterSpacing: "0.15em",
22990
+ textTransform: "uppercase",
22991
+ color: "var(--brand-accent)",
22992
+ marginBottom: "1.5rem"
22993
+ },
22994
+ children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_link.default, { href: homeHref, style: { color: "inherit" }, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { ...eyebrowKey ? { "data-ohw-editable": "text", "data-ohw-key": eyebrowKey } : {}, children: "Home" }) })
22995
+ }
22996
+ ),
22997
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
22998
+ "h1",
22999
+ {
23000
+ style: {
23001
+ fontFamily: "var(--brand-font-heading)",
23002
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
23003
+ lineHeight: 1.1,
23004
+ letterSpacing: "-0.025em",
23005
+ color: "var(--brand-text)",
23006
+ marginBottom: "1rem"
23007
+ },
23008
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
23009
+ children: title
23010
+ }
23011
+ ),
23012
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
23013
+ "p",
23014
+ {
23015
+ style: {
23016
+ fontFamily: "var(--brand-font-body)",
23017
+ fontSize: "1rem",
23018
+ lineHeight: 1.7,
23019
+ fontWeight: 300,
23020
+ color: "var(--brand-text-muted)",
23021
+ maxWidth: "340px"
23022
+ },
23023
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
23024
+ children: "This page doesn't have any content yet."
23025
+ }
23026
+ )
23027
+ ] });
23028
+ }
22272
23029
  // Annotate the CommonJS export names for ESM import in node:
22273
23030
  0 && (module.exports = {
22274
23031
  AI_DEFAULT_BRAND,
@@ -22286,6 +23043,7 @@ function OhhwellsBridge() {
22286
23043
  DropdownMenuItem,
22287
23044
  DropdownMenuSeparator,
22288
23045
  DropdownMenuTrigger,
23046
+ EmptySection,
22289
23047
  ItemActionToolbar,
22290
23048
  ItemInteractionLayer,
22291
23049
  LinkEditorPanel,