@ohhwells/bridge 0.1.91 → 0.1.92-next.269

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,9 +2639,13 @@ 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"]);
2645
+ function isChromeSection2(el) {
2646
+ if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) return true;
2647
+ return el.tagName === "HEADER" || el.tagName === "FOOTER";
2648
+ }
1899
2649
  function readRootVar(name) {
1900
2650
  if (typeof document === "undefined") return "";
1901
2651
  return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
@@ -1917,13 +2667,13 @@ function deriveBrandOverride() {
1917
2667
  };
1918
2668
  }
1919
2669
  function deriveTemplateBrand() {
1920
- const dark = readRootVar("--color-dark");
1921
- const primary = readRootVar("--color-primary");
1922
- const light = readRootVar("--color-light");
2670
+ const primary = readRootVar("--brand-primary") || readRootVar("--color-primary");
2671
+ const dark = readRootVar("--brand-text") || readRootVar("--color-dark");
2672
+ const light = readRootVar("--brand-background") || readRootVar("--color-light");
1923
2673
  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");
2674
+ const accent = readRootVar("--brand-accent") || readRootVar("--color-accent");
2675
+ const heading = readRootVar("--brand-font-heading") || readRootVar("--font-heading") || readRootVar("--font-display");
2676
+ const body = readRootVar("--brand-font-body") || readRootVar("--font-body");
1927
2677
  return {
1928
2678
  palette: { dark, primary, accent: accent || dark, light },
1929
2679
  fonts: {
@@ -1932,12 +2682,35 @@ function deriveTemplateBrand() {
1932
2682
  }
1933
2683
  };
1934
2684
  }
1935
- function deriveTemplateButtonRadius() {
2685
+ function deriveTemplateButtonStyle() {
1936
2686
  if (typeof document === "undefined") return null;
1937
- const btn = document.querySelector('[data-ohw-role="button"]');
2687
+ const btn = Array.from(document.querySelectorAll('[data-ohw-role="button"]')).find(
2688
+ (el) => !el.closest(`[${CONTAINER_ATTR}]`)
2689
+ );
1938
2690
  if (!btn) return null;
1939
- const radius = getComputedStyle(btn).borderTopLeftRadius;
1940
- return radius || null;
2691
+ const cs = getComputedStyle(btn);
2692
+ const corners = [
2693
+ cs.borderTopLeftRadius,
2694
+ cs.borderTopRightRadius,
2695
+ cs.borderBottomRightRadius,
2696
+ cs.borderBottomLeftRadius
2697
+ ].map((v) => v || "0px");
2698
+ const radius = corners.every((v) => v === corners[0]) ? corners[0] : corners.join(" ");
2699
+ const px = (v) => parseFloat(v) || 0;
2700
+ const lineHeight = px(cs.lineHeight) || px(cs.fontSize) * 1.2;
2701
+ const contentH = btn.getBoundingClientRect().height - px(cs.borderTopWidth) - px(cs.borderBottomWidth);
2702
+ const impliedY = Math.round(Math.max(0, (contentH - lineHeight) / 2));
2703
+ const padY = Math.max(px(cs.paddingTop), px(cs.paddingBottom), impliedY);
2704
+ const padX = Math.max(px(cs.paddingLeft), px(cs.paddingRight));
2705
+ return {
2706
+ radius: radius || "10px",
2707
+ padding: `${padY}px ${padX}px`,
2708
+ fontFamily: cs.fontFamily || "",
2709
+ fontSize: cs.fontSize || "",
2710
+ fontWeight: cs.fontWeight || "",
2711
+ letterSpacing: cs.letterSpacing || "",
2712
+ textTransform: cs.textTransform || ""
2713
+ };
1941
2714
  }
1942
2715
  var mounted = /* @__PURE__ */ new Map();
1943
2716
  function findTemplateSection(id) {
@@ -1953,6 +2726,19 @@ function findPlacementAnchor(id, exclude) {
1953
2726
  for (const el of document.querySelectorAll(`[data-ohw-section="${CSS.escape(id)}"]`)) {
1954
2727
  if (el === exclude) continue;
1955
2728
  if (el.parentElement?.closest("[data-ohw-section]")) continue;
2729
+ if (isChromeSection2(el)) return null;
2730
+ return el;
2731
+ }
2732
+ return null;
2733
+ }
2734
+ function findFooterSection() {
2735
+ const byId = findTemplateSection("footer");
2736
+ if (byId) return byId;
2737
+ for (const el of Array.from(
2738
+ document.querySelectorAll("footer[data-ohw-section]")
2739
+ ).reverse()) {
2740
+ if (el.hasAttribute(CONTAINER_ATTR)) continue;
2741
+ if (el.parentElement?.closest("[data-ohw-section]")) continue;
1956
2742
  return el;
1957
2743
  }
1958
2744
  return null;
@@ -1979,7 +2765,7 @@ function placeContainer(container, entry) {
1979
2765
  return;
1980
2766
  }
1981
2767
  }
1982
- const footer = findTemplateSection("footer");
2768
+ const footer = findFooterSection();
1983
2769
  if (footer) {
1984
2770
  footer.insertAdjacentElement("beforebegin", container);
1985
2771
  } else {
@@ -1988,18 +2774,18 @@ function placeContainer(container, entry) {
1988
2774
  }
1989
2775
  function syncRemovedSections(state) {
1990
2776
  const removed = new Set(state.removed ?? []);
1991
- for (const el of document.querySelectorAll(`[${REMOVED_ATTR}]`)) {
2777
+ for (const el of document.querySelectorAll(`[${REMOVED_ATTR2}]`)) {
1992
2778
  const id = el.getAttribute("data-ohw-section") ?? "";
1993
2779
  if (!removed.has(id)) {
1994
2780
  el.style.removeProperty("display");
1995
- el.removeAttribute(REMOVED_ATTR);
2781
+ el.removeAttribute(REMOVED_ATTR2);
1996
2782
  }
1997
2783
  }
1998
2784
  for (const id of removed) {
1999
2785
  const section = findTemplateSection(id);
2000
2786
  if (section && !section.hasAttribute(REPLACED_ATTR)) {
2001
2787
  section.style.display = "none";
2002
- section.setAttribute(REMOVED_ATTR, "");
2788
+ section.setAttribute(REMOVED_ATTR2, "");
2003
2789
  }
2004
2790
  }
2005
2791
  }
@@ -2014,9 +2800,9 @@ function syncTemplateHidden(state, pageHasSections) {
2014
2800
  if (!hide) return;
2015
2801
  for (const el of Array.from(document.querySelectorAll("[data-ohw-section]"))) {
2016
2802
  if (el.hasAttribute(CONTAINER_ATTR)) continue;
2017
- if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) continue;
2803
+ if (isChromeSection2(el)) continue;
2018
2804
  if (el.parentElement?.closest("[data-ohw-section]")) continue;
2019
- if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR)) continue;
2805
+ if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR2)) continue;
2020
2806
  el.style.display = "none";
2021
2807
  el.setAttribute(TEMPLATE_HIDDEN_ATTR, "");
2022
2808
  }
@@ -2040,18 +2826,23 @@ function syncReplacedOriginals(state) {
2040
2826
  }
2041
2827
  }
2042
2828
  var sectionOrderIndex = /* @__PURE__ */ new Map();
2829
+ var removedSectionIds = /* @__PURE__ */ new Set();
2043
2830
  function setAiSectionOrder(raw, currentPath) {
2044
2831
  const next = /* @__PURE__ */ new Map();
2832
+ const removed = /* @__PURE__ */ new Set();
2045
2833
  if (raw) {
2046
2834
  try {
2047
2835
  const entries = JSON.parse(raw);
2048
2836
  for (const entry of entries) {
2049
- if (!entry.pagePath || entry.pagePath === currentPath) next.set(entry.instanceId, entry.order);
2837
+ if (entry.pagePath && entry.pagePath !== currentPath) continue;
2838
+ next.set(entry.instanceId, entry.order);
2839
+ if (entry.removed) removed.add(entry.instanceId);
2050
2840
  }
2051
2841
  } catch {
2052
2842
  }
2053
2843
  }
2054
2844
  sectionOrderIndex = next;
2845
+ removedSectionIds = removed;
2055
2846
  }
2056
2847
  function applyExplicitOrder(entries) {
2057
2848
  if (sectionOrderIndex.size === 0) return entries;
@@ -2087,11 +2878,23 @@ function orderByChain(sections) {
2087
2878
  for (const root of roots) visit(root);
2088
2879
  return out.length === sections.length ? out : sections;
2089
2880
  }
2881
+ function syncSoftRemovedGenerated() {
2882
+ for (const [id, section] of mounted) {
2883
+ const el = section.container;
2884
+ if (removedSectionIds.has(id)) {
2885
+ el.style.display = "none";
2886
+ el.setAttribute(REMOVED_ATTR, "");
2887
+ } else if (el.hasAttribute(REMOVED_ATTR)) {
2888
+ el.style.removeProperty("display");
2889
+ el.removeAttribute(REMOVED_ATTR);
2890
+ }
2891
+ }
2892
+ }
2090
2893
  function applyAiSectionsToDom(state, options) {
2091
2894
  if (typeof document === "undefined") return;
2092
2895
  const brandOverride = deriveBrandOverride();
2093
2896
  const templateBrand = deriveTemplateBrand();
2094
- const templateButtonRadius = deriveTemplateButtonRadius();
2897
+ const templateButtonStyle = deriveTemplateButtonStyle();
2095
2898
  const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
2096
2899
  const pagePath = window.location.pathname;
2097
2900
  const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
@@ -2131,7 +2934,7 @@ function applyAiSectionsToDom(state, options) {
2131
2934
  {
2132
2935
  tree: entry.tree,
2133
2936
  brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
2134
- buttonRadius: templateButtonRadius,
2937
+ buttonStyle: templateButtonStyle,
2135
2938
  resolveMedia,
2136
2939
  editKeyPrefix: `ai.${entry.id}`
2137
2940
  }
@@ -2154,6 +2957,7 @@ function applyAiSectionsToDom(state, options) {
2154
2957
  syncReplacedOriginals(state);
2155
2958
  syncRemovedSections(state);
2156
2959
  syncTemplateHidden(state, pageSections.length > 0);
2960
+ syncSoftRemovedGenerated();
2157
2961
  }
2158
2962
 
2159
2963
  // src/useLinkHrefGuardian.ts
@@ -7883,6 +8687,7 @@ function MediaOverlay({
7883
8687
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
7884
8688
  );
7885
8689
  }, [isVideo]);
8690
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
7886
8691
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
7887
8692
  const box = {
7888
8693
  position: "fixed",
@@ -8012,17 +8817,17 @@ function MediaOverlay({
8012
8817
  },
8013
8818
  children: [
8014
8819
  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"
8820
+ replaceLabel
8016
8821
  ]
8017
8822
  }
8018
8823
  ),
8019
- replaceMode === "none" ? null : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8824
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8020
8825
  Button,
8021
8826
  {
8022
8827
  "data-ohw-media-overlay": "",
8023
8828
  variant: "outline",
8024
8829
  size: "sm",
8025
- "aria-label": isVideo ? "Replace video" : "Replace image",
8830
+ "aria-label": replaceLabel,
8026
8831
  className: "gap-1.5 cursor-pointer hover:bg-background",
8027
8832
  style: {
8028
8833
  ...OVERLAY_BUTTON_STYLE,
@@ -8045,7 +8850,7 @@ function MediaOverlay({
8045
8850
  },
8046
8851
  children: [
8047
8852
  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
8853
+ replaceMode === "full" ? replaceLabel : null
8049
8854
  ]
8050
8855
  }
8051
8856
  )
@@ -8099,234 +8904,22 @@ function CarouselOverlay({
8099
8904
  onClick: (e) => {
8100
8905
  e.stopPropagation();
8101
8906
  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, "");
8907
+ },
8908
+ children: [
8909
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react6.GalleryHorizontal, { size: 14 }),
8910
+ "Edit gallery"
8911
+ ]
8912
+ }
8913
+ )
8206
8914
  }
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
8915
  );
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
8916
  }
8323
8917
 
8324
8918
  // src/ui/ai-section/AiSectionOverlay.tsx
8919
+ var import_react8 = require("react");
8920
+ var import_lucide_react7 = require("lucide-react");
8325
8921
  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
- }
8922
+ var findSectionElement = findByInstanceId;
8330
8923
  function readRect(instanceId) {
8331
8924
  const el = findSectionElement(instanceId);
8332
8925
  if (!el) return null;
@@ -8366,7 +8959,7 @@ function useLiveSectionRect(sectionId) {
8366
8959
  }
8367
8960
  function computeSectionBoundaryFlags(instanceId) {
8368
8961
  const topLevel = topLevelSections();
8369
- const index = topLevel.findIndex((el) => (el.dataset.ohwInstance ?? el.dataset.ohwSection) === instanceId);
8962
+ const index = topLevel.findIndex((el) => instanceIdOf(el) === instanceId);
8370
8963
  if (index === -1) return { isFirst: true, isLast: true };
8371
8964
  return { isFirst: index === 0, isLast: index === topLevel.length - 1 };
8372
8965
  }
@@ -8450,18 +9043,20 @@ function AiSectionOverlay({
8450
9043
  selectedIdRef.current = selectedId;
8451
9044
  const report = (0, import_react8.useCallback)(
8452
9045
  (el) => {
9046
+ const labelSrc = el ? sectionElementOf(el) : null;
8453
9047
  postToParent2({
8454
9048
  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
9049
+ sectionId: el ? instanceIdOf(el) || null : null,
9050
+ sectionLabel: labelSrc ? labelSrc.dataset.ohwSectionLabel ?? titleCaseSectionId(labelSrc.dataset.ohwSection ?? "") : null
8457
9051
  });
8458
9052
  },
8459
9053
  [postToParent2]
8460
9054
  );
8461
9055
  const selectFromElement = (0, import_react8.useCallback)(
8462
9056
  (el, options) => {
8463
- const sectionEl = el?.closest("[data-ohw-section]") ?? null;
8464
- const id = sectionEl ? sectionEl.dataset.ohwInstance ?? sectionEl.dataset.ohwSection ?? null : null;
9057
+ const inner = el?.closest("[data-ohw-section]") ?? null;
9058
+ const sectionEl = inner ? movableUnit(inner) : null;
9059
+ const id = sectionEl ? instanceIdOf(sectionEl) || null : null;
8465
9060
  if (id === selectedIdRef.current) return;
8466
9061
  setSelectedId(id);
8467
9062
  if (options?.report !== false) report(sectionEl);
@@ -8527,7 +9122,8 @@ function AiSectionOverlay({
8527
9122
  return;
8528
9123
  }
8529
9124
  const sec = t.closest("[data-ohw-section]");
8530
- setHoveredId(sec ? sec.dataset.ohwInstance ?? sec.dataset.ohwSection ?? null : null);
9125
+ const unit = sec ? movableUnit(sec) : null;
9126
+ setHoveredId(unit ? instanceIdOf(unit) || null : null);
8531
9127
  };
8532
9128
  const onLeave = () => setHoveredId(null);
8533
9129
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -13038,6 +13634,7 @@ function readLogoSizeState(content, placement) {
13038
13634
  function getLogoElement(el) {
13039
13635
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
13040
13636
  if (marked) return marked;
13637
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
13041
13638
  const root = el.closest("nav, [data-ohw-nav-root], footer");
13042
13639
  if (!root) return null;
13043
13640
  const anchor = el.closest("a");
@@ -14104,15 +14701,17 @@ function useSectionDrag({
14104
14701
  clearSectionDragVisuals();
14105
14702
  return;
14106
14703
  }
14107
- const orderJson = JSON.stringify(entries);
14704
+ const orderJson = JSON.stringify(
14705
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
14706
+ );
14108
14707
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
14109
14708
  setAiSectionOrder(orderJson, window.location.pathname);
14110
14709
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
14111
- applyPersistedOrder(entries);
14710
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
14112
14711
  clearSectionDragVisuals();
14113
14712
  requestAnimationFrame(() => {
14114
14713
  if (editContentRef.current[SECTION_ORDER_KEY] === orderJson) {
14115
- applyPersistedOrder(entries);
14714
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
14116
14715
  }
14117
14716
  requestAnimationFrame(() => {
14118
14717
  window.dispatchEvent(new Event("resize"));
@@ -14145,8 +14744,9 @@ function useSectionDrag({
14145
14744
  const target = e.target;
14146
14745
  if (!(target instanceof HTMLElement)) return;
14147
14746
  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;
14747
+ const inner = target.closest("[data-ohw-section]");
14748
+ if (!inner || isChromeSection(inner) || inner.dataset.ohwSection === "footer") return;
14749
+ const sectionEl = movableUnit(inner);
14150
14750
  if (!topLevelSections().includes(sectionEl)) return;
14151
14751
  startSectionPressDrag(sectionEl, e.clientX, e.clientY, e.pointerId);
14152
14752
  };
@@ -14413,6 +15013,9 @@ function collectEditableNodes(extraContent, root = document) {
14413
15013
  if (el.dataset.ohwEditable === "link") {
14414
15014
  return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref3(el) };
14415
15015
  }
15016
+ if (el.dataset.ohwEditable === "map") {
15017
+ return { key: el.dataset.ohwKey ?? "", type: "map", text: el.dataset.ohwMapQuery ?? "" };
15018
+ }
14416
15019
  return {
14417
15020
  key: el.dataset.ohwKey ?? "",
14418
15021
  type: el.dataset.ohwEditable ?? "text",
@@ -14975,21 +15578,10 @@ function parseSchedulingInsertAfter(insertAfter) {
14975
15578
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
14976
15579
  };
14977
15580
  }
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;
15581
+ function resolveEntryAnchor(entry) {
15582
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
15583
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
15584
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
14993
15585
  }
14994
15586
  function schedulingMountDepth(insertAfter) {
14995
15587
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -15006,8 +15598,7 @@ function getPageSchedulingEntries(raw) {
15006
15598
  }
15007
15599
  }
15008
15600
  function isSchedulingWidgetMissing(entry) {
15009
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
15010
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
15601
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
15011
15602
  }
15012
15603
  function hasMissingSchedulingWidgets(entries) {
15013
15604
  return entries.some(isSchedulingWidgetMissing);
@@ -15045,18 +15636,18 @@ function initSectionsFromContent(content, removeExisting = false, currentPath =
15045
15636
  } catch {
15046
15637
  }
15047
15638
  }
15048
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
15049
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
15050
- const sectionId = schedulingSectionId(effectiveInsertAfter);
15639
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
15640
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
15641
+ const sectionId = schedulingSectionId(widgetId);
15051
15642
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
15052
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
15053
- if (!mountPoint) return false;
15643
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
15644
+ if (!anchorEl) return false;
15645
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
15054
15646
  const container = document.createElement("div");
15055
15647
  container.dataset.ohwSectionContainer = "scheduling";
15056
- container.dataset.ohwSection = sectionId;
15057
15648
  container.dataset.ohwInstance = sectionId;
15058
- if (insertBefore) {
15059
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
15649
+ if (beforeId) {
15650
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
15060
15651
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
15061
15652
  if (!beforePoint) return false;
15062
15653
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -15067,20 +15658,26 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
15067
15658
  }
15068
15659
  tail.insertAdjacentElement("afterend", container);
15069
15660
  }
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
- });
15661
+ try {
15662
+ const root = (0, import_client2.createRoot)(container);
15663
+ schedulingRoots.set(container, root);
15664
+ (0, import_react_dom3.flushSync)(() => {
15665
+ root.render(
15666
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
15667
+ SchedulingWidget,
15668
+ {
15669
+ notifyOnConnect,
15670
+ initialScheduleId: scheduleId,
15671
+ insertAfter: widgetId
15672
+ }
15673
+ )
15674
+ );
15675
+ });
15676
+ } catch (err) {
15677
+ console.error("[ow:scheduling] render threw", err);
15678
+ container.remove();
15679
+ return false;
15680
+ }
15084
15681
  const tracker = getSectionsTracker();
15085
15682
  let sections = [];
15086
15683
  try {
@@ -15088,10 +15685,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
15088
15685
  } catch {
15089
15686
  }
15090
15687
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
15091
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
15688
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
15092
15689
  sections.push({
15093
15690
  type: "scheduling",
15094
- insertAfter: effectiveInsertAfter,
15691
+ insertAfter: widgetId,
15692
+ anchorId,
15693
+ beforeId: beforeId ?? null,
15095
15694
  pagePath: window.location.pathname,
15096
15695
  ...scheduleId ? { scheduleId } : {}
15097
15696
  });
@@ -15105,7 +15704,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
15105
15704
  for (let i = pending.length - 1; i >= 0; i--) {
15106
15705
  const entry = pending[i];
15107
15706
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
15108
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId ?? null)) {
15707
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
15708
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
15109
15709
  pending.splice(i, 1);
15110
15710
  }
15111
15711
  }
@@ -15197,7 +15797,7 @@ var EDITOR_CHROME_SELECTOR = '[data-ohw-toolbar], [data-ohw-form-toolbar], [data
15197
15797
  function isOverEditorChrome(x, y) {
15198
15798
  return document.elementsFromPoint(x, y).some((el) => el instanceof HTMLElement && el.matches(EDITOR_CHROME_SELECTOR));
15199
15799
  }
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"])';
15800
+ 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
15801
  function getVideoEl2(el) {
15202
15802
  return el instanceof HTMLVideoElement ? el : el.querySelector("video");
15203
15803
  }
@@ -15253,6 +15853,12 @@ function applyVideoSettingNode(key, val) {
15253
15853
  });
15254
15854
  return true;
15255
15855
  }
15856
+ function applyMapQuery(el, val) {
15857
+ if (!(el instanceof HTMLIFrameElement)) return;
15858
+ const nextSrc = `https://www.google.com/maps?q=${encodeURIComponent(val)}&output=embed`;
15859
+ if (el.src !== nextSrc) el.src = nextSrc;
15860
+ el.setAttribute("data-ohw-map-query", val);
15861
+ }
15256
15862
  function applyLinkByKey(key, val) {
15257
15863
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
15258
15864
  if (el.dataset.ohwEditable === "link") applyLinkHref(el, val);
@@ -15263,6 +15869,11 @@ function applyLinkByKey(key, val) {
15263
15869
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
15264
15870
  }
15265
15871
  }
15872
+ function isInsideLinkEditor(target) {
15873
+ return Boolean(
15874
+ 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"]')
15875
+ );
15876
+ }
15266
15877
  function isInsideFloatingPanel(target) {
15267
15878
  return Boolean(target.closest("[data-ohw-floating-panel]"));
15268
15879
  }
@@ -15270,11 +15881,6 @@ function isPointOverFloatingPanel(clientX, clientY) {
15270
15881
  const el = document.elementFromPoint(clientX, clientY);
15271
15882
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
15272
15883
  }
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
15884
  function getHrefKeyFromElement(el) {
15279
15885
  if (!el) return null;
15280
15886
  const anchor = el.closest("[data-ohw-href-key]");
@@ -15533,7 +16139,7 @@ function getNavigationSelectionParent(el) {
15533
16139
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
15534
16140
  return getFooterLinksContainer();
15535
16141
  }
15536
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
16142
+ 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
16143
  return getNavigationRoot(el);
15538
16144
  }
15539
16145
  return null;
@@ -15748,7 +16354,6 @@ var ICONS = {
15748
16354
  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
16355
  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
16356
  };
15751
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
15752
16357
  var SELECTION_CHROME_GAP2 = 4;
15753
16358
  var TOOLBAR_STROKE_GAP2 = 4;
15754
16359
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -16128,6 +16733,7 @@ function StateToggle({
16128
16733
  );
16129
16734
  }
16130
16735
  var contentCache = /* @__PURE__ */ new Map();
16736
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
16131
16737
  var brandingCache = /* @__PURE__ */ new Map();
16132
16738
  var OHW_LOADER_STYLE = {
16133
16739
  position: "fixed",
@@ -16657,13 +17263,6 @@ function OhhwellsBridge() {
16657
17263
  const [isItemDragging, setIsItemDragging] = (0, import_react17.useState)(false);
16658
17264
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react17.useState)(false);
16659
17265
  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
17266
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react17.useState)(null);
16668
17267
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react17.useState)(null);
16669
17268
  const footerDragRef = (0, import_react17.useRef)(null);
@@ -16681,6 +17280,13 @@ function OhhwellsBridge() {
16681
17280
  const brandKitRef = (0, import_react17.useRef)("");
16682
17281
  const stylesRef = (0, import_react17.useRef)("");
16683
17282
  const pendingDeleteUndoRef = (0, import_react17.useRef)(null);
17283
+ const [floatingPanel, setFloatingPanel] = (0, import_react17.useState)(null);
17284
+ const floatingPanelOpenRef = (0, import_react17.useRef)(false);
17285
+ const setFloatingPanelRef = (0, import_react17.useRef)(setFloatingPanel);
17286
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react17.useState)(null);
17287
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react17.useState)(null);
17288
+ const [editorViewport, setEditorViewport] = (0, import_react17.useState)("desktop");
17289
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react17.useState)(null);
16684
17290
  const [sitePages, setSitePages] = (0, import_react17.useState)([]);
16685
17291
  const [sectionsByPath, setSectionsByPath] = (0, import_react17.useState)({});
16686
17292
  const sectionsPrefetchGenRef = (0, import_react17.useRef)(0);
@@ -16689,7 +17295,18 @@ function OhhwellsBridge() {
16689
17295
  const linkPopoverOpenRef = (0, import_react17.useRef)(false);
16690
17296
  const linkPopoverGraceUntilRef = (0, import_react17.useRef)(0);
16691
17297
  setLinkPopoverRef.current = setLinkPopover;
17298
+ setFloatingPanelRef.current = setFloatingPanel;
16692
17299
  linkPopoverSessionRef.current = linkPopover;
17300
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
17301
+ (0, import_react17.useEffect)(() => {
17302
+ const syncViewport = () => {
17303
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
17304
+ setEditorViewport((prev) => prev === next ? prev : next);
17305
+ };
17306
+ syncViewport();
17307
+ window.addEventListener("resize", syncViewport);
17308
+ return () => window.removeEventListener("resize", syncViewport);
17309
+ }, []);
16693
17310
  const {
16694
17311
  navDragRef,
16695
17312
  navDropSlots,
@@ -18014,6 +18631,7 @@ function OhhwellsBridge() {
18014
18631
  }
18015
18632
  if (typeof content[STYLE_STORE_KEY] === "string") {
18016
18633
  stylesRef.current = content[STYLE_STORE_KEY];
18634
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
18017
18635
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
18018
18636
  }
18019
18637
  applyBrandChrome(content);
@@ -18021,11 +18639,11 @@ function OhhwellsBridge() {
18021
18639
  for (const [key, val] of Object.entries(content)) {
18022
18640
  if (key === "__ohw_sections") continue;
18023
18641
  if (key === AI_SECTIONS_KEY) continue;
18642
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
18643
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
18024
18644
  if (key === BRAND_KIT_KEY) continue;
18025
18645
  if (key === STYLE_STORE_KEY) continue;
18026
18646
  if (BRAND_CHROME_KEYS.has(key)) continue;
18027
- if (key === LOGO_PLACEHOLDER_KEY) continue;
18028
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
18029
18647
  if (applyVideoSettingNode(key, val)) continue;
18030
18648
  if (applyCarouselNode(key, val)) continue;
18031
18649
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -18053,6 +18671,8 @@ function OhhwellsBridge() {
18053
18671
  }
18054
18672
  } else if (el.dataset.ohwEditable === "link") {
18055
18673
  applyLinkHref(el, val);
18674
+ } else if (el.dataset.ohwEditable === "map") {
18675
+ applyMapQuery(el, val);
18056
18676
  } else if (el.dataset.ohwEditable === "icon") {
18057
18677
  applyIconMarkup(el, val);
18058
18678
  } else if (el.dataset.ohwEditable === "form") {
@@ -18091,7 +18711,9 @@ function OhhwellsBridge() {
18091
18711
  let cancelled = false;
18092
18712
  setFetchState("loading");
18093
18713
  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) => {
18714
+ const initialPath = pathname;
18715
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
18716
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18095
18717
  if (cancelled) return;
18096
18718
  const content = data?.content ?? {};
18097
18719
  const branding = Boolean(data?.showBranding);
@@ -18210,10 +18832,10 @@ function OhhwellsBridge() {
18210
18832
  const applyFromCache = () => {
18211
18833
  const content = contentCache.get(subdomain);
18212
18834
  if (!content) return;
18213
- retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
18214
- initSectionInstancesFromContent(content, window.location.pathname);
18215
18835
  observer?.disconnect();
18216
18836
  try {
18837
+ retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
18838
+ initSectionInstancesFromContent(content, window.location.pathname);
18217
18839
  applyBrandChrome(content);
18218
18840
  if (typeof content[BRAND_KIT_KEY] === "string") {
18219
18841
  applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
@@ -18225,16 +18847,17 @@ function OhhwellsBridge() {
18225
18847
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
18226
18848
  }
18227
18849
  if (typeof content[STYLE_STORE_KEY] === "string") {
18850
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
18228
18851
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
18229
18852
  }
18230
18853
  for (const [key, val] of Object.entries(content)) {
18231
18854
  if (key === "__ohw_sections") continue;
18232
18855
  if (key === AI_SECTIONS_KEY) continue;
18856
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
18857
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
18233
18858
  if (key === BRAND_KIT_KEY) continue;
18234
18859
  if (key === STYLE_STORE_KEY) continue;
18235
18860
  if (BRAND_CHROME_KEYS.has(key)) continue;
18236
- if (key === LOGO_PLACEHOLDER_KEY) continue;
18237
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
18238
18861
  if (applyVideoSettingNode(key, val)) continue;
18239
18862
  if (applyCarouselNode(key, val)) continue;
18240
18863
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -18249,6 +18872,8 @@ function OhhwellsBridge() {
18249
18872
  if (video && video.src !== val) applyVideoSrc(video, val);
18250
18873
  } else if (el.dataset.ohwEditable === "link") {
18251
18874
  applyLinkHref(el, val);
18875
+ } else if (el.dataset.ohwEditable === "map") {
18876
+ applyMapQuery(el, val);
18252
18877
  } else if (el.dataset.ohwEditable === "form") {
18253
18878
  } else if (isIconMarkupValue(val)) {
18254
18879
  } else if (el.innerHTML !== val) {
@@ -18280,6 +18905,17 @@ function OhhwellsBridge() {
18280
18905
  debounceTimer = setTimeout(applyFromCache, 150);
18281
18906
  };
18282
18907
  applyFromCache();
18908
+ const pathCacheKey = `${subdomain}::${pathname}`;
18909
+ if (!fetchedContentPaths.has(pathCacheKey)) {
18910
+ fetchedContentPaths.add(pathCacheKey);
18911
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
18912
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18913
+ if (!data?.content) return;
18914
+ contentCache.set(subdomain, data.content);
18915
+ applyFromCache();
18916
+ }).catch(() => {
18917
+ });
18918
+ }
18283
18919
  observer = new MutationObserver(scheduleApply);
18284
18920
  observer.observe(document.body, { childList: true, subtree: true });
18285
18921
  return () => {
@@ -18304,6 +18940,10 @@ function OhhwellsBridge() {
18304
18940
  deselectRef.current();
18305
18941
  deactivateRef.current();
18306
18942
  }, [pathname, isEditMode]);
18943
+ (0, import_react17.useEffect)(() => {
18944
+ if (!isEditMode) return;
18945
+ initSectionInstancesFromContent(editContentRef.current, pathname);
18946
+ }, [pathname, isEditMode]);
18307
18947
  (0, import_react17.useEffect)(() => {
18308
18948
  const contentForNav = () => {
18309
18949
  if (isEditMode) return editContentRef.current;
@@ -18395,26 +19035,11 @@ function OhhwellsBridge() {
18395
19035
  const t2 = setTimeout(measure, 500);
18396
19036
  const ro = new ResizeObserver(schedule);
18397
19037
  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
19038
  return () => {
18412
19039
  clearTimeout(t1);
18413
19040
  clearTimeout(t2);
18414
19041
  if (raf != null) cancelAnimationFrame(raf);
18415
19042
  ro.disconnect();
18416
- clearResizeTimers();
18417
- window.removeEventListener("resize", handleResize);
18418
19043
  };
18419
19044
  }, [pathname, isEditMode, postToParent2]);
18420
19045
  (0, import_react17.useEffect)(() => {
@@ -18669,9 +19294,6 @@ function OhhwellsBridge() {
18669
19294
  if (target.closest("[data-ohw-state-toggle]")) return;
18670
19295
  if (target.closest("[data-ohw-max-badge]")) return;
18671
19296
  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
19297
  if (isInsideFloatingPanel(target)) return;
18676
19298
  if (target.closest("[data-ohw-form-toolbar]")) return;
18677
19299
  if (target.closest(
@@ -18679,6 +19301,9 @@ function OhhwellsBridge() {
18679
19301
  )) {
18680
19302
  return;
18681
19303
  }
19304
+ if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
19305
+ clearMediaSelectionRef.current();
19306
+ }
18682
19307
  {
18683
19308
  const formEl = getFormElement(target);
18684
19309
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -18830,14 +19455,6 @@ function OhhwellsBridge() {
18830
19455
  }
18831
19456
  const clickedButton = findClosestButtonLike(target);
18832
19457
  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
19458
  if (isMediaEditable(editable) && !buttonOnMedia) {
18842
19459
  e.preventDefault();
18843
19460
  e.stopPropagation();
@@ -18864,11 +19481,6 @@ function OhhwellsBridge() {
18864
19481
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
18865
19482
  const hrefCtx = getHrefKeyFromElement(hrefLookupTarget);
18866
19483
  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
19484
  if (navAnchor) {
18873
19485
  e.preventDefault();
18874
19486
  e.stopPropagation();
@@ -19038,6 +19650,9 @@ function OhhwellsBridge() {
19038
19650
  setHoveredItemRect(null);
19039
19651
  hoveredNavContainerRef.current = null;
19040
19652
  setHoveredNavContainerRect(null);
19653
+ siblingHintElRef.current = null;
19654
+ setSiblingHintRect(null);
19655
+ setSiblingHintRects([]);
19041
19656
  return;
19042
19657
  }
19043
19658
  {
@@ -19156,7 +19771,6 @@ function OhhwellsBridge() {
19156
19771
  hoveredNavContainerRef.current = null;
19157
19772
  setHoveredNavContainerRect(null);
19158
19773
  hoveredItemElRef.current = editable;
19159
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
19160
19774
  }
19161
19775
  }
19162
19776
  }
@@ -19453,7 +20067,7 @@ function OhhwellsBridge() {
19453
20067
  }
19454
20068
  };
19455
20069
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
19456
- if (linkPopoverOpenRef.current) {
20070
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
19457
20071
  if (hoveredImageRef.current) {
19458
20072
  hoveredImageRef.current = null;
19459
20073
  hoveredImageHasTextOverlapRef.current = false;
@@ -19818,8 +20432,7 @@ function OhhwellsBridge() {
19818
20432
  };
19819
20433
  const handleMouseMove = (e) => {
19820
20434
  const { clientX, clientY } = e;
19821
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
19822
- if (isOverEditorChrome(clientX, clientY)) {
20435
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
19823
20436
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
19824
20437
  formHoverElRef.current = null;
19825
20438
  setFormHoverRect(null);
@@ -19827,6 +20440,12 @@ function OhhwellsBridge() {
19827
20440
  setHoveredItemRect(null);
19828
20441
  hoveredNavContainerRef.current = null;
19829
20442
  setHoveredNavContainerRect(null);
20443
+ siblingHintElRef.current = null;
20444
+ setSiblingHintRect(null);
20445
+ setSiblingHintRects([]);
20446
+ dismissImageHover();
20447
+ clearImageHover();
20448
+ setSectionGap(null);
19830
20449
  return;
19831
20450
  }
19832
20451
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -19838,7 +20457,11 @@ function OhhwellsBridge() {
19838
20457
  if (e.data?.type !== "ow:pointer-sync") return;
19839
20458
  const { clientX, clientY } = e.data;
19840
20459
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
19841
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
20460
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
20461
+ dismissImageHover();
20462
+ clearImageHover();
20463
+ return;
20464
+ }
19842
20465
  if (probeSocialsRowAt(clientX, clientY)) return;
19843
20466
  probeSectionGapAt(clientX, clientY);
19844
20467
  probeImageAt(clientX, clientY);
@@ -20117,6 +20740,44 @@ function OhhwellsBridge() {
20117
20740
  if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
20118
20741
  }, 400));
20119
20742
  };
20743
+ const reapCommittedAiSections = (excludeIds) => {
20744
+ const aiState = parseAiSectionsState(aiSectionsRef.current);
20745
+ if (aiState.sections.length === 0) return [];
20746
+ let orderEntries = [];
20747
+ try {
20748
+ const parsed = JSON.parse(editContentRef.current[SECTION_ORDER_KEY] ?? "[]");
20749
+ if (Array.isArray(parsed)) orderEntries = parsed;
20750
+ } catch {
20751
+ return [];
20752
+ }
20753
+ const removedIds = orderEntries.filter((entry) => entry && typeof entry.instanceId === "string" && entry.removed).map((entry) => entry.instanceId);
20754
+ if (removedIds.length === 0) return [];
20755
+ const result = reapRemovedAiSections(aiState, parseStyleStore(stylesRef.current), removedIds, excludeIds);
20756
+ if (!result.changed) return [];
20757
+ const nodes = [];
20758
+ aiSectionsRef.current = serializeAiSectionsState(result.state);
20759
+ nodes.push({ key: AI_SECTIONS_KEY, text: aiSectionsRef.current });
20760
+ const reaped = new Set(result.reapedIds);
20761
+ const nextOrderJson = JSON.stringify(orderEntries.filter((entry) => !reaped.has(entry.instanceId)));
20762
+ editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: nextOrderJson };
20763
+ setAiSectionOrder(nextOrderJson, window.location.pathname);
20764
+ nodes.push({ key: SECTION_ORDER_KEY, text: nextOrderJson });
20765
+ if (result.store) {
20766
+ stylesRef.current = JSON.stringify(result.store);
20767
+ nodes.push({ key: STYLE_STORE_KEY, text: stylesRef.current });
20768
+ }
20769
+ const nextContent = { ...editContentRef.current };
20770
+ for (const key of Object.keys(nextContent)) {
20771
+ if (key.startsWith(AI_SLOT_KEY_PREFIX) && result.slotPrefixes.some((prefix) => key.startsWith(prefix)) && nextContent[key] !== "") {
20772
+ nextContent[key] = "";
20773
+ nodes.push({ key, text: "" });
20774
+ }
20775
+ }
20776
+ editContentRef.current = nextContent;
20777
+ applyAiSectionsToDom(result.state);
20778
+ applyStylesToDom(parseStyleStore(stylesRef.current));
20779
+ return nodes;
20780
+ };
20120
20781
  const handleHydrate = (e) => {
20121
20782
  if (e.data?.type !== "ow:hydrate") return;
20122
20783
  const content = e.data.content;
@@ -20135,6 +20796,7 @@ function OhhwellsBridge() {
20135
20796
  }
20136
20797
  if (typeof content[STYLE_STORE_KEY] === "string") {
20137
20798
  stylesRef.current = content[STYLE_STORE_KEY];
20799
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
20138
20800
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
20139
20801
  }
20140
20802
  applyBrandChrome(content);
@@ -20146,11 +20808,11 @@ function OhhwellsBridge() {
20146
20808
  continue;
20147
20809
  }
20148
20810
  if (key === AI_SECTIONS_KEY) continue;
20811
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
20812
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
20149
20813
  if (key === BRAND_KIT_KEY) continue;
20150
20814
  if (key === STYLE_STORE_KEY) continue;
20151
20815
  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
20816
  if (applyVideoSettingNode(key, val)) continue;
20155
20817
  if (applyCarouselNode(key, val)) continue;
20156
20818
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -20164,6 +20826,8 @@ function OhhwellsBridge() {
20164
20826
  if (video && video.src !== val) applyVideoSrc(video, val);
20165
20827
  } else if (el.dataset.ohwEditable === "link") {
20166
20828
  applyLinkHref(el, val);
20829
+ } else if (el.dataset.ohwEditable === "map") {
20830
+ applyMapQuery(el, val);
20167
20831
  } else if (el.dataset.ohwEditable === "icon") {
20168
20832
  applyIconMarkup(el, val);
20169
20833
  } else if (isIconMarkupValue(val)) {
@@ -20185,8 +20849,16 @@ function OhhwellsBridge() {
20185
20849
  reconcileFooterOrderFromContent(editContentRef.current);
20186
20850
  syncNavigationDragCursorAttrs();
20187
20851
  enforceLinkHrefs();
20852
+ const hydrateReapExclude = /* @__PURE__ */ new Set();
20853
+ const hydratePendingUndo = pendingDeleteUndoRef.current;
20854
+ if (hydratePendingUndo?.sectionInstanceId) hydrateReapExclude.add(hydratePendingUndo.sectionInstanceId);
20855
+ const reapNodes = reapCommittedAiSections(hydrateReapExclude);
20856
+ if (reapNodes.length > 0) postToParentRef.current({ type: "ow:change", nodes: reapNodes });
20188
20857
  const hydratedHeight = document.body.scrollHeight;
20189
20858
  if (hydratedHeight > 50) postToParentRef.current({ type: "ow:height", height: hydratedHeight });
20859
+ if (parseAiSectionsState(aiSectionsRef.current).sections.length > 0) {
20860
+ postAiSectionsChanged();
20861
+ }
20190
20862
  postToParentRef.current({ type: "ow:hydrate-done" });
20191
20863
  };
20192
20864
  const handleUpdateLogoIdentity = (e) => {
@@ -20324,12 +20996,35 @@ function OhhwellsBridge() {
20324
20996
  window.addEventListener("message", handleAiSetBrand);
20325
20997
  const handleAiSetStyles = (e) => {
20326
20998
  if (e.data?.type !== "ow:ai-set-styles") return;
20327
- const value = typeof e.data.value === "string" ? e.data.value : "";
20999
+ let value = typeof e.data.value === "string" ? e.data.value : "";
20328
21000
  const previous = stylesRef.current;
21001
+ let previousSections;
21002
+ const store = parseStyleStore(value);
21003
+ if (store) {
21004
+ const folded = foldAlignIntoTrees(parseAiSectionsState(aiSectionsRef.current), store);
21005
+ if (folded.changed) {
21006
+ const nextSections = serializeAiSectionsState(folded.state);
21007
+ if (nextSections !== aiSectionsRef.current) {
21008
+ previousSections = aiSectionsRef.current;
21009
+ aiSectionsRef.current = nextSections;
21010
+ applyAiSectionsToDom(folded.state);
21011
+ postToParentRef.current({
21012
+ type: "ow:change",
21013
+ nodes: [{ key: AI_SECTIONS_KEY, text: nextSections }]
21014
+ });
21015
+ }
21016
+ value = JSON.stringify(folded.store);
21017
+ }
21018
+ }
20329
21019
  stylesRef.current = value;
20330
21020
  applyStylesToDom(parseStyleStore(value));
20331
21021
  postToParentRef.current({ type: "ow:change", nodes: [{ key: STYLE_STORE_KEY, text: value }] });
20332
- postToParentRef.current({ type: "ow:ai-styles-applied", previous, value });
21022
+ postToParentRef.current({
21023
+ type: "ow:ai-styles-applied",
21024
+ previous,
21025
+ value,
21026
+ ...previousSections !== void 0 ? { previousSections } : {}
21027
+ });
20333
21028
  };
20334
21029
  window.addEventListener("message", handleAiSetStyles);
20335
21030
  const handleGetBrand = (e) => {
@@ -20346,8 +21041,11 @@ function OhhwellsBridge() {
20346
21041
  if (!instanceId || !direction) return;
20347
21042
  const entries = moveSectionInstance(instanceId, direction, window.location.pathname);
20348
21043
  if (!entries) return;
20349
- const orderJson = JSON.stringify(entries);
21044
+ const orderJson = JSON.stringify(
21045
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
21046
+ );
20350
21047
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
21048
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
20351
21049
  setAiSectionOrder(orderJson, window.location.pathname);
20352
21050
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20353
21051
  window.dispatchEvent(new Event("resize"));
@@ -20393,8 +21091,11 @@ function OhhwellsBridge() {
20393
21091
  const currentEntries = getPageSectionOrderEntries(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname);
20394
21092
  const entries = deleteSectionInstance(instanceId, window.location.pathname, currentEntries);
20395
21093
  if (!entries) return;
20396
- const orderJson = JSON.stringify(entries);
21094
+ const orderJson = JSON.stringify(
21095
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
21096
+ );
20397
21097
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
21098
+ setAiSectionOrder(orderJson, window.location.pathname);
20398
21099
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20399
21100
  aiSectionApiRef.current?.clear();
20400
21101
  window.dispatchEvent(new Event("resize"));
@@ -20403,6 +21104,7 @@ function OhhwellsBridge() {
20403
21104
  const actionId = newInstanceId();
20404
21105
  pendingDeleteUndoRef.current = {
20405
21106
  actionId,
21107
+ sectionInstanceId: instanceId,
20406
21108
  restore: () => {
20407
21109
  const restoredEntries = getPageSectionOrderEntries(
20408
21110
  editContentRef.current[SECTION_ORDER_KEY],
@@ -20410,8 +21112,11 @@ function OhhwellsBridge() {
20410
21112
  );
20411
21113
  const restored = restoreSectionInstance(instanceId, window.location.pathname, restoredEntries);
20412
21114
  if (!restored) return;
20413
- const restoredJson = JSON.stringify(restored);
21115
+ const restoredJson = JSON.stringify(
21116
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, restored)
21117
+ );
20414
21118
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: restoredJson };
21119
+ setAiSectionOrder(restoredJson, window.location.pathname);
20415
21120
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: restoredJson }] });
20416
21121
  window.dispatchEvent(new Event("resize"));
20417
21122
  const restoreHeight = document.body.scrollHeight;
@@ -20437,7 +21142,9 @@ function OhhwellsBridge() {
20437
21142
  const result = duplicateSectionInstance(instanceId, newId, window.location.pathname, currentEntries);
20438
21143
  if (!result) return;
20439
21144
  const { entries, keyRekeys } = result;
20440
- const orderJson = JSON.stringify(entries);
21145
+ const orderJson = JSON.stringify(
21146
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
21147
+ );
20441
21148
  const nodes = [{ key: SECTION_ORDER_KEY, text: orderJson }];
20442
21149
  for (const { from, to } of keyRekeys) {
20443
21150
  const inherited = editContentRef.current[from];
@@ -20447,6 +21154,7 @@ function OhhwellsBridge() {
20447
21154
  ...editContentRef.current,
20448
21155
  ...Object.fromEntries(nodes.map(({ key, text }) => [key, text]))
20449
21156
  };
21157
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
20450
21158
  setAiSectionOrder(orderJson, window.location.pathname);
20451
21159
  postToParentRef.current({ type: "ow:change", nodes });
20452
21160
  window.dispatchEvent(new Event("resize"));
@@ -20465,6 +21173,12 @@ function OhhwellsBridge() {
20465
21173
  closeLinkPopoverRef.current();
20466
21174
  return;
20467
21175
  }
21176
+ if (floatingPanelOpenRef.current) {
21177
+ setFloatingPanelRef.current(null);
21178
+ deselectRef.current();
21179
+ deactivateRef.current();
21180
+ return;
21181
+ }
20468
21182
  deselectRef.current();
20469
21183
  deactivateRef.current();
20470
21184
  clearMediaSelectionRef.current();
@@ -20710,6 +21424,10 @@ function OhhwellsBridge() {
20710
21424
  };
20711
21425
  const handleSave = (e) => {
20712
21426
  if (e.data?.type !== "ow:save") return;
21427
+ const pendingUndo = pendingDeleteUndoRef.current;
21428
+ const reapExclude = /* @__PURE__ */ new Set();
21429
+ if (pendingUndo?.sectionInstanceId) reapExclude.add(pendingUndo.sectionInstanceId);
21430
+ const reapNodes = reapCommittedAiSections(reapExclude);
20713
21431
  const nodes = collectEditableNodes(editContentRef.current);
20714
21432
  const tracker = document.querySelector("[data-ohw-sections-tracker]");
20715
21433
  if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
@@ -20729,6 +21447,11 @@ function OhhwellsBridge() {
20729
21447
  const text = specs.length ? JSON.stringify(specs) : editContentRef.current[fieldsKey(formKey)];
20730
21448
  if (text) nodes.push({ key: fieldsKey(formKey), type: "text", text });
20731
21449
  });
21450
+ for (const reapNode of reapNodes) {
21451
+ if (reapNode.key.startsWith(AI_SLOT_KEY_PREFIX) && !nodes.some((n) => n.key === reapNode.key)) {
21452
+ nodes.push({ key: reapNode.key, type: "text", text: reapNode.text });
21453
+ }
21454
+ }
20732
21455
  postToParentRef.current({ type: "ow:save-result", nodes });
20733
21456
  };
20734
21457
  const handleInsertSection = (e) => {
@@ -20739,8 +21462,12 @@ function OhhwellsBridge() {
20739
21462
  if (inserted) {
20740
21463
  const tracker = getSectionsTracker();
20741
21464
  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 });
21465
+ const reportHeight = () => {
21466
+ const h = document.body.scrollHeight;
21467
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
21468
+ };
21469
+ reportHeight();
21470
+ setTimeout(reportHeight, 500);
20744
21471
  }
20745
21472
  };
20746
21473
  const handleSwitchSchedule = (e) => {
@@ -21144,10 +21871,10 @@ function OhhwellsBridge() {
21144
21871
  window.removeEventListener("message", handleDeleteSection);
21145
21872
  window.removeEventListener("message", handleDuplicateSection);
21146
21873
  window.removeEventListener("message", handleDeactivate);
21147
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
21148
21874
  window.removeEventListener("message", handleToastAction);
21149
21875
  window.removeEventListener("message", handleFormCount);
21150
21876
  window.removeEventListener("message", handleUiEscape);
21877
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
21151
21878
  autoSaveTimers.current.forEach(clearTimeout);
21152
21879
  autoSaveTimers.current.clear();
21153
21880
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -21350,7 +22077,7 @@ function OhhwellsBridge() {
21350
22077
  postToParent2({
21351
22078
  type: "ow:ready",
21352
22079
  version: "1",
21353
- bridgeVersion: "0.1.90",
22080
+ bridgeVersion: "0.1.92",
21354
22081
  path: pathname,
21355
22082
  nodes: collectEditableNodes(editContentRef.current),
21356
22083
  sections
@@ -22269,6 +22996,59 @@ function OhhwellsBridge() {
22269
22996
  ) : null
22270
22997
  ] });
22271
22998
  }
22999
+
23000
+ // src/ui/EmptySection.tsx
23001
+ var import_link = __toESM(require("next/link"), 1);
23002
+ var import_jsx_runtime34 = require("react/jsx-runtime");
23003
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
23004
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
23005
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
23006
+ "p",
23007
+ {
23008
+ style: {
23009
+ fontFamily: "var(--brand-font-body)",
23010
+ fontSize: "0.75rem",
23011
+ fontWeight: 500,
23012
+ letterSpacing: "0.15em",
23013
+ textTransform: "uppercase",
23014
+ color: "var(--brand-accent)",
23015
+ marginBottom: "1.5rem"
23016
+ },
23017
+ 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" }) })
23018
+ }
23019
+ ),
23020
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
23021
+ "h1",
23022
+ {
23023
+ style: {
23024
+ fontFamily: "var(--brand-font-heading)",
23025
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
23026
+ lineHeight: 1.1,
23027
+ letterSpacing: "-0.025em",
23028
+ color: "var(--brand-text)",
23029
+ marginBottom: "1rem"
23030
+ },
23031
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
23032
+ children: title
23033
+ }
23034
+ ),
23035
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
23036
+ "p",
23037
+ {
23038
+ style: {
23039
+ fontFamily: "var(--brand-font-body)",
23040
+ fontSize: "1rem",
23041
+ lineHeight: 1.7,
23042
+ fontWeight: 300,
23043
+ color: "var(--brand-text-muted)",
23044
+ maxWidth: "340px"
23045
+ },
23046
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
23047
+ children: "This page doesn't have any content yet."
23048
+ }
23049
+ )
23050
+ ] });
23051
+ }
22272
23052
  // Annotate the CommonJS export names for ESM import in node:
22273
23053
  0 && (module.exports = {
22274
23054
  AI_DEFAULT_BRAND,
@@ -22286,6 +23066,7 @@ function OhhwellsBridge() {
22286
23066
  DropdownMenuItem,
22287
23067
  DropdownMenuSeparator,
22288
23068
  DropdownMenuTrigger,
23069
+ EmptySection,
22289
23070
  ItemActionToolbar,
22290
23071
  ItemInteractionLayer,
22291
23072
  LinkEditorPanel,