@ohhwells/bridge 0.1.85 → 0.1.86-next.255

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,12 @@ 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]) { border-radius: 0 !important; }`
497
+ );
498
+ for (const align of ["left", "center", "right"]) {
499
+ rules.push(`[data-ohw-style-align="${align}"] { text-align: ${align} !important; }`);
500
+ }
406
501
  return rules.join("\n");
407
502
  }
408
503
  var STYLE_FONT_LINK_ID = "ohw-style-fonts";
@@ -429,10 +524,25 @@ var SECTION_ATTRS = {
429
524
  textDistribution: "data-ohw-style-distribution",
430
525
  headlineScale: "data-ohw-style-headline",
431
526
  imageAspect: "data-ohw-style-aspect",
432
- spacing: "data-ohw-style-spacing"
527
+ spacing: "data-ohw-style-spacing",
528
+ cornerStyle: "data-ohw-style-corners",
529
+ align: "data-ohw-style-align"
433
530
  };
434
531
  var NODE_WROTE_ATTR = "data-ohw-style-node";
435
- var NODE_PROPS = ["color", "font-family", "font-size", "background"];
532
+ var NODE_PROPS = [
533
+ "color",
534
+ "font-family",
535
+ "font-size",
536
+ "background",
537
+ "text-align",
538
+ "justify-content",
539
+ "align-items"
540
+ ];
541
+ var ALIGN_JUSTIFY = {
542
+ left: "flex-start",
543
+ center: "center",
544
+ right: "flex-end"
545
+ };
436
546
  function saveInline(el, prop) {
437
547
  const attr = `data-ohw-style-prev-${prop}`;
438
548
  if (el.hasAttribute(attr)) return;
@@ -476,6 +586,10 @@ function clearNodeProps(root) {
476
586
  function buttonSurfaceOf(el) {
477
587
  return el.closest("a, button") ?? el;
478
588
  }
589
+ function alignSubjectOf(el) {
590
+ const button = el.closest('[data-ohw-role="button"]');
591
+ return button?.parentElement ?? el;
592
+ }
479
593
  function applyStylesToDom(store) {
480
594
  ensureStyleSheet();
481
595
  clearSectionAttrs(document);
@@ -521,6 +635,18 @@ function applyStylesToDom(store) {
521
635
  el.style.setProperty("font-size", `${override.fontSize}px`, "important");
522
636
  el.setAttribute(NODE_WROTE_ATTR, "");
523
637
  }
638
+ if (override.align !== void 0) {
639
+ const subject = alignSubjectOf(el);
640
+ saveInline(subject, "text-align");
641
+ saveInline(subject, "justify-content");
642
+ subject.style.setProperty("text-align", override.align, "important");
643
+ subject.style.setProperty(
644
+ "justify-content",
645
+ ALIGN_JUSTIFY[override.align] ?? "flex-start",
646
+ "important"
647
+ );
648
+ subject.setAttribute(NODE_WROTE_ATTR, "");
649
+ }
524
650
  if (override.buttonBackground !== void 0 || override.buttonText !== void 0) {
525
651
  const surface = buttonSurfaceOf(el);
526
652
  if (override.buttonBackground !== void 0) {
@@ -541,9 +667,494 @@ function applyStylesToDom(store) {
541
667
  var import_react_dom = require("react-dom");
542
668
  var import_client = require("react-dom/client");
543
669
 
670
+ // src/lib/sections.ts
671
+ var LINK_PICKER_EXCLUDED_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
672
+ function isChromeSection(el) {
673
+ return el.matches("header, nav, footer, aside");
674
+ }
675
+ function titleCaseSectionId(id) {
676
+ return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
677
+ }
678
+ function parseSectionsFromRoot(root) {
679
+ const seen = /* @__PURE__ */ new Set();
680
+ const sections = [];
681
+ for (const el of root.querySelectorAll("[data-ohw-section]")) {
682
+ const id = el.getAttribute("data-ohw-section") ?? "";
683
+ if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
684
+ if (el.parentElement?.closest("[data-ohw-section]")) continue;
685
+ if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
686
+ continue;
687
+ seen.add(id);
688
+ const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
689
+ sections.push({ id, label });
690
+ }
691
+ return sections;
692
+ }
693
+ function collectSectionsFromDom() {
694
+ if (typeof document === "undefined") return [];
695
+ return parseSectionsFromRoot(document);
696
+ }
697
+ function parseSectionsFromHtml(html) {
698
+ const doc = new DOMParser().parseFromString(html, "text/html");
699
+ return parseSectionsFromRoot(doc);
700
+ }
701
+
702
+ // src/lib/section-instances.ts
703
+ var SECTION_ORDER_KEY = "__ohw_section_order";
704
+ var REMOVED_ATTR = "data-ohw-section-removed";
705
+ function isRemovedSection(el) {
706
+ return el.hasAttribute(REMOVED_ATTR);
707
+ }
708
+ function topLevelSections() {
709
+ return Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
710
+ (el) => !el.parentElement?.closest("[data-ohw-section]") && !isChromeSection(el) && !isRemovedSection(el)
711
+ );
712
+ }
713
+ function instanceIdOf(el) {
714
+ return el.dataset.ohwInstance ?? el.dataset.ohwSection ?? "";
715
+ }
716
+ function findByInstanceId(instanceId) {
717
+ const escapedId = CSS.escape(instanceId);
718
+ return document.querySelector(`[data-ohw-instance="${escapedId}"]`) ?? document.querySelector(`[data-ohw-section="${escapedId}"]:not([data-ohw-instance])`);
719
+ }
720
+ function planSectionMove(instanceId, targetIndex, currentPath) {
721
+ const sections = topLevelSections();
722
+ const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
723
+ if (index === -1) return null;
724
+ const dragged = sections[index];
725
+ const others = sections.filter((_, i) => i !== index);
726
+ const clamped = Math.max(0, Math.min(targetIndex, others.length));
727
+ const reordered = [...others.slice(0, clamped), dragged, ...others.slice(clamped)];
728
+ return reordered.map((el, order) => ({
729
+ instanceId: instanceIdOf(el),
730
+ type: el.getAttribute("data-ohw-section") ?? "",
731
+ order,
732
+ pagePath: currentPath
733
+ }));
734
+ }
735
+ function moveSectionInstance(instanceId, direction, currentPath) {
736
+ const sections = topLevelSections();
737
+ const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
738
+ if (index === -1) return null;
739
+ const siblingIndex = direction === "up" ? index - 1 : index + 1;
740
+ if (siblingIndex < 0 || siblingIndex >= sections.length) return null;
741
+ const entries = planSectionMove(instanceId, siblingIndex, currentPath);
742
+ if (!entries) return null;
743
+ applyPersistedOrder(entries);
744
+ return entries;
745
+ }
746
+ function syncRemovedFlags(entries) {
747
+ const removedIds = new Set(entries.filter((e) => e.removed).map((e) => e.instanceId));
748
+ document.querySelectorAll(`[${REMOVED_ATTR}]`).forEach((el) => {
749
+ if (!removedIds.has(instanceIdOf(el))) {
750
+ el.style.removeProperty("display");
751
+ el.removeAttribute(REMOVED_ATTR);
752
+ }
753
+ });
754
+ for (const id of removedIds) {
755
+ const el = findByInstanceId(id);
756
+ if (el) {
757
+ el.style.display = "none";
758
+ el.setAttribute(REMOVED_ATTR, "");
759
+ }
760
+ }
761
+ }
762
+ function applyPersistedOrder(entries) {
763
+ syncRemovedFlags(entries);
764
+ if (entries.length === 0) return;
765
+ const sections = topLevelSections();
766
+ if (sections.length === 0) return;
767
+ const orderIndex = new Map(entries.map((e) => [e.instanceId, e.order]));
768
+ const ordered = [...sections].sort((a, b) => {
769
+ const aOrder = orderIndex.get(instanceIdOf(a));
770
+ const bOrder = orderIndex.get(instanceIdOf(b));
771
+ if (aOrder === void 0 && bOrder === void 0) return 0;
772
+ if (aOrder === void 0) return 1;
773
+ if (bOrder === void 0) return -1;
774
+ return aOrder - bOrder;
775
+ });
776
+ let prev = null;
777
+ for (const el of ordered) {
778
+ if (prev) prev.after(el);
779
+ prev = el;
780
+ }
781
+ }
782
+ function setSectionRemoved(instanceId, currentPath, existingEntries, removed) {
783
+ if (!findByInstanceId(instanceId)) return null;
784
+ const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
785
+ const allSections = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
786
+ (el) => !el.parentElement?.closest("[data-ohw-section]") && !isChromeSection(el)
787
+ );
788
+ allSections.forEach((el, order) => {
789
+ const id = instanceIdOf(el);
790
+ if (!byId.has(id)) {
791
+ byId.set(id, { instanceId: id, type: el.getAttribute("data-ohw-section") ?? "", order, pagePath: currentPath });
792
+ }
793
+ });
794
+ const target = byId.get(instanceId);
795
+ if (!target) return null;
796
+ byId.set(instanceId, { ...target, removed });
797
+ const entries = Array.from(byId.values());
798
+ applyPersistedOrder(entries);
799
+ return entries;
800
+ }
801
+ function deleteSectionInstance(instanceId, currentPath, existingEntries) {
802
+ return setSectionRemoved(instanceId, currentPath, existingEntries, true);
803
+ }
804
+ function restoreSectionInstance(instanceId, currentPath, existingEntries) {
805
+ return setSectionRemoved(instanceId, currentPath, existingEntries, false);
806
+ }
807
+ function duplicateSectionInstance(instanceId, newId, currentPath, existingEntries) {
808
+ const original = findByInstanceId(instanceId);
809
+ if (!original) return null;
810
+ const clone = original.cloneNode(true);
811
+ clone.setAttribute("data-ohw-instance", newId);
812
+ const keyRekeys = rekeySectionSubtree(clone, newId);
813
+ original.insertAdjacentElement("afterend", clone);
814
+ const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
815
+ const entries = topLevelSections().map((el, order) => {
816
+ const id = instanceIdOf(el);
817
+ return {
818
+ instanceId: id,
819
+ type: el.getAttribute("data-ohw-section") ?? "",
820
+ order,
821
+ pagePath: currentPath,
822
+ ...byId.get(id)?.removed ? { removed: true } : {}
823
+ };
824
+ });
825
+ applyPersistedOrder(entries);
826
+ return { entries, keyRekeys };
827
+ }
828
+ function newInstanceId() {
829
+ return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `instance-${Date.now()}-${Math.random().toString(36).slice(2)}`;
830
+ }
831
+ function getPageSectionOrderEntries(raw, currentPath) {
832
+ if (!raw) return [];
833
+ try {
834
+ const entries = JSON.parse(raw);
835
+ return entries.filter((e) => !e.pagePath || e.pagePath === currentPath);
836
+ } catch {
837
+ return [];
838
+ }
839
+ }
840
+ function rekeySectionSubtree(root, instanceId) {
841
+ const suffix = `::${instanceId}`;
842
+ const pairs = [];
843
+ const rekey = (el, attr) => {
844
+ const current = el.getAttribute(attr);
845
+ if (!current) return;
846
+ const base = current.includes("::") ? current.slice(0, current.indexOf("::")) : current;
847
+ const next = `${base}${suffix}`;
848
+ el.setAttribute(attr, next);
849
+ pairs.push({ from: current, to: next });
850
+ };
851
+ if (root.hasAttribute("data-ohw-key")) rekey(root, "data-ohw-key");
852
+ if (root.hasAttribute("data-ohw-href-key")) rekey(root, "data-ohw-href-key");
853
+ root.querySelectorAll("[data-ohw-key]").forEach((el) => rekey(el, "data-ohw-key"));
854
+ root.querySelectorAll("[data-ohw-href-key]").forEach((el) => rekey(el, "data-ohw-href-key"));
855
+ return pairs;
856
+ }
857
+ function initSectionInstancesFromContent(content, currentPath) {
858
+ document.querySelectorAll("[data-ohw-section]:not([data-ohw-instance])").forEach((el) => {
859
+ el.setAttribute("data-ohw-instance", el.getAttribute("data-ohw-section") ?? "");
860
+ });
861
+ const entries = getPageSectionOrderEntries(content[SECTION_ORDER_KEY], currentPath);
862
+ for (const entry of entries) {
863
+ if (entry.instanceId === entry.type) continue;
864
+ if (document.querySelector(`[data-ohw-instance="${CSS.escape(entry.instanceId)}"]`)) continue;
865
+ const original = document.querySelector(
866
+ `[data-ohw-section="${CSS.escape(entry.type)}"][data-ohw-instance="${CSS.escape(entry.type)}"]`
867
+ );
868
+ if (!original) continue;
869
+ const clone = original.cloneNode(true);
870
+ clone.setAttribute("data-ohw-instance", entry.instanceId);
871
+ rekeySectionSubtree(clone, entry.instanceId);
872
+ original.insertAdjacentElement("afterend", clone);
873
+ }
874
+ applyPersistedOrder(entries);
875
+ }
876
+
544
877
  // src/ui/ai-tree/AiTreeRenderer.tsx
545
878
  var import_react = __toESM(require("react"), 1);
546
879
  var import_lucide_react = require("lucide-react");
880
+
881
+ // src/lib/placeholder-imagery.ts
882
+ var U = (id) => `https://images.unsplash.com/photo-${id}?auto=format&fit=crop&w=1600&q=80`;
883
+ var GENERIC = [
884
+ U("1441986300917-64674bd600d8"),
885
+ U("1486406146926-c627a92ad1ab"),
886
+ U("1497032628192-86f99bcd76bc"),
887
+ U("1521737604893-d14cc237f11d"),
888
+ U("1522071820081-009f0129c71c"),
889
+ U("1519389950473-47ba0277781c"),
890
+ U("1460925895917-afdab827c52f"),
891
+ U("1504384308090-c894fdcc538d")
892
+ ];
893
+ var PEOPLE = [
894
+ U("1500648767791-00dcc994a43e"),
895
+ U("1494790108377-be9c29b29330"),
896
+ U("1507003211169-0a1dd7228f2d"),
897
+ U("1438761681033-6461ffad8d80"),
898
+ U("1544005313-94ddf0286df2"),
899
+ U("1472099645785-5658abf4ff4e"),
900
+ U("1519085360753-af0119f7cbe7"),
901
+ U("1534528741775-53994a69daeb")
902
+ ];
903
+ var THEMED = [
904
+ {
905
+ keywords: ["portrait", "headshot", "person", "people", "team", "staff", "avatar", "founder", "face"],
906
+ pool: PEOPLE
907
+ },
908
+ {
909
+ keywords: ["pet", "dog", "cat", "puppy", "kitten", "vet", "animal"],
910
+ pool: [
911
+ U("1548199973-03cce0bbc87b"),
912
+ U("1450778869180-41d0601e046e"),
913
+ U("1583511655857-d19b40a7a54e"),
914
+ U("1587300003388-59208cc962cb"),
915
+ U("1517849845537-4d257902454a"),
916
+ U("1601758228041-f3b2795255f1")
917
+ ]
918
+ },
919
+ {
920
+ keywords: [
921
+ "baker",
922
+ "bakery",
923
+ "cafe",
924
+ "coffee",
925
+ "latte",
926
+ "restaurant",
927
+ "pastr",
928
+ "bread",
929
+ "cake",
930
+ "cater",
931
+ "chef",
932
+ "kitchen",
933
+ "food",
934
+ "pizza",
935
+ "dessert",
936
+ "brunch",
937
+ "bistro",
938
+ "deli",
939
+ "dish",
940
+ "menu"
941
+ ],
942
+ pool: [
943
+ U("1509440159596-0249088772ff"),
944
+ U("1555507036-ab1f4038808a"),
945
+ U("1517433670267-08bbd4be890f"),
946
+ U("1486427944299-d1955d23e34d"),
947
+ U("1504754524776-8f4f37790ca0"),
948
+ U("1495474472287-4d71bcdd2085"),
949
+ U("1521017432531-fbd92d768814"),
950
+ U("1556909114-f6e7ad7d3136")
951
+ ]
952
+ },
953
+ {
954
+ keywords: [
955
+ "shop",
956
+ "store",
957
+ "boutique",
958
+ "retail",
959
+ "clothing",
960
+ "fashion",
961
+ "jewel",
962
+ "gift",
963
+ "florist",
964
+ "market",
965
+ "grocer",
966
+ "product",
967
+ "storefront"
968
+ ],
969
+ pool: [
970
+ U("1441984904996-e0b6ba687e04"),
971
+ U("1472851294608-062f824d29cc"),
972
+ U("1523381210434-271e8be1f52b"),
973
+ U("1534452203293-494d7ddbf7e0"),
974
+ U("1445205170230-053b83016050"),
975
+ U("1560243563-062bfc001d68")
976
+ ]
977
+ },
978
+ {
979
+ keywords: [
980
+ "yoga",
981
+ "pilates",
982
+ "fitness",
983
+ "gym",
984
+ "workout",
985
+ "trainer",
986
+ "wellness",
987
+ "meditat",
988
+ "massage",
989
+ "therap",
990
+ "physio",
991
+ "chiro",
992
+ "nutrition",
993
+ "spa",
994
+ "studio"
995
+ ],
996
+ pool: [
997
+ U("1544367567-0f2fcb009e0b"),
998
+ U("1506126613408-eca07ce68773"),
999
+ U("1545205597-3d9d02c29597"),
1000
+ U("1552196563-55cd4e45efb3"),
1001
+ U("1518611012118-696072aa579a"),
1002
+ U("1571019613454-1cb2f99b2d8b"),
1003
+ U("1540555700478-4be289fbecef"),
1004
+ U("1519824145371-296894a0daa9")
1005
+ ]
1006
+ },
1007
+ {
1008
+ keywords: [
1009
+ "salon",
1010
+ "hairdress",
1011
+ "haircut",
1012
+ "barber",
1013
+ "manicure",
1014
+ "pedicure",
1015
+ "nails",
1016
+ "beauty",
1017
+ "makeup",
1018
+ "cosmetic",
1019
+ "eyelash",
1020
+ "eyebrow",
1021
+ "skincare",
1022
+ "esthetic",
1023
+ "waxing",
1024
+ "hair"
1025
+ ],
1026
+ pool: [
1027
+ U("1560066984-138dadb4c035"),
1028
+ U("1522337660859-02fbefca4702"),
1029
+ U("1562322140-8baeececf3df"),
1030
+ U("1521590832167-7bcbfaa6381f"),
1031
+ U("1487412947147-5cebf100ffc2"),
1032
+ U("1526045478516-99145907023c")
1033
+ ]
1034
+ },
1035
+ {
1036
+ keywords: [
1037
+ "cleaning",
1038
+ "plumb",
1039
+ "electric",
1040
+ "landscap",
1041
+ "contractor",
1042
+ "handyman",
1043
+ "renov",
1044
+ "hvac",
1045
+ "roofing",
1046
+ "painting",
1047
+ "carpentry",
1048
+ "flooring",
1049
+ "movers",
1050
+ "construction",
1051
+ "tools"
1052
+ ],
1053
+ pool: [
1054
+ U("1581578731548-c64695cc6952"),
1055
+ U("1504307651254-35680f356dfd"),
1056
+ U("1581092160562-40aa08e78837"),
1057
+ U("1621905251189-08b45d6a269e"),
1058
+ U("1558618666-fcd25c85cd64"),
1059
+ U("1585128792020-803d29415281")
1060
+ ]
1061
+ },
1062
+ {
1063
+ keywords: [
1064
+ "legal",
1065
+ "attorney",
1066
+ "lawyer",
1067
+ "account",
1068
+ "bookkeep",
1069
+ "consult",
1070
+ "coaching",
1071
+ "financ",
1072
+ "insurance",
1073
+ "realtor",
1074
+ "estate",
1075
+ "marketing",
1076
+ "agency",
1077
+ "office",
1078
+ "business",
1079
+ "desk"
1080
+ ],
1081
+ pool: [
1082
+ U("1497366216548-37526070297c"),
1083
+ U("1497366811353-6870744d04b2"),
1084
+ U("1454165804606-c3d57bc86b40"),
1085
+ U("1521791136064-7986c2920216"),
1086
+ U("1556761175-b413da4baf72"),
1087
+ U("1542744173-8e7e53415bb0")
1088
+ ]
1089
+ },
1090
+ {
1091
+ keywords: [
1092
+ "wedding",
1093
+ "event",
1094
+ "party",
1095
+ "celebrat",
1096
+ "venue",
1097
+ "community",
1098
+ "nonprofit",
1099
+ "charity",
1100
+ "workshop",
1101
+ "photograph",
1102
+ "concert"
1103
+ ],
1104
+ pool: [
1105
+ U("1511578314322-379afb476865"),
1106
+ U("1501281668745-f7f57925c3b4"),
1107
+ U("1523580494863-6f3031224c94"),
1108
+ U("1540575467063-178a50c2df87"),
1109
+ U("1505236858219-8359eb29e329"),
1110
+ U("1528605248644-14dd04022da1")
1111
+ ]
1112
+ }
1113
+ ];
1114
+ function poolForSubject(subject) {
1115
+ for (const theme of THEMED) {
1116
+ if (theme.keywords.some((k) => subject.includes(k))) {
1117
+ return theme.pool;
1118
+ }
1119
+ }
1120
+ return GENERIC;
1121
+ }
1122
+ function mixedHash(text) {
1123
+ let hash = 2166136261;
1124
+ for (let i = 0; i < text.length; i++) {
1125
+ hash ^= text.charCodeAt(i);
1126
+ hash = Math.imul(hash, 16777619);
1127
+ }
1128
+ return hash >>> 16 & 65535;
1129
+ }
1130
+ function resolvePlaceholderRef(ref) {
1131
+ const match = /^placeholder:([a-z0-9-]+)$/.exec(ref);
1132
+ if (!match) return null;
1133
+ const subject = match[1];
1134
+ const pool = poolForSubject(subject.replace(/-\d+$/, ""));
1135
+ return pool[mixedHash(ref) % pool.length];
1136
+ }
1137
+ function collectPlaceholderRefs(tree) {
1138
+ const seen = /* @__PURE__ */ new Set();
1139
+ for (const match of JSON.stringify(tree ?? null).matchAll(/"(placeholder:[a-z0-9-]+)"/gu)) {
1140
+ seen.add(match[1]);
1141
+ }
1142
+ return [...seen];
1143
+ }
1144
+ function buildPlaceholderMap(tree) {
1145
+ const map = {};
1146
+ const cursor = /* @__PURE__ */ new Map();
1147
+ for (const ref of collectPlaceholderRefs(tree)) {
1148
+ const subject = ref.slice("placeholder:".length).replace(/-\d+$/, "");
1149
+ const pool = poolForSubject(subject);
1150
+ const start = cursor.get(pool) ?? mixedHash(ref) % pool.length;
1151
+ map[ref] = pool[start % pool.length];
1152
+ cursor.set(pool, start + 1);
1153
+ }
1154
+ return map;
1155
+ }
1156
+
1157
+ // src/ui/ai-tree/AiTreeRenderer.tsx
547
1158
  var import_jsx_runtime = require("react/jsx-runtime");
548
1159
  function lucideByName(name) {
549
1160
  const pascal = name.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
@@ -557,6 +1168,7 @@ var typeStyle = (spec, font) => ({
557
1168
  fontWeight: spec.weight
558
1169
  });
559
1170
  var str = (value) => typeof value === "string" ? value : "";
1171
+ var cardRadius = (slots) => slots.cornerStyle === "sharp" ? 0 : AI_TREE_TOKENS.radiusCard;
560
1172
  var featureLines = (value) => value.split(/\n|<br\s*\/?>/i).map((line) => line.trim()).filter(Boolean);
561
1173
  var CHECK_MASK = `data:image/svg+xml,${encodeURIComponent(
562
1174
  '<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>'
@@ -616,6 +1228,22 @@ function accentBandContext(brand) {
616
1228
  function textAttrs(ctx, path) {
617
1229
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
618
1230
  }
1231
+ var AI_RESPONSIVE_CSS = [
1232
+ "@media (max-width: 960px) {",
1233
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
1234
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
1235
+ "}",
1236
+ "@media (max-width: 640px) {",
1237
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
1238
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
1239
+ // Group containers flatten to a column on phones; span placements come along for free.
1240
+ " [data-ai-responsive] [data-ai-group] { display: flex !important; flex-direction: column !important; }",
1241
+ " [data-ai-responsive] [data-ai-group] > * { grid-column: auto !important; }",
1242
+ " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
1243
+ " [data-ai-responsive] { overflow-x: hidden; }",
1244
+ " [data-ai-responsive] img { max-width: 100%; }",
1245
+ "}"
1246
+ ].join("\n");
619
1247
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
620
1248
  function MediaBox({
621
1249
  refValue,
@@ -628,13 +1256,17 @@ function MediaBox({
628
1256
  const url = refValue ? ctx.resolveMedia(refValue) : null;
629
1257
  const isIcon = /^(lucide|simple):/.test(refValue);
630
1258
  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" } : {};
1259
+ const editAttrs = ctx.keyFor && editPath ? {
1260
+ "data-ohw-key": ctx.keyFor(editPath),
1261
+ "data-ohw-editable": isIcon ? "icon" : "image"
1262
+ } : {};
632
1263
  if (isIcon) {
633
1264
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
634
1265
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
635
1266
  "span",
636
1267
  {
637
1268
  "data-ai-icon": refValue,
1269
+ ...editAttrs,
638
1270
  style: {
639
1271
  display: "inline-flex",
640
1272
  width: 48,
@@ -729,7 +1361,7 @@ function TextBlock({ slots, ctx, path }) {
729
1361
  }
730
1362
  function SectionHeaderBlock({ node, ctx, path }) {
731
1363
  const slots = node.slots ?? {};
732
- const align = slots.alignment === "center" ? "center" : "left";
1364
+ const align = node.align ?? (slots.alignment === "center" ? "center" : "left");
733
1365
  const children = node.children ?? [];
734
1366
  const buttonRowIdx = children.findIndex((c) => c.type === "button-row");
735
1367
  const buttonRow = buttonRowIdx >= 0 ? children[buttonRowIdx] : void 0;
@@ -773,7 +1405,7 @@ function SectionHeaderBlock({ node, ctx, path }) {
773
1405
  display: "flex",
774
1406
  gap: AI_TREE_TOKENS.spacing6,
775
1407
  marginTop: AI_TREE_TOKENS.spacing8,
776
- justifyContent: align === "center" ? "center" : "flex-start"
1408
+ justifyContent: align === "center" ? "center" : align === "right" ? "flex-end" : "flex-start"
777
1409
  },
778
1410
  children: (buttonRow.children ?? []).map((button, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
779
1411
  ButtonEl,
@@ -879,10 +1511,11 @@ function PricingCard({ node, ctx, path }) {
879
1511
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
880
1512
  "div",
881
1513
  {
1514
+ "data-ohw-card": "",
882
1515
  style: {
883
1516
  background: hasBg ? ctx.brand.palette.light : "transparent",
884
1517
  border: `1px solid ${dark}`,
885
- borderRadius: AI_TREE_TOKENS.radiusCard,
1518
+ borderRadius: cardRadius(slots),
886
1519
  padding: AI_TREE_TOKENS.paddingBlock,
887
1520
  display: "flex",
888
1521
  flexDirection: "column",
@@ -987,10 +1620,11 @@ function TestimonialCard({ node, ctx, path }) {
987
1620
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
988
1621
  "div",
989
1622
  {
1623
+ "data-ohw-card": "",
990
1624
  "data-ai-avatar-pos": avatarPos ?? void 0,
991
1625
  style: {
992
1626
  background: hasBg ? ctx.cardSurface : "transparent",
993
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1627
+ borderRadius: hasBg ? cardRadius(slots) : 0,
994
1628
  overflow: "hidden",
995
1629
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
996
1630
  minWidth: 0
@@ -1025,10 +1659,11 @@ function TeamCard({ node, ctx, path }) {
1025
1659
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1026
1660
  "div",
1027
1661
  {
1662
+ "data-ohw-card": "",
1028
1663
  "data-ai-avatar-pos": avatarPos ?? void 0,
1029
1664
  style: {
1030
1665
  background: hasBg ? ctx.cardSurface : "transparent",
1031
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1666
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1032
1667
  overflow: "hidden",
1033
1668
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1034
1669
  minWidth: 0,
@@ -1106,7 +1741,7 @@ function CardBlock({ node, ctx, path }) {
1106
1741
  editPath: `${path}.media`
1107
1742
  }
1108
1743
  ) : null;
1109
- const centered = slots.alignment === "center";
1744
+ const centered = (node.align ?? slots.alignment) === "center";
1110
1745
  const content = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1111
1746
  "div",
1112
1747
  {
@@ -1199,9 +1834,10 @@ function CardBlock({ node, ctx, path }) {
1199
1834
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1200
1835
  "div",
1201
1836
  {
1837
+ "data-ohw-card": "",
1202
1838
  style: {
1203
1839
  background: hasBg ? ctx.cardSurface : "transparent",
1204
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1840
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1205
1841
  overflow: "hidden",
1206
1842
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1207
1843
  display: horizontal ? "flex" : "block",
@@ -1229,7 +1865,7 @@ function CardBlock({ node, ctx, path }) {
1229
1865
  ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1230
1866
  "div",
1231
1867
  {
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" },
1868
+ 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
1869
  children: media
1234
1870
  }
1235
1871
  )),
@@ -1520,7 +2156,7 @@ function CollectionBlock({ node, ctx, path }) {
1520
2156
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1521
2157
  "div",
1522
2158
  {
1523
- "data-ai-grid": "",
2159
+ "data-ai-grid": String(itemsPerRow),
1524
2160
  style: {
1525
2161
  display: "grid",
1526
2162
  gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
@@ -1640,6 +2276,32 @@ function renderNode(node, ctx, path) {
1640
2276
  if (child) {
1641
2277
  return renderNode(child, ctx, `${path}.c0`);
1642
2278
  }
2279
+ if (str(slots.provider) === "map" && str(slots.query)) {
2280
+ const query = str(slots.query);
2281
+ const mapAttrs = ctx.keyFor ? {
2282
+ "data-ohw-key": ctx.keyFor(`${path}.query`),
2283
+ "data-ohw-editable": "map",
2284
+ "data-ohw-map-query": query
2285
+ } : {};
2286
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2287
+ "iframe",
2288
+ {
2289
+ ...mapAttrs,
2290
+ "data-ai-embed": "map",
2291
+ title: str(slots.title) || "Map",
2292
+ src: `https://www.google.com/maps?q=${encodeURIComponent(query)}&output=embed`,
2293
+ loading: "lazy",
2294
+ referrerPolicy: "no-referrer-when-downgrade",
2295
+ style: {
2296
+ width: "100%",
2297
+ minHeight: 320,
2298
+ border: 0,
2299
+ borderRadius: AI_TREE_TOKENS.radiusCard,
2300
+ display: "block"
2301
+ }
2302
+ }
2303
+ );
2304
+ }
1643
2305
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1644
2306
  "div",
1645
2307
  {
@@ -1796,9 +2458,14 @@ function AiTreeRenderer({
1796
2458
  const resolvedBrand = brand ?? AI_DEFAULT_BRAND;
1797
2459
  const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
1798
2460
  const blockBrand = band?.brand ?? resolvedBrand;
2461
+ const placeholderMap = buildPlaceholderMap(tree);
1799
2462
  const ctx = {
1800
2463
  brand: blockBrand,
1801
- resolveMedia: resolveMedia ?? (() => null),
2464
+ // An owner/library ref resolves through the host resolver; a `placeholder:<subject>` ref the
2465
+ // host cannot resolve falls back to real stock photography (the per-section map first, then a
2466
+ // standalone resolve), so generated galleries, image rows, and overlay backgrounds arrive with
2467
+ // photos instead of grey boxes.
2468
+ resolveMedia: (ref) => resolveMedia?.(ref) ?? placeholderMap[ref] ?? resolvePlaceholderRef(ref),
1802
2469
  cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
1803
2470
  keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
1804
2471
  sectionAlignment: (tree.settings ?? {}).alignment === "center" ? "center" : "left",
@@ -1825,11 +2492,25 @@ function AiTreeRenderer({
1825
2492
  }
1826
2493
  })();
1827
2494
  const distributed = !isOverlay && settings.textDistribution;
2495
+ const rowAlignItems = (rowAlign) => {
2496
+ if (rowAlign === "top") return "start";
2497
+ if (rowAlign === "bottom") return "end";
2498
+ if (rowAlign === "center" || rowAlign === "stretch") return rowAlign;
2499
+ if (distributed === "space-between") return "stretch";
2500
+ return (distributed ?? settings.verticalPosition) === "top" ? "start" : "center";
2501
+ };
2502
+ const cellAlignStyle = (blockAlign) => blockAlign ? {
2503
+ display: "flex",
2504
+ flexDirection: "column",
2505
+ alignItems: blockAlign === "left" ? "flex-start" : blockAlign === "right" ? "flex-end" : "center",
2506
+ textAlign: blockAlign
2507
+ } : {};
1828
2508
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1829
2509
  "section",
1830
2510
  {
1831
2511
  "data-ai-section": tree.tag ?? "",
1832
2512
  ...bgAttrs,
2513
+ "data-ai-responsive": "",
1833
2514
  style: {
1834
2515
  position: "relative",
1835
2516
  padding: `${pad}px 0`,
@@ -1840,12 +2521,13 @@ function AiTreeRenderer({
1840
2521
  color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
1841
2522
  },
1842
2523
  children: [
1843
- isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
2524
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
1844
2525
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
2526
+ isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
1845
2527
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1846
2528
  "div",
1847
2529
  {
1848
- "data-ai-container": "",
2530
+ "data-ai-section-inner": "",
1849
2531
  style: {
1850
2532
  position: "relative",
1851
2533
  maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
@@ -1856,12 +2538,12 @@ function AiTreeRenderer({
1856
2538
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1857
2539
  "div",
1858
2540
  {
1859
- "data-ai-row": "",
2541
+ "data-ai-columns": "",
1860
2542
  style: {
1861
2543
  display: "grid",
1862
2544
  gridTemplateColumns: "repeat(12, 1fr)",
1863
2545
  gap: AI_TREE_TOKENS.spacing6,
1864
- alignItems: distributed === "space-between" ? "stretch" : (distributed ?? settings.verticalPosition) === "top" ? "start" : "center",
2546
+ alignItems: rowAlignItems(row.align),
1865
2547
  marginTop: r2 > 0 ? AI_TREE_TOKENS.spacing8 : 0
1866
2548
  },
1867
2549
  children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -1871,6 +2553,8 @@ function AiTreeRenderer({
1871
2553
  style: {
1872
2554
  gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`,
1873
2555
  minWidth: 0,
2556
+ // Horizontal placement of the block's content within its column.
2557
+ ...cellAlignStyle(block.align),
1874
2558
  // space-between: each column becomes a flex column whose content spreads over
1875
2559
  // the full row height instead of clumping at the top.
1876
2560
  ...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
@@ -1893,7 +2577,7 @@ function AiTreeRenderer({
1893
2577
  var import_jsx_runtime2 = require("react/jsx-runtime");
1894
2578
  var CONTAINER_ATTR = "data-ohw-ai-generated";
1895
2579
  var REPLACED_ATTR = "data-ohw-ai-replaced-by";
1896
- var REMOVED_ATTR = "data-ohw-ai-removed";
2580
+ var REMOVED_ATTR2 = "data-ohw-ai-removed";
1897
2581
  var TEMPLATE_HIDDEN_ATTR = "data-ohw-ai-template-hidden";
1898
2582
  var CHROME_SECTION_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
1899
2583
  function readRootVar(name) {
@@ -1987,18 +2671,18 @@ function placeContainer(container, entry) {
1987
2671
  }
1988
2672
  function syncRemovedSections(state) {
1989
2673
  const removed = new Set(state.removed ?? []);
1990
- for (const el of document.querySelectorAll(`[${REMOVED_ATTR}]`)) {
2674
+ for (const el of document.querySelectorAll(`[${REMOVED_ATTR2}]`)) {
1991
2675
  const id = el.getAttribute("data-ohw-section") ?? "";
1992
2676
  if (!removed.has(id)) {
1993
2677
  el.style.removeProperty("display");
1994
- el.removeAttribute(REMOVED_ATTR);
2678
+ el.removeAttribute(REMOVED_ATTR2);
1995
2679
  }
1996
2680
  }
1997
2681
  for (const id of removed) {
1998
2682
  const section = findTemplateSection(id);
1999
2683
  if (section && !section.hasAttribute(REPLACED_ATTR)) {
2000
2684
  section.style.display = "none";
2001
- section.setAttribute(REMOVED_ATTR, "");
2685
+ section.setAttribute(REMOVED_ATTR2, "");
2002
2686
  }
2003
2687
  }
2004
2688
  }
@@ -2015,7 +2699,7 @@ function syncTemplateHidden(state, pageHasSections) {
2015
2699
  if (el.hasAttribute(CONTAINER_ATTR)) continue;
2016
2700
  if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) continue;
2017
2701
  if (el.parentElement?.closest("[data-ohw-section]")) continue;
2018
- if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR)) continue;
2702
+ if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR2)) continue;
2019
2703
  el.style.display = "none";
2020
2704
  el.setAttribute(TEMPLATE_HIDDEN_ATTR, "");
2021
2705
  }
@@ -2039,18 +2723,23 @@ function syncReplacedOriginals(state) {
2039
2723
  }
2040
2724
  }
2041
2725
  var sectionOrderIndex = /* @__PURE__ */ new Map();
2726
+ var removedSectionIds = /* @__PURE__ */ new Set();
2042
2727
  function setAiSectionOrder(raw, currentPath) {
2043
2728
  const next = /* @__PURE__ */ new Map();
2729
+ const removed = /* @__PURE__ */ new Set();
2044
2730
  if (raw) {
2045
2731
  try {
2046
2732
  const entries = JSON.parse(raw);
2047
2733
  for (const entry of entries) {
2048
- if (!entry.pagePath || entry.pagePath === currentPath) next.set(entry.instanceId, entry.order);
2734
+ if (entry.pagePath && entry.pagePath !== currentPath) continue;
2735
+ next.set(entry.instanceId, entry.order);
2736
+ if (entry.removed) removed.add(entry.instanceId);
2049
2737
  }
2050
2738
  } catch {
2051
2739
  }
2052
2740
  }
2053
2741
  sectionOrderIndex = next;
2742
+ removedSectionIds = removed;
2054
2743
  }
2055
2744
  function applyExplicitOrder(entries) {
2056
2745
  if (sectionOrderIndex.size === 0) return entries;
@@ -2086,6 +2775,18 @@ function orderByChain(sections) {
2086
2775
  for (const root of roots) visit(root);
2087
2776
  return out.length === sections.length ? out : sections;
2088
2777
  }
2778
+ function syncSoftRemovedGenerated() {
2779
+ for (const [id, section] of mounted) {
2780
+ const el = section.container;
2781
+ if (removedSectionIds.has(id)) {
2782
+ el.style.display = "none";
2783
+ el.setAttribute(REMOVED_ATTR, "");
2784
+ } else if (el.hasAttribute(REMOVED_ATTR)) {
2785
+ el.style.removeProperty("display");
2786
+ el.removeAttribute(REMOVED_ATTR);
2787
+ }
2788
+ }
2789
+ }
2089
2790
  function applyAiSectionsToDom(state, options) {
2090
2791
  if (typeof document === "undefined") return;
2091
2792
  const brandOverride = deriveBrandOverride();
@@ -2153,6 +2854,7 @@ function applyAiSectionsToDom(state, options) {
2153
2854
  syncReplacedOriginals(state);
2154
2855
  syncRemovedSections(state);
2155
2856
  syncTemplateHidden(state, pageSections.length > 0);
2857
+ syncSoftRemovedGenerated();
2156
2858
  }
2157
2859
 
2158
2860
  // src/useLinkHrefGuardian.ts
@@ -7882,6 +8584,7 @@ function MediaOverlay({
7882
8584
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
7883
8585
  );
7884
8586
  }, [isVideo]);
8587
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
7885
8588
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
7886
8589
  const box = {
7887
8590
  position: "fixed",
@@ -8011,17 +8714,17 @@ function MediaOverlay({
8011
8714
  },
8012
8715
  children: [
8013
8716
  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 }),
8014
- isVideo ? "Replace video" : "Replace image"
8717
+ replaceLabel
8015
8718
  ]
8016
8719
  }
8017
8720
  ),
8018
- replaceMode === "none" ? null : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8721
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8019
8722
  Button,
8020
8723
  {
8021
8724
  "data-ohw-media-overlay": "",
8022
8725
  variant: "outline",
8023
8726
  size: "sm",
8024
- "aria-label": isVideo ? "Replace video" : "Replace image",
8727
+ "aria-label": replaceLabel,
8025
8728
  className: "gap-1.5 cursor-pointer hover:bg-background",
8026
8729
  style: {
8027
8730
  ...OVERLAY_BUTTON_STYLE,
@@ -8044,7 +8747,7 @@ function MediaOverlay({
8044
8747
  },
8045
8748
  children: [
8046
8749
  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 }),
8047
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
8750
+ replaceMode === "full" ? replaceLabel : null
8048
8751
  ]
8049
8752
  }
8050
8753
  )
@@ -8081,219 +8784,37 @@ function CarouselOverlay({
8081
8784
  width: rect.width,
8082
8785
  height: rect.height,
8083
8786
  zIndex: 2147483646,
8084
- pointerEvents: "auto",
8085
- boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
8086
- background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
8087
- },
8088
- onClick: () => onEdit(hover.key),
8089
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
8090
- Button,
8091
- {
8092
- "data-ohw-carousel-overlay": "",
8093
- variant: "outline",
8094
- size: "sm",
8095
- className: "cursor-pointer gap-1.5 hover:bg-background",
8096
- style: OVERLAY_BUTTON_STYLE2,
8097
- onMouseDown: (e) => e.preventDefault(),
8098
- onClick: (e) => {
8099
- e.stopPropagation();
8100
- onEdit(hover.key);
8101
- },
8102
- children: [
8103
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react6.GalleryHorizontal, { size: 14 }),
8104
- "Edit gallery"
8105
- ]
8106
- }
8107
- )
8108
- }
8109
- );
8110
- }
8111
-
8112
- // src/ui/ai-section/AiSectionOverlay.tsx
8113
- var import_react8 = require("react");
8114
- var import_lucide_react7 = require("lucide-react");
8115
-
8116
- // src/lib/sections.ts
8117
- var LINK_PICKER_EXCLUDED_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
8118
- function isChromeSection(el) {
8119
- return el.matches("header, nav, footer, aside");
8120
- }
8121
- function titleCaseSectionId(id) {
8122
- return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
8123
- }
8124
- function parseSectionsFromRoot(root) {
8125
- const seen = /* @__PURE__ */ new Set();
8126
- const sections = [];
8127
- for (const el of root.querySelectorAll("[data-ohw-section]")) {
8128
- const id = el.getAttribute("data-ohw-section") ?? "";
8129
- if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
8130
- if (el.parentElement?.closest("[data-ohw-section]")) continue;
8131
- if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
8132
- continue;
8133
- seen.add(id);
8134
- const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
8135
- sections.push({ id, label });
8136
- }
8137
- return sections;
8138
- }
8139
- function collectSectionsFromDom() {
8140
- if (typeof document === "undefined") return [];
8141
- return parseSectionsFromRoot(document);
8142
- }
8143
- function parseSectionsFromHtml(html) {
8144
- const doc = new DOMParser().parseFromString(html, "text/html");
8145
- return parseSectionsFromRoot(doc);
8146
- }
8147
-
8148
- // src/lib/section-instances.ts
8149
- var SECTION_ORDER_KEY = "__ohw_section_order";
8150
- var REMOVED_ATTR2 = "data-ohw-section-removed";
8151
- function isRemovedSection(el) {
8152
- return el.hasAttribute(REMOVED_ATTR2);
8153
- }
8154
- function topLevelSections() {
8155
- return Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
8156
- (el) => !el.parentElement?.closest("[data-ohw-section]") && !isChromeSection(el) && !isRemovedSection(el)
8157
- );
8158
- }
8159
- function instanceIdOf(el) {
8160
- return el.dataset.ohwInstance ?? el.dataset.ohwSection ?? "";
8161
- }
8162
- function findByInstanceId(instanceId) {
8163
- const escapedId = CSS.escape(instanceId);
8164
- return document.querySelector(`[data-ohw-instance="${escapedId}"]`) ?? document.querySelector(`[data-ohw-section="${escapedId}"]:not([data-ohw-instance])`);
8165
- }
8166
- function planSectionMove(instanceId, targetIndex, currentPath) {
8167
- const sections = topLevelSections();
8168
- const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
8169
- if (index === -1) return null;
8170
- const dragged = sections[index];
8171
- const others = sections.filter((_, i) => i !== index);
8172
- const clamped = Math.max(0, Math.min(targetIndex, others.length));
8173
- const reordered = [...others.slice(0, clamped), dragged, ...others.slice(clamped)];
8174
- return reordered.map((el, order) => ({
8175
- instanceId: instanceIdOf(el),
8176
- type: el.getAttribute("data-ohw-section") ?? "",
8177
- order,
8178
- pagePath: currentPath
8179
- }));
8180
- }
8181
- function moveSectionInstance(instanceId, direction, currentPath) {
8182
- const sections = topLevelSections();
8183
- const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
8184
- if (index === -1) return null;
8185
- const siblingIndex = direction === "up" ? index - 1 : index + 1;
8186
- if (siblingIndex < 0 || siblingIndex >= sections.length) return null;
8187
- const entries = planSectionMove(instanceId, siblingIndex, currentPath);
8188
- if (!entries) return null;
8189
- applyPersistedOrder(entries);
8190
- return entries;
8191
- }
8192
- function syncRemovedFlags(entries) {
8193
- const removedIds = new Set(entries.filter((e) => e.removed).map((e) => e.instanceId));
8194
- document.querySelectorAll(`[${REMOVED_ATTR2}]`).forEach((el) => {
8195
- if (!removedIds.has(instanceIdOf(el))) {
8196
- el.style.removeProperty("display");
8197
- el.removeAttribute(REMOVED_ATTR2);
8198
- }
8199
- });
8200
- for (const id of removedIds) {
8201
- const el = findByInstanceId(id);
8202
- if (el) {
8203
- el.style.display = "none";
8204
- el.setAttribute(REMOVED_ATTR2, "");
8787
+ pointerEvents: "auto",
8788
+ boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
8789
+ background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
8790
+ },
8791
+ onClick: () => onEdit(hover.key),
8792
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
8793
+ Button,
8794
+ {
8795
+ "data-ohw-carousel-overlay": "",
8796
+ variant: "outline",
8797
+ size: "sm",
8798
+ className: "cursor-pointer gap-1.5 hover:bg-background",
8799
+ style: OVERLAY_BUTTON_STYLE2,
8800
+ onMouseDown: (e) => e.preventDefault(),
8801
+ onClick: (e) => {
8802
+ e.stopPropagation();
8803
+ onEdit(hover.key);
8804
+ },
8805
+ children: [
8806
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react6.GalleryHorizontal, { size: 14 }),
8807
+ "Edit gallery"
8808
+ ]
8809
+ }
8810
+ )
8205
8811
  }
8206
- }
8207
- }
8208
- function applyPersistedOrder(entries) {
8209
- syncRemovedFlags(entries);
8210
- if (entries.length === 0) return;
8211
- const sections = topLevelSections();
8212
- if (sections.length === 0) return;
8213
- const orderIndex = new Map(entries.map((e) => [e.instanceId, e.order]));
8214
- const ordered = [...sections].sort((a, b) => {
8215
- const aOrder = orderIndex.get(instanceIdOf(a));
8216
- const bOrder = orderIndex.get(instanceIdOf(b));
8217
- if (aOrder === void 0 && bOrder === void 0) return 0;
8218
- if (aOrder === void 0) return 1;
8219
- if (bOrder === void 0) return -1;
8220
- return aOrder - bOrder;
8221
- });
8222
- let prev = null;
8223
- for (const el of ordered) {
8224
- if (prev) prev.after(el);
8225
- prev = el;
8226
- }
8227
- }
8228
- function setSectionRemoved(instanceId, currentPath, existingEntries, removed) {
8229
- if (!findByInstanceId(instanceId)) return null;
8230
- const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
8231
- const allSections = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
8232
- (el) => !el.parentElement?.closest("[data-ohw-section]") && !isChromeSection(el)
8233
8812
  );
8234
- allSections.forEach((el, order) => {
8235
- const id = instanceIdOf(el);
8236
- if (!byId.has(id)) {
8237
- byId.set(id, { instanceId: id, type: el.getAttribute("data-ohw-section") ?? "", order, pagePath: currentPath });
8238
- }
8239
- });
8240
- const target = byId.get(instanceId);
8241
- if (!target) return null;
8242
- byId.set(instanceId, { ...target, removed });
8243
- const entries = Array.from(byId.values());
8244
- applyPersistedOrder(entries);
8245
- return entries;
8246
- }
8247
- function deleteSectionInstance(instanceId, currentPath, existingEntries) {
8248
- return setSectionRemoved(instanceId, currentPath, existingEntries, true);
8249
- }
8250
- function restoreSectionInstance(instanceId, currentPath, existingEntries) {
8251
- return setSectionRemoved(instanceId, currentPath, existingEntries, false);
8252
- }
8253
- function newInstanceId() {
8254
- return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `instance-${Date.now()}-${Math.random().toString(36).slice(2)}`;
8255
- }
8256
- function getPageSectionOrderEntries(raw, currentPath) {
8257
- if (!raw) return [];
8258
- try {
8259
- const entries = JSON.parse(raw);
8260
- return entries.filter((e) => !e.pagePath || e.pagePath === currentPath);
8261
- } catch {
8262
- return [];
8263
- }
8264
- }
8265
- function rekeySectionSubtree(root, instanceId) {
8266
- const suffix = `::${instanceId}`;
8267
- const rekey = (el, attr) => {
8268
- const current = el.getAttribute(attr);
8269
- if (current) el.setAttribute(attr, `${current}${suffix}`);
8270
- };
8271
- if (root.hasAttribute("data-ohw-key")) rekey(root, "data-ohw-key");
8272
- if (root.hasAttribute("data-ohw-href-key")) rekey(root, "data-ohw-href-key");
8273
- root.querySelectorAll("[data-ohw-key]").forEach((el) => rekey(el, "data-ohw-key"));
8274
- root.querySelectorAll("[data-ohw-href-key]").forEach((el) => rekey(el, "data-ohw-href-key"));
8275
- }
8276
- function initSectionInstancesFromContent(content, currentPath) {
8277
- document.querySelectorAll("[data-ohw-section]:not([data-ohw-instance])").forEach((el) => {
8278
- el.setAttribute("data-ohw-instance", el.getAttribute("data-ohw-section") ?? "");
8279
- });
8280
- const entries = getPageSectionOrderEntries(content[SECTION_ORDER_KEY], currentPath);
8281
- for (const entry of entries) {
8282
- if (entry.instanceId === entry.type) continue;
8283
- if (document.querySelector(`[data-ohw-instance="${CSS.escape(entry.instanceId)}"]`)) continue;
8284
- const original = document.querySelector(
8285
- `[data-ohw-section="${CSS.escape(entry.type)}"][data-ohw-instance="${CSS.escape(entry.type)}"]`
8286
- );
8287
- if (!original) continue;
8288
- const clone = original.cloneNode(true);
8289
- clone.setAttribute("data-ohw-instance", entry.instanceId);
8290
- rekeySectionSubtree(clone, entry.instanceId);
8291
- original.insertAdjacentElement("afterend", clone);
8292
- }
8293
- applyPersistedOrder(entries);
8294
8813
  }
8295
8814
 
8296
8815
  // src/ui/ai-section/AiSectionOverlay.tsx
8816
+ var import_react8 = require("react");
8817
+ var import_lucide_react7 = require("lucide-react");
8297
8818
  var import_jsx_runtime17 = require("react/jsx-runtime");
8298
8819
  function findSectionElement(instanceId) {
8299
8820
  const escaped = CSS.escape(instanceId);
@@ -13009,6 +13530,7 @@ function readLogoSizeState(content, placement) {
13009
13530
  function getLogoElement(el) {
13010
13531
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
13011
13532
  if (marked) return marked;
13533
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
13012
13534
  const root = el.closest("nav, [data-ohw-nav-root], footer");
13013
13535
  if (!root) return null;
13014
13536
  const anchor = el.closest("a");
@@ -14384,6 +14906,9 @@ function collectEditableNodes(extraContent, root = document) {
14384
14906
  if (el.dataset.ohwEditable === "link") {
14385
14907
  return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref3(el) };
14386
14908
  }
14909
+ if (el.dataset.ohwEditable === "map") {
14910
+ return { key: el.dataset.ohwKey ?? "", type: "map", text: el.dataset.ohwMapQuery ?? "" };
14911
+ }
14387
14912
  return {
14388
14913
  key: el.dataset.ohwKey ?? "",
14389
14914
  type: el.dataset.ohwEditable ?? "text",
@@ -14937,21 +15462,10 @@ function parseSchedulingInsertAfter(insertAfter) {
14937
15462
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
14938
15463
  };
14939
15464
  }
14940
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
14941
- const parsed = parseSchedulingInsertAfter(insertAfter);
14942
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
14943
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
14944
- return { effectiveInsertAfter, insertBefore };
14945
- }
14946
- function getSchedulingMountPoint(insertAfter) {
14947
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
14948
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
14949
- if (!anchorEl && anchor === "scheduling") {
14950
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
14951
- anchorEl = widgets.at(-1) ?? null;
14952
- }
14953
- if (!anchorEl) return null;
14954
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
15465
+ function resolveEntryAnchor(entry) {
15466
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
15467
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
15468
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
14955
15469
  }
14956
15470
  function schedulingMountDepth(insertAfter) {
14957
15471
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -14968,8 +15482,7 @@ function getPageSchedulingEntries(raw) {
14968
15482
  }
14969
15483
  }
14970
15484
  function isSchedulingWidgetMissing(entry) {
14971
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
14972
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
15485
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
14973
15486
  }
14974
15487
  function hasMissingSchedulingWidgets(entries) {
14975
15488
  return entries.some(isSchedulingWidgetMissing);
@@ -14999,16 +15512,17 @@ function initSectionsFromContent(content, removeExisting = false) {
14999
15512
  } catch {
15000
15513
  }
15001
15514
  }
15002
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
15003
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
15004
- const sectionId = schedulingSectionId(effectiveInsertAfter);
15515
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
15516
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
15517
+ const sectionId = schedulingSectionId(widgetId);
15005
15518
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
15006
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
15007
- if (!mountPoint) return false;
15519
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
15520
+ if (!anchorEl) return false;
15521
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
15008
15522
  const container = document.createElement("div");
15009
15523
  container.dataset.ohwSectionContainer = "scheduling";
15010
- if (insertBefore) {
15011
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
15524
+ if (beforeId) {
15525
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
15012
15526
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
15013
15527
  if (!beforePoint) return false;
15014
15528
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -15019,19 +15533,25 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
15019
15533
  }
15020
15534
  tail.insertAdjacentElement("afterend", container);
15021
15535
  }
15022
- const root = (0, import_client2.createRoot)(container);
15023
- (0, import_react_dom3.flushSync)(() => {
15024
- root.render(
15025
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
15026
- SchedulingWidget,
15027
- {
15028
- notifyOnConnect,
15029
- initialScheduleId: scheduleId,
15030
- insertAfter: effectiveInsertAfter
15031
- }
15032
- )
15033
- );
15034
- });
15536
+ try {
15537
+ const root = (0, import_client2.createRoot)(container);
15538
+ (0, import_react_dom3.flushSync)(() => {
15539
+ root.render(
15540
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
15541
+ SchedulingWidget,
15542
+ {
15543
+ notifyOnConnect,
15544
+ initialScheduleId: scheduleId,
15545
+ insertAfter: widgetId
15546
+ }
15547
+ )
15548
+ );
15549
+ });
15550
+ } catch (err) {
15551
+ console.error("[ow:scheduling] render threw", err);
15552
+ container.remove();
15553
+ return false;
15554
+ }
15035
15555
  const tracker = getSectionsTracker();
15036
15556
  let sections = [];
15037
15557
  try {
@@ -15039,10 +15559,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
15039
15559
  } catch {
15040
15560
  }
15041
15561
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
15042
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
15562
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
15043
15563
  sections.push({
15044
15564
  type: "scheduling",
15045
- insertAfter: effectiveInsertAfter,
15565
+ insertAfter: widgetId,
15566
+ anchorId,
15567
+ beforeId: beforeId ?? null,
15046
15568
  pagePath: window.location.pathname,
15047
15569
  ...scheduleId ? { scheduleId } : {}
15048
15570
  });
@@ -15056,7 +15578,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
15056
15578
  for (let i = pending.length - 1; i >= 0; i--) {
15057
15579
  const entry = pending[i];
15058
15580
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
15059
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId ?? null)) {
15581
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
15582
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
15060
15583
  pending.splice(i, 1);
15061
15584
  }
15062
15585
  }
@@ -15148,7 +15671,7 @@ var EDITOR_CHROME_SELECTOR = '[data-ohw-toolbar], [data-ohw-form-toolbar], [data
15148
15671
  function isOverEditorChrome(x, y) {
15149
15672
  return document.elementsFromPoint(x, y).some((el) => el instanceof HTMLElement && el.matches(EDITOR_CHROME_SELECTOR));
15150
15673
  }
15151
- 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"])';
15674
+ 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"])';
15152
15675
  function getVideoEl2(el) {
15153
15676
  return el instanceof HTMLVideoElement ? el : el.querySelector("video");
15154
15677
  }
@@ -15204,6 +15727,12 @@ function applyVideoSettingNode(key, val) {
15204
15727
  });
15205
15728
  return true;
15206
15729
  }
15730
+ function applyMapQuery(el, val) {
15731
+ if (!(el instanceof HTMLIFrameElement)) return;
15732
+ const nextSrc = `https://www.google.com/maps?q=${encodeURIComponent(val)}&output=embed`;
15733
+ if (el.src !== nextSrc) el.src = nextSrc;
15734
+ el.setAttribute("data-ohw-map-query", val);
15735
+ }
15207
15736
  function applyLinkByKey(key, val) {
15208
15737
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
15209
15738
  if (el.dataset.ohwEditable === "link") applyLinkHref(el, val);
@@ -15214,6 +15743,11 @@ function applyLinkByKey(key, val) {
15214
15743
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
15215
15744
  }
15216
15745
  }
15746
+ function isInsideLinkEditor(target) {
15747
+ return Boolean(
15748
+ 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"]')
15749
+ );
15750
+ }
15217
15751
  function isInsideFloatingPanel(target) {
15218
15752
  return Boolean(target.closest("[data-ohw-floating-panel]"));
15219
15753
  }
@@ -15221,11 +15755,6 @@ function isPointOverFloatingPanel(clientX, clientY) {
15221
15755
  const el = document.elementFromPoint(clientX, clientY);
15222
15756
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
15223
15757
  }
15224
- function isInsideLinkEditor(target) {
15225
- return Boolean(
15226
- 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"]')
15227
- );
15228
- }
15229
15758
  function getHrefKeyFromElement(el) {
15230
15759
  if (!el) return null;
15231
15760
  const anchor = el.closest("[data-ohw-href-key]");
@@ -15484,7 +16013,7 @@ function getNavigationSelectionParent(el) {
15484
16013
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
15485
16014
  return getFooterLinksContainer();
15486
16015
  }
15487
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
16016
+ 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)) {
15488
16017
  return getNavigationRoot(el);
15489
16018
  }
15490
16019
  return null;
@@ -15699,7 +16228,6 @@ var ICONS = {
15699
16228
  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"/>',
15700
16229
  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"/>'
15701
16230
  };
15702
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
15703
16231
  var SELECTION_CHROME_GAP2 = 4;
15704
16232
  var TOOLBAR_STROKE_GAP2 = 4;
15705
16233
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -16079,6 +16607,7 @@ function StateToggle({
16079
16607
  );
16080
16608
  }
16081
16609
  var contentCache = /* @__PURE__ */ new Map();
16610
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
16082
16611
  var brandingCache = /* @__PURE__ */ new Map();
16083
16612
  var OHW_LOADER_STYLE = {
16084
16613
  position: "fixed",
@@ -16608,13 +17137,6 @@ function OhhwellsBridge() {
16608
17137
  const [isItemDragging, setIsItemDragging] = (0, import_react17.useState)(false);
16609
17138
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react17.useState)(false);
16610
17139
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
16611
- const [floatingPanel, setFloatingPanel] = (0, import_react17.useState)(null);
16612
- const floatingPanelOpenRef = (0, import_react17.useRef)(false);
16613
- floatingPanelOpenRef.current = floatingPanel !== null;
16614
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react17.useState)(null);
16615
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react17.useState)(null);
16616
- const [editorViewport, setEditorViewport] = (0, import_react17.useState)("desktop");
16617
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react17.useState)(null);
16618
17140
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react17.useState)(null);
16619
17141
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react17.useState)(null);
16620
17142
  const footerDragRef = (0, import_react17.useRef)(null);
@@ -16632,6 +17154,13 @@ function OhhwellsBridge() {
16632
17154
  const brandKitRef = (0, import_react17.useRef)("");
16633
17155
  const stylesRef = (0, import_react17.useRef)("");
16634
17156
  const pendingDeleteUndoRef = (0, import_react17.useRef)(null);
17157
+ const [floatingPanel, setFloatingPanel] = (0, import_react17.useState)(null);
17158
+ const floatingPanelOpenRef = (0, import_react17.useRef)(false);
17159
+ const setFloatingPanelRef = (0, import_react17.useRef)(setFloatingPanel);
17160
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react17.useState)(null);
17161
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react17.useState)(null);
17162
+ const [editorViewport, setEditorViewport] = (0, import_react17.useState)("desktop");
17163
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react17.useState)(null);
16635
17164
  const [sitePages, setSitePages] = (0, import_react17.useState)([]);
16636
17165
  const [sectionsByPath, setSectionsByPath] = (0, import_react17.useState)({});
16637
17166
  const sectionsPrefetchGenRef = (0, import_react17.useRef)(0);
@@ -16640,7 +17169,18 @@ function OhhwellsBridge() {
16640
17169
  const linkPopoverOpenRef = (0, import_react17.useRef)(false);
16641
17170
  const linkPopoverGraceUntilRef = (0, import_react17.useRef)(0);
16642
17171
  setLinkPopoverRef.current = setLinkPopover;
17172
+ setFloatingPanelRef.current = setFloatingPanel;
16643
17173
  linkPopoverSessionRef.current = linkPopover;
17174
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
17175
+ (0, import_react17.useEffect)(() => {
17176
+ const syncViewport = () => {
17177
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
17178
+ setEditorViewport((prev) => prev === next ? prev : next);
17179
+ };
17180
+ syncViewport();
17181
+ window.addEventListener("resize", syncViewport);
17182
+ return () => window.removeEventListener("resize", syncViewport);
17183
+ }, []);
16644
17184
  const {
16645
17185
  navDragRef,
16646
17186
  navDropSlots,
@@ -17965,17 +18505,19 @@ function OhhwellsBridge() {
17965
18505
  }
17966
18506
  if (typeof content[STYLE_STORE_KEY] === "string") {
17967
18507
  stylesRef.current = content[STYLE_STORE_KEY];
18508
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
17968
18509
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
17969
18510
  }
17970
18511
  applyBrandChrome(content);
18512
+ initSectionInstancesFromContent(content, window.location.pathname);
17971
18513
  for (const [key, val] of Object.entries(content)) {
17972
18514
  if (key === "__ohw_sections") continue;
17973
18515
  if (key === AI_SECTIONS_KEY) continue;
18516
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
18517
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
17974
18518
  if (key === BRAND_KIT_KEY) continue;
17975
18519
  if (key === STYLE_STORE_KEY) continue;
17976
18520
  if (BRAND_CHROME_KEYS.has(key)) continue;
17977
- if (key === LOGO_PLACEHOLDER_KEY) continue;
17978
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
17979
18521
  if (applyVideoSettingNode(key, val)) continue;
17980
18522
  if (applyCarouselNode(key, val)) continue;
17981
18523
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -18003,6 +18545,8 @@ function OhhwellsBridge() {
18003
18545
  }
18004
18546
  } else if (el.dataset.ohwEditable === "link") {
18005
18547
  applyLinkHref(el, val);
18548
+ } else if (el.dataset.ohwEditable === "map") {
18549
+ applyMapQuery(el, val);
18006
18550
  } else if (el.dataset.ohwEditable === "icon") {
18007
18551
  applyIconMarkup(el, val);
18008
18552
  } else if (el.dataset.ohwEditable === "form") {
@@ -18023,7 +18567,6 @@ function OhhwellsBridge() {
18023
18567
  if (isEditModeRef.current) requestMissingSocialIconsRef.current();
18024
18568
  enforceLinkHrefs();
18025
18569
  initSectionsFromContent(content, true);
18026
- initSectionInstancesFromContent(content, window.location.pathname);
18027
18570
  sectionsLoadedRef.current = true;
18028
18571
  pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
18029
18572
  if (imageLoads.length === 0) return Promise.resolve();
@@ -18042,7 +18585,9 @@ function OhhwellsBridge() {
18042
18585
  let cancelled = false;
18043
18586
  setFetchState("loading");
18044
18587
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
18045
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18588
+ const initialPath = pathname;
18589
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
18590
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18046
18591
  if (cancelled) return;
18047
18592
  const content = data?.content ?? {};
18048
18593
  const branding = Boolean(data?.showBranding);
@@ -18176,16 +18721,17 @@ function OhhwellsBridge() {
18176
18721
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
18177
18722
  }
18178
18723
  if (typeof content[STYLE_STORE_KEY] === "string") {
18724
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
18179
18725
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
18180
18726
  }
18181
18727
  for (const [key, val] of Object.entries(content)) {
18182
18728
  if (key === "__ohw_sections") continue;
18183
18729
  if (key === AI_SECTIONS_KEY) continue;
18730
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
18731
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
18184
18732
  if (key === BRAND_KIT_KEY) continue;
18185
18733
  if (key === STYLE_STORE_KEY) continue;
18186
18734
  if (BRAND_CHROME_KEYS.has(key)) continue;
18187
- if (key === LOGO_PLACEHOLDER_KEY) continue;
18188
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
18189
18735
  if (applyVideoSettingNode(key, val)) continue;
18190
18736
  if (applyCarouselNode(key, val)) continue;
18191
18737
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -18200,6 +18746,8 @@ function OhhwellsBridge() {
18200
18746
  if (video && video.src !== val) applyVideoSrc(video, val);
18201
18747
  } else if (el.dataset.ohwEditable === "link") {
18202
18748
  applyLinkHref(el, val);
18749
+ } else if (el.dataset.ohwEditable === "map") {
18750
+ applyMapQuery(el, val);
18203
18751
  } else if (el.dataset.ohwEditable === "form") {
18204
18752
  } else if (isIconMarkupValue(val)) {
18205
18753
  } else if (el.innerHTML !== val) {
@@ -18231,6 +18779,17 @@ function OhhwellsBridge() {
18231
18779
  debounceTimer = setTimeout(applyFromCache, 150);
18232
18780
  };
18233
18781
  applyFromCache();
18782
+ const pathCacheKey = `${subdomain}::${pathname}`;
18783
+ if (!fetchedContentPaths.has(pathCacheKey)) {
18784
+ fetchedContentPaths.add(pathCacheKey);
18785
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
18786
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18787
+ if (!data?.content) return;
18788
+ contentCache.set(subdomain, data.content);
18789
+ applyFromCache();
18790
+ }).catch(() => {
18791
+ });
18792
+ }
18234
18793
  observer = new MutationObserver(scheduleApply);
18235
18794
  observer.observe(document.body, { childList: true, subtree: true });
18236
18795
  return () => {
@@ -18346,26 +18905,11 @@ function OhhwellsBridge() {
18346
18905
  const t2 = setTimeout(measure, 500);
18347
18906
  const ro = new ResizeObserver(schedule);
18348
18907
  ro.observe(document.body);
18349
- let lastWidth = window.innerWidth;
18350
- let resizeTimers = [];
18351
- const clearResizeTimers = () => {
18352
- resizeTimers.forEach(clearTimeout);
18353
- resizeTimers = [];
18354
- };
18355
- const handleResize = () => {
18356
- if (window.innerWidth === lastWidth) return;
18357
- lastWidth = window.innerWidth;
18358
- clearResizeTimers();
18359
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
18360
- };
18361
- window.addEventListener("resize", handleResize);
18362
18908
  return () => {
18363
18909
  clearTimeout(t1);
18364
18910
  clearTimeout(t2);
18365
18911
  if (raf != null) cancelAnimationFrame(raf);
18366
18912
  ro.disconnect();
18367
- clearResizeTimers();
18368
- window.removeEventListener("resize", handleResize);
18369
18913
  };
18370
18914
  }, [pathname, isEditMode, postToParent2]);
18371
18915
  (0, import_react17.useEffect)(() => {
@@ -18611,9 +19155,6 @@ function OhhwellsBridge() {
18611
19155
  if (target.closest("[data-ohw-state-toggle]")) return;
18612
19156
  if (target.closest("[data-ohw-max-badge]")) return;
18613
19157
  if (isInsideLinkEditor(target)) return;
18614
- if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
18615
- clearMediaSelectionRef.current();
18616
- }
18617
19158
  if (isInsideFloatingPanel(target)) return;
18618
19159
  if (target.closest("[data-ohw-form-toolbar]")) return;
18619
19160
  if (target.closest(
@@ -18621,6 +19162,9 @@ function OhhwellsBridge() {
18621
19162
  )) {
18622
19163
  return;
18623
19164
  }
19165
+ if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
19166
+ clearMediaSelectionRef.current();
19167
+ }
18624
19168
  {
18625
19169
  const formEl = getFormElement(target);
18626
19170
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -18772,14 +19316,6 @@ function OhhwellsBridge() {
18772
19316
  }
18773
19317
  const clickedButton = findClosestButtonLike(target);
18774
19318
  const buttonOnMedia = Boolean(clickedButton && isMediaEditable(editable) && editable.contains(clickedButton));
18775
- console.log("[click-debug]", {
18776
- editableType: editable.dataset.ohwEditable,
18777
- editableTag: editable.tagName,
18778
- targetTag: target.tagName,
18779
- clickedButtonTag: clickedButton?.tagName ?? null,
18780
- buttonOnMedia,
18781
- isMediaEditableEditable: isMediaEditable(editable)
18782
- });
18783
19319
  if (isMediaEditable(editable) && !buttonOnMedia) {
18784
19320
  e.preventDefault();
18785
19321
  e.stopPropagation();
@@ -18806,11 +19342,6 @@ function OhhwellsBridge() {
18806
19342
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
18807
19343
  const hrefCtx = getHrefKeyFromElement(hrefLookupTarget);
18808
19344
  const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
18809
- console.log("[click-debug 2]", {
18810
- hrefLookupTargetTag: hrefLookupTarget.tagName,
18811
- hrefCtx: hrefCtx ? { key: hrefCtx.key } : null,
18812
- navAnchorTag: navAnchor?.tagName ?? null
18813
- });
18814
19345
  if (navAnchor) {
18815
19346
  e.preventDefault();
18816
19347
  e.stopPropagation();
@@ -18980,6 +19511,9 @@ function OhhwellsBridge() {
18980
19511
  setHoveredItemRect(null);
18981
19512
  hoveredNavContainerRef.current = null;
18982
19513
  setHoveredNavContainerRect(null);
19514
+ siblingHintElRef.current = null;
19515
+ setSiblingHintRect(null);
19516
+ setSiblingHintRects([]);
18983
19517
  return;
18984
19518
  }
18985
19519
  {
@@ -19098,7 +19632,6 @@ function OhhwellsBridge() {
19098
19632
  hoveredNavContainerRef.current = null;
19099
19633
  setHoveredNavContainerRect(null);
19100
19634
  hoveredItemElRef.current = editable;
19101
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
19102
19635
  }
19103
19636
  }
19104
19637
  }
@@ -19395,7 +19928,7 @@ function OhhwellsBridge() {
19395
19928
  }
19396
19929
  };
19397
19930
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
19398
- if (linkPopoverOpenRef.current) {
19931
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
19399
19932
  if (hoveredImageRef.current) {
19400
19933
  hoveredImageRef.current = null;
19401
19934
  hoveredImageHasTextOverlapRef.current = false;
@@ -19760,8 +20293,7 @@ function OhhwellsBridge() {
19760
20293
  };
19761
20294
  const handleMouseMove = (e) => {
19762
20295
  const { clientX, clientY } = e;
19763
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
19764
- if (isOverEditorChrome(clientX, clientY)) {
20296
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
19765
20297
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
19766
20298
  formHoverElRef.current = null;
19767
20299
  setFormHoverRect(null);
@@ -19769,6 +20301,12 @@ function OhhwellsBridge() {
19769
20301
  setHoveredItemRect(null);
19770
20302
  hoveredNavContainerRef.current = null;
19771
20303
  setHoveredNavContainerRect(null);
20304
+ siblingHintElRef.current = null;
20305
+ setSiblingHintRect(null);
20306
+ setSiblingHintRects([]);
20307
+ dismissImageHover();
20308
+ clearImageHover();
20309
+ setSectionGap(null);
19772
20310
  return;
19773
20311
  }
19774
20312
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -19780,7 +20318,11 @@ function OhhwellsBridge() {
19780
20318
  if (e.data?.type !== "ow:pointer-sync") return;
19781
20319
  const { clientX, clientY } = e.data;
19782
20320
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
19783
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
20321
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
20322
+ dismissImageHover();
20323
+ clearImageHover();
20324
+ return;
20325
+ }
19784
20326
  if (probeSocialsRowAt(clientX, clientY)) return;
19785
20327
  probeSectionGapAt(clientX, clientY);
19786
20328
  probeImageAt(clientX, clientY);
@@ -20059,6 +20601,44 @@ function OhhwellsBridge() {
20059
20601
  if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
20060
20602
  }, 400));
20061
20603
  };
20604
+ const reapCommittedAiSections = (excludeIds) => {
20605
+ const aiState = parseAiSectionsState(aiSectionsRef.current);
20606
+ if (aiState.sections.length === 0) return [];
20607
+ let orderEntries = [];
20608
+ try {
20609
+ const parsed = JSON.parse(editContentRef.current[SECTION_ORDER_KEY] ?? "[]");
20610
+ if (Array.isArray(parsed)) orderEntries = parsed;
20611
+ } catch {
20612
+ return [];
20613
+ }
20614
+ const removedIds = orderEntries.filter((entry) => entry && typeof entry.instanceId === "string" && entry.removed).map((entry) => entry.instanceId);
20615
+ if (removedIds.length === 0) return [];
20616
+ const result = reapRemovedAiSections(aiState, parseStyleStore(stylesRef.current), removedIds, excludeIds);
20617
+ if (!result.changed) return [];
20618
+ const nodes = [];
20619
+ aiSectionsRef.current = serializeAiSectionsState(result.state);
20620
+ nodes.push({ key: AI_SECTIONS_KEY, text: aiSectionsRef.current });
20621
+ const reaped = new Set(result.reapedIds);
20622
+ const nextOrderJson = JSON.stringify(orderEntries.filter((entry) => !reaped.has(entry.instanceId)));
20623
+ editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: nextOrderJson };
20624
+ setAiSectionOrder(nextOrderJson, window.location.pathname);
20625
+ nodes.push({ key: SECTION_ORDER_KEY, text: nextOrderJson });
20626
+ if (result.store) {
20627
+ stylesRef.current = JSON.stringify(result.store);
20628
+ nodes.push({ key: STYLE_STORE_KEY, text: stylesRef.current });
20629
+ }
20630
+ const nextContent = { ...editContentRef.current };
20631
+ for (const key of Object.keys(nextContent)) {
20632
+ if (key.startsWith(AI_SLOT_KEY_PREFIX) && result.slotPrefixes.some((prefix) => key.startsWith(prefix)) && nextContent[key] !== "") {
20633
+ nextContent[key] = "";
20634
+ nodes.push({ key, text: "" });
20635
+ }
20636
+ }
20637
+ editContentRef.current = nextContent;
20638
+ applyAiSectionsToDom(result.state);
20639
+ applyStylesToDom(parseStyleStore(stylesRef.current));
20640
+ return nodes;
20641
+ };
20062
20642
  const handleHydrate = (e) => {
20063
20643
  if (e.data?.type !== "ow:hydrate") return;
20064
20644
  const content = e.data.content;
@@ -20077,9 +20657,11 @@ function OhhwellsBridge() {
20077
20657
  }
20078
20658
  if (typeof content[STYLE_STORE_KEY] === "string") {
20079
20659
  stylesRef.current = content[STYLE_STORE_KEY];
20660
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
20080
20661
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
20081
20662
  }
20082
20663
  applyBrandChrome(content);
20664
+ initSectionInstancesFromContent(content, window.location.pathname);
20083
20665
  let sectionsJson = null;
20084
20666
  for (const [key, val] of Object.entries(content)) {
20085
20667
  if (key === "__ohw_sections") {
@@ -20087,11 +20669,11 @@ function OhhwellsBridge() {
20087
20669
  continue;
20088
20670
  }
20089
20671
  if (key === AI_SECTIONS_KEY) continue;
20672
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
20673
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
20090
20674
  if (key === BRAND_KIT_KEY) continue;
20091
20675
  if (key === STYLE_STORE_KEY) continue;
20092
20676
  if (BRAND_CHROME_KEYS.has(key)) continue;
20093
- if (key === LOGO_PLACEHOLDER_KEY) continue;
20094
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
20095
20677
  if (applyVideoSettingNode(key, val)) continue;
20096
20678
  if (applyCarouselNode(key, val)) continue;
20097
20679
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -20105,6 +20687,8 @@ function OhhwellsBridge() {
20105
20687
  if (video && video.src !== val) applyVideoSrc(video, val);
20106
20688
  } else if (el.dataset.ohwEditable === "link") {
20107
20689
  applyLinkHref(el, val);
20690
+ } else if (el.dataset.ohwEditable === "map") {
20691
+ applyMapQuery(el, val);
20108
20692
  } else if (el.dataset.ohwEditable === "icon") {
20109
20693
  applyIconMarkup(el, val);
20110
20694
  } else if (isIconMarkupValue(val)) {
@@ -20121,12 +20705,16 @@ function OhhwellsBridge() {
20121
20705
  sectionsLoadedRef.current = true;
20122
20706
  pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
20123
20707
  }
20124
- initSectionInstancesFromContent(content, window.location.pathname);
20125
20708
  editContentRef.current = { ...editContentRef.current, ...content };
20126
20709
  reconcileNavbarItemsFromContent(editContentRef.current);
20127
20710
  reconcileFooterOrderFromContent(editContentRef.current);
20128
20711
  syncNavigationDragCursorAttrs();
20129
20712
  enforceLinkHrefs();
20713
+ const hydrateReapExclude = /* @__PURE__ */ new Set();
20714
+ const hydratePendingUndo = pendingDeleteUndoRef.current;
20715
+ if (hydratePendingUndo?.sectionInstanceId) hydrateReapExclude.add(hydratePendingUndo.sectionInstanceId);
20716
+ const reapNodes = reapCommittedAiSections(hydrateReapExclude);
20717
+ if (reapNodes.length > 0) postToParentRef.current({ type: "ow:change", nodes: reapNodes });
20130
20718
  const hydratedHeight = document.body.scrollHeight;
20131
20719
  if (hydratedHeight > 50) postToParentRef.current({ type: "ow:height", height: hydratedHeight });
20132
20720
  postToParentRef.current({ type: "ow:hydrate-done" });
@@ -20266,12 +20854,35 @@ function OhhwellsBridge() {
20266
20854
  window.addEventListener("message", handleAiSetBrand);
20267
20855
  const handleAiSetStyles = (e) => {
20268
20856
  if (e.data?.type !== "ow:ai-set-styles") return;
20269
- const value = typeof e.data.value === "string" ? e.data.value : "";
20857
+ let value = typeof e.data.value === "string" ? e.data.value : "";
20270
20858
  const previous = stylesRef.current;
20859
+ let previousSections;
20860
+ const store = parseStyleStore(value);
20861
+ if (store) {
20862
+ const folded = foldAlignIntoTrees(parseAiSectionsState(aiSectionsRef.current), store);
20863
+ if (folded.changed) {
20864
+ const nextSections = serializeAiSectionsState(folded.state);
20865
+ if (nextSections !== aiSectionsRef.current) {
20866
+ previousSections = aiSectionsRef.current;
20867
+ aiSectionsRef.current = nextSections;
20868
+ applyAiSectionsToDom(folded.state);
20869
+ postToParentRef.current({
20870
+ type: "ow:change",
20871
+ nodes: [{ key: AI_SECTIONS_KEY, text: nextSections }]
20872
+ });
20873
+ }
20874
+ value = JSON.stringify(folded.store);
20875
+ }
20876
+ }
20271
20877
  stylesRef.current = value;
20272
20878
  applyStylesToDom(parseStyleStore(value));
20273
20879
  postToParentRef.current({ type: "ow:change", nodes: [{ key: STYLE_STORE_KEY, text: value }] });
20274
- postToParentRef.current({ type: "ow:ai-styles-applied", previous, value });
20880
+ postToParentRef.current({
20881
+ type: "ow:ai-styles-applied",
20882
+ previous,
20883
+ value,
20884
+ ...previousSections !== void 0 ? { previousSections } : {}
20885
+ });
20275
20886
  };
20276
20887
  window.addEventListener("message", handleAiSetStyles);
20277
20888
  const handleGetBrand = (e) => {
@@ -20310,6 +20921,7 @@ function OhhwellsBridge() {
20310
20921
  if (!entries) return;
20311
20922
  const orderJson = JSON.stringify(entries);
20312
20923
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
20924
+ setAiSectionOrder(orderJson, window.location.pathname);
20313
20925
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20314
20926
  aiSectionApiRef.current?.clear();
20315
20927
  window.dispatchEvent(new Event("resize"));
@@ -20318,6 +20930,7 @@ function OhhwellsBridge() {
20318
20930
  const actionId = newInstanceId();
20319
20931
  pendingDeleteUndoRef.current = {
20320
20932
  actionId,
20933
+ sectionInstanceId: instanceId,
20321
20934
  restore: () => {
20322
20935
  const restoredEntries = getPageSectionOrderEntries(
20323
20936
  editContentRef.current[SECTION_ORDER_KEY],
@@ -20327,6 +20940,7 @@ function OhhwellsBridge() {
20327
20940
  if (!restored) return;
20328
20941
  const restoredJson = JSON.stringify(restored);
20329
20942
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: restoredJson };
20943
+ setAiSectionOrder(restoredJson, window.location.pathname);
20330
20944
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: restoredJson }] });
20331
20945
  window.dispatchEvent(new Event("resize"));
20332
20946
  const restoreHeight = document.body.scrollHeight;
@@ -20343,6 +20957,34 @@ function OhhwellsBridge() {
20343
20957
  });
20344
20958
  };
20345
20959
  window.addEventListener("message", handleDeleteSection);
20960
+ const handleDuplicateSection = (e) => {
20961
+ if (e.data?.type !== "ow:duplicate-section") return;
20962
+ const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
20963
+ if (!instanceId) return;
20964
+ const newId = newInstanceId();
20965
+ const currentEntries = getPageSectionOrderEntries(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname);
20966
+ const result = duplicateSectionInstance(instanceId, newId, window.location.pathname, currentEntries);
20967
+ if (!result) return;
20968
+ const { entries, keyRekeys } = result;
20969
+ const orderJson = JSON.stringify(entries);
20970
+ const nodes = [{ key: SECTION_ORDER_KEY, text: orderJson }];
20971
+ for (const { from, to } of keyRekeys) {
20972
+ const inherited = editContentRef.current[from];
20973
+ if (inherited !== void 0) nodes.push({ key: to, text: inherited });
20974
+ }
20975
+ editContentRef.current = {
20976
+ ...editContentRef.current,
20977
+ ...Object.fromEntries(nodes.map(({ key, text }) => [key, text]))
20978
+ };
20979
+ setAiSectionOrder(orderJson, window.location.pathname);
20980
+ postToParentRef.current({ type: "ow:change", nodes });
20981
+ window.dispatchEvent(new Event("resize"));
20982
+ const duplicateHeight = document.body.scrollHeight;
20983
+ if (duplicateHeight > 50) postToParentRef.current({ type: "ow:height", height: duplicateHeight });
20984
+ const clone = document.querySelector(`[data-ohw-instance="${CSS.escape(newId)}"]`);
20985
+ if (clone) aiSectionApiRef.current?.selectFromElement(clone);
20986
+ };
20987
+ window.addEventListener("message", handleDuplicateSection);
20346
20988
  const handleDeactivate = (e) => {
20347
20989
  if (e.data?.type !== "ow:deactivate") return;
20348
20990
  if (document.documentElement.hasAttribute("data-ohw-panel-dragging")) return;
@@ -20352,6 +20994,12 @@ function OhhwellsBridge() {
20352
20994
  closeLinkPopoverRef.current();
20353
20995
  return;
20354
20996
  }
20997
+ if (floatingPanelOpenRef.current) {
20998
+ setFloatingPanelRef.current(null);
20999
+ deselectRef.current();
21000
+ deactivateRef.current();
21001
+ return;
21002
+ }
20355
21003
  deselectRef.current();
20356
21004
  deactivateRef.current();
20357
21005
  clearMediaSelectionRef.current();
@@ -20597,6 +21245,10 @@ function OhhwellsBridge() {
20597
21245
  };
20598
21246
  const handleSave = (e) => {
20599
21247
  if (e.data?.type !== "ow:save") return;
21248
+ const pendingUndo = pendingDeleteUndoRef.current;
21249
+ const reapExclude = /* @__PURE__ */ new Set();
21250
+ if (pendingUndo?.sectionInstanceId) reapExclude.add(pendingUndo.sectionInstanceId);
21251
+ const reapNodes = reapCommittedAiSections(reapExclude);
20600
21252
  const nodes = collectEditableNodes(editContentRef.current);
20601
21253
  const tracker = document.querySelector("[data-ohw-sections-tracker]");
20602
21254
  if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
@@ -20616,6 +21268,11 @@ function OhhwellsBridge() {
20616
21268
  const text = specs.length ? JSON.stringify(specs) : editContentRef.current[fieldsKey(formKey)];
20617
21269
  if (text) nodes.push({ key: fieldsKey(formKey), type: "text", text });
20618
21270
  });
21271
+ for (const reapNode of reapNodes) {
21272
+ if (reapNode.key.startsWith(AI_SLOT_KEY_PREFIX) && !nodes.some((n) => n.key === reapNode.key)) {
21273
+ nodes.push({ key: reapNode.key, type: "text", text: reapNode.text });
21274
+ }
21275
+ }
20619
21276
  postToParentRef.current({ type: "ow:save-result", nodes });
20620
21277
  };
20621
21278
  const handleInsertSection = (e) => {
@@ -20626,8 +21283,12 @@ function OhhwellsBridge() {
20626
21283
  if (inserted) {
20627
21284
  const tracker = getSectionsTracker();
20628
21285
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
20629
- const h = document.body.scrollHeight;
20630
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
21286
+ const reportHeight = () => {
21287
+ const h = document.body.scrollHeight;
21288
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
21289
+ };
21290
+ reportHeight();
21291
+ setTimeout(reportHeight, 500);
20631
21292
  }
20632
21293
  };
20633
21294
  const handleSwitchSchedule = (e) => {
@@ -21029,11 +21690,12 @@ function OhhwellsBridge() {
21029
21690
  window.removeEventListener("message", handleMoveSection);
21030
21691
  window.removeEventListener("message", handlePanelDragging);
21031
21692
  window.removeEventListener("message", handleDeleteSection);
21693
+ window.removeEventListener("message", handleDuplicateSection);
21032
21694
  window.removeEventListener("message", handleDeactivate);
21033
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
21034
21695
  window.removeEventListener("message", handleToastAction);
21035
21696
  window.removeEventListener("message", handleFormCount);
21036
21697
  window.removeEventListener("message", handleUiEscape);
21698
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
21037
21699
  autoSaveTimers.current.forEach(clearTimeout);
21038
21700
  autoSaveTimers.current.clear();
21039
21701
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -21236,7 +21898,7 @@ function OhhwellsBridge() {
21236
21898
  postToParent2({
21237
21899
  type: "ow:ready",
21238
21900
  version: "1",
21239
- bridgeVersion: "0.1.84",
21901
+ bridgeVersion: "0.1.86",
21240
21902
  path: pathname,
21241
21903
  nodes: collectEditableNodes(editContentRef.current),
21242
21904
  sections
@@ -22155,6 +22817,59 @@ function OhhwellsBridge() {
22155
22817
  ) : null
22156
22818
  ] });
22157
22819
  }
22820
+
22821
+ // src/ui/EmptySection.tsx
22822
+ var import_link = __toESM(require("next/link"), 1);
22823
+ var import_jsx_runtime34 = require("react/jsx-runtime");
22824
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
22825
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
22826
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
22827
+ "p",
22828
+ {
22829
+ style: {
22830
+ fontFamily: "var(--brand-font-body)",
22831
+ fontSize: "0.75rem",
22832
+ fontWeight: 500,
22833
+ letterSpacing: "0.15em",
22834
+ textTransform: "uppercase",
22835
+ color: "var(--brand-accent)",
22836
+ marginBottom: "1.5rem"
22837
+ },
22838
+ 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" }) })
22839
+ }
22840
+ ),
22841
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
22842
+ "h1",
22843
+ {
22844
+ style: {
22845
+ fontFamily: "var(--brand-font-heading)",
22846
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
22847
+ lineHeight: 1.1,
22848
+ letterSpacing: "-0.025em",
22849
+ color: "var(--brand-text)",
22850
+ marginBottom: "1rem"
22851
+ },
22852
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
22853
+ children: title
22854
+ }
22855
+ ),
22856
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
22857
+ "p",
22858
+ {
22859
+ style: {
22860
+ fontFamily: "var(--brand-font-body)",
22861
+ fontSize: "1rem",
22862
+ lineHeight: 1.7,
22863
+ fontWeight: 300,
22864
+ color: "var(--brand-text-muted)",
22865
+ maxWidth: "340px"
22866
+ },
22867
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
22868
+ children: "This page doesn't have any content yet."
22869
+ }
22870
+ )
22871
+ ] });
22872
+ }
22158
22873
  // Annotate the CommonJS export names for ESM import in node:
22159
22874
  0 && (module.exports = {
22160
22875
  AI_DEFAULT_BRAND,
@@ -22172,6 +22887,7 @@ function OhhwellsBridge() {
22172
22887
  DropdownMenuItem,
22173
22888
  DropdownMenuSeparator,
22174
22889
  DropdownMenuTrigger,
22890
+ EmptySection,
22175
22891
  ItemActionToolbar,
22176
22892
  ItemInteractionLayer,
22177
22893
  LinkEditorPanel,