@ohhwells/bridge 0.1.84 → 0.1.85-next.254

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -69,6 +69,7 @@ function isRenderableTree(value) {
69
69
 
70
70
  // src/lib/ai-sections-store.ts
71
71
  var AI_SECTIONS_KEY = "__ohw_ai_sections";
72
+ var AI_SLOT_KEY_PREFIX = "ai.";
72
73
  var EMPTY_AI_SECTIONS = { v: 1, sections: [] };
73
74
  function parseAiSectionsState(raw) {
74
75
  if (!raw) return EMPTY_AI_SECTIONS;
@@ -112,6 +113,63 @@ function applyTreeToState(state, payload) {
112
113
  const others = state.sections.filter((existing) => existing.id !== entry.id);
113
114
  return { ...state, v: 1, sections: [...others, entry] };
114
115
  }
116
+ function foldAlignIntoTrees(state, store) {
117
+ const byId = new Map(state.sections.map((entry) => [entry.id, entry]));
118
+ const nextTrees = /* @__PURE__ */ new Map();
119
+ const treeFor = (id) => {
120
+ const cloned = nextTrees.get(id);
121
+ if (cloned) return cloned;
122
+ const entry = byId.get(id);
123
+ if (!entry) return void 0;
124
+ const fresh = {
125
+ ...entry.tree,
126
+ rows: entry.tree.rows.map((row) => ({ ...row, blocks: row.blocks.map((block) => ({ ...block })) }))
127
+ };
128
+ nextTrees.set(id, fresh);
129
+ return fresh;
130
+ };
131
+ const nodes = {};
132
+ for (const [key, override] of Object.entries(store.nodes)) {
133
+ const match = override.align !== void 0 && key.startsWith(AI_SLOT_KEY_PREFIX) ? key.match(/^ai\.(.+?)\.r(\d+)\.b(\d+)(?:\.|$)/) : null;
134
+ const tree = match ? treeFor(match[1]) : void 0;
135
+ const block = match && tree ? tree.rows[Number(match[2])]?.blocks[Number(match[3])] : void 0;
136
+ if (!block) {
137
+ nodes[key] = override;
138
+ continue;
139
+ }
140
+ block.align = override.align;
141
+ const rest = { ...override };
142
+ delete rest.align;
143
+ if (Object.keys(rest).length > 0) nodes[key] = rest;
144
+ }
145
+ const sections = {};
146
+ for (const [sectionId, override] of Object.entries(store.sections)) {
147
+ const tree = override.align !== void 0 ? treeFor(sectionId) : void 0;
148
+ if (!tree) {
149
+ sections[sectionId] = override;
150
+ continue;
151
+ }
152
+ for (const row of tree.rows) {
153
+ for (const block of row.blocks) block.align = override.align;
154
+ }
155
+ const rest = { ...override };
156
+ delete rest.align;
157
+ if (Object.keys(rest).length > 0) sections[sectionId] = rest;
158
+ }
159
+ if (nextTrees.size === 0) return { state, store, changed: false };
160
+ return {
161
+ state: {
162
+ ...state,
163
+ v: 1,
164
+ sections: state.sections.map((entry) => {
165
+ const tree = nextTrees.get(entry.id);
166
+ return tree ? { ...entry, tree } : entry;
167
+ })
168
+ },
169
+ store: { v: 1, sections, nodes },
170
+ changed: true
171
+ };
172
+ }
115
173
  function removeFromState(state, id) {
116
174
  return { ...state, v: 1, sections: state.sections.filter((entry) => entry.id !== id) };
117
175
  }
@@ -330,6 +388,12 @@ function styleSheetCss() {
330
388
  `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px}px !important; padding-bottom: ${px}px !important; }`
331
389
  );
332
390
  }
391
+ rules.push(
392
+ `[data-ohw-style-corners="sharp"] :is(.card, [data-ohw-card]) { border-radius: 0 !important; }`
393
+ );
394
+ for (const align of ["left", "center", "right"]) {
395
+ rules.push(`[data-ohw-style-align="${align}"] { text-align: ${align} !important; }`);
396
+ }
333
397
  return rules.join("\n");
334
398
  }
335
399
  var STYLE_FONT_LINK_ID = "ohw-style-fonts";
@@ -356,10 +420,25 @@ var SECTION_ATTRS = {
356
420
  textDistribution: "data-ohw-style-distribution",
357
421
  headlineScale: "data-ohw-style-headline",
358
422
  imageAspect: "data-ohw-style-aspect",
359
- spacing: "data-ohw-style-spacing"
423
+ spacing: "data-ohw-style-spacing",
424
+ cornerStyle: "data-ohw-style-corners",
425
+ align: "data-ohw-style-align"
360
426
  };
361
427
  var NODE_WROTE_ATTR = "data-ohw-style-node";
362
- var NODE_PROPS = ["color", "font-family", "font-size", "background"];
428
+ var NODE_PROPS = [
429
+ "color",
430
+ "font-family",
431
+ "font-size",
432
+ "background",
433
+ "text-align",
434
+ "justify-content",
435
+ "align-items"
436
+ ];
437
+ var ALIGN_JUSTIFY = {
438
+ left: "flex-start",
439
+ center: "center",
440
+ right: "flex-end"
441
+ };
363
442
  function saveInline(el, prop) {
364
443
  const attr = `data-ohw-style-prev-${prop}`;
365
444
  if (el.hasAttribute(attr)) return;
@@ -403,6 +482,10 @@ function clearNodeProps(root) {
403
482
  function buttonSurfaceOf(el) {
404
483
  return el.closest("a, button") ?? el;
405
484
  }
485
+ function alignSubjectOf(el) {
486
+ const button = el.closest('[data-ohw-role="button"]');
487
+ return button?.parentElement ?? el;
488
+ }
406
489
  function applyStylesToDom(store) {
407
490
  ensureStyleSheet();
408
491
  clearSectionAttrs(document);
@@ -448,6 +531,18 @@ function applyStylesToDom(store) {
448
531
  el.style.setProperty("font-size", `${override.fontSize}px`, "important");
449
532
  el.setAttribute(NODE_WROTE_ATTR, "");
450
533
  }
534
+ if (override.align !== void 0) {
535
+ const subject = alignSubjectOf(el);
536
+ saveInline(subject, "text-align");
537
+ saveInline(subject, "justify-content");
538
+ subject.style.setProperty("text-align", override.align, "important");
539
+ subject.style.setProperty(
540
+ "justify-content",
541
+ ALIGN_JUSTIFY[override.align] ?? "flex-start",
542
+ "important"
543
+ );
544
+ subject.setAttribute(NODE_WROTE_ATTR, "");
545
+ }
451
546
  if (override.buttonBackground !== void 0 || override.buttonText !== void 0) {
452
547
  const surface = buttonSurfaceOf(el);
453
548
  if (override.buttonBackground !== void 0) {
@@ -471,6 +566,284 @@ import { createRoot } from "react-dom/client";
471
566
  // src/ui/ai-tree/AiTreeRenderer.tsx
472
567
  import React from "react";
473
568
  import { ArrowLeft, ArrowRight, ChevronDown, icons as lucideIcons } from "lucide-react";
569
+
570
+ // src/lib/placeholder-imagery.ts
571
+ var U = (id) => `https://images.unsplash.com/photo-${id}?auto=format&fit=crop&w=1600&q=80`;
572
+ var GENERIC = [
573
+ U("1441986300917-64674bd600d8"),
574
+ U("1486406146926-c627a92ad1ab"),
575
+ U("1497032628192-86f99bcd76bc"),
576
+ U("1521737604893-d14cc237f11d"),
577
+ U("1522071820081-009f0129c71c"),
578
+ U("1519389950473-47ba0277781c"),
579
+ U("1460925895917-afdab827c52f"),
580
+ U("1504384308090-c894fdcc538d")
581
+ ];
582
+ var PEOPLE = [
583
+ U("1500648767791-00dcc994a43e"),
584
+ U("1494790108377-be9c29b29330"),
585
+ U("1507003211169-0a1dd7228f2d"),
586
+ U("1438761681033-6461ffad8d80"),
587
+ U("1544005313-94ddf0286df2"),
588
+ U("1472099645785-5658abf4ff4e"),
589
+ U("1519085360753-af0119f7cbe7"),
590
+ U("1534528741775-53994a69daeb")
591
+ ];
592
+ var THEMED = [
593
+ {
594
+ keywords: ["portrait", "headshot", "person", "people", "team", "staff", "avatar", "founder", "face"],
595
+ pool: PEOPLE
596
+ },
597
+ {
598
+ keywords: ["pet", "dog", "cat", "puppy", "kitten", "vet", "animal"],
599
+ pool: [
600
+ U("1548199973-03cce0bbc87b"),
601
+ U("1450778869180-41d0601e046e"),
602
+ U("1583511655857-d19b40a7a54e"),
603
+ U("1587300003388-59208cc962cb"),
604
+ U("1517849845537-4d257902454a"),
605
+ U("1601758228041-f3b2795255f1")
606
+ ]
607
+ },
608
+ {
609
+ keywords: [
610
+ "baker",
611
+ "bakery",
612
+ "cafe",
613
+ "coffee",
614
+ "latte",
615
+ "restaurant",
616
+ "pastr",
617
+ "bread",
618
+ "cake",
619
+ "cater",
620
+ "chef",
621
+ "kitchen",
622
+ "food",
623
+ "pizza",
624
+ "dessert",
625
+ "brunch",
626
+ "bistro",
627
+ "deli",
628
+ "dish",
629
+ "menu"
630
+ ],
631
+ pool: [
632
+ U("1509440159596-0249088772ff"),
633
+ U("1555507036-ab1f4038808a"),
634
+ U("1517433670267-08bbd4be890f"),
635
+ U("1486427944299-d1955d23e34d"),
636
+ U("1504754524776-8f4f37790ca0"),
637
+ U("1495474472287-4d71bcdd2085"),
638
+ U("1521017432531-fbd92d768814"),
639
+ U("1556909114-f6e7ad7d3136")
640
+ ]
641
+ },
642
+ {
643
+ keywords: [
644
+ "shop",
645
+ "store",
646
+ "boutique",
647
+ "retail",
648
+ "clothing",
649
+ "fashion",
650
+ "jewel",
651
+ "gift",
652
+ "florist",
653
+ "market",
654
+ "grocer",
655
+ "product",
656
+ "storefront"
657
+ ],
658
+ pool: [
659
+ U("1441984904996-e0b6ba687e04"),
660
+ U("1472851294608-062f824d29cc"),
661
+ U("1523381210434-271e8be1f52b"),
662
+ U("1534452203293-494d7ddbf7e0"),
663
+ U("1445205170230-053b83016050"),
664
+ U("1560243563-062bfc001d68")
665
+ ]
666
+ },
667
+ {
668
+ keywords: [
669
+ "yoga",
670
+ "pilates",
671
+ "fitness",
672
+ "gym",
673
+ "workout",
674
+ "trainer",
675
+ "wellness",
676
+ "meditat",
677
+ "massage",
678
+ "therap",
679
+ "physio",
680
+ "chiro",
681
+ "nutrition",
682
+ "spa",
683
+ "studio"
684
+ ],
685
+ pool: [
686
+ U("1544367567-0f2fcb009e0b"),
687
+ U("1506126613408-eca07ce68773"),
688
+ U("1545205597-3d9d02c29597"),
689
+ U("1552196563-55cd4e45efb3"),
690
+ U("1518611012118-696072aa579a"),
691
+ U("1571019613454-1cb2f99b2d8b"),
692
+ U("1540555700478-4be289fbecef"),
693
+ U("1519824145371-296894a0daa9")
694
+ ]
695
+ },
696
+ {
697
+ keywords: [
698
+ "salon",
699
+ "hairdress",
700
+ "haircut",
701
+ "barber",
702
+ "manicure",
703
+ "pedicure",
704
+ "nails",
705
+ "beauty",
706
+ "makeup",
707
+ "cosmetic",
708
+ "eyelash",
709
+ "eyebrow",
710
+ "skincare",
711
+ "esthetic",
712
+ "waxing",
713
+ "hair"
714
+ ],
715
+ pool: [
716
+ U("1560066984-138dadb4c035"),
717
+ U("1522337660859-02fbefca4702"),
718
+ U("1562322140-8baeececf3df"),
719
+ U("1521590832167-7bcbfaa6381f"),
720
+ U("1487412947147-5cebf100ffc2"),
721
+ U("1526045478516-99145907023c")
722
+ ]
723
+ },
724
+ {
725
+ keywords: [
726
+ "cleaning",
727
+ "plumb",
728
+ "electric",
729
+ "landscap",
730
+ "contractor",
731
+ "handyman",
732
+ "renov",
733
+ "hvac",
734
+ "roofing",
735
+ "painting",
736
+ "carpentry",
737
+ "flooring",
738
+ "movers",
739
+ "construction",
740
+ "tools"
741
+ ],
742
+ pool: [
743
+ U("1581578731548-c64695cc6952"),
744
+ U("1504307651254-35680f356dfd"),
745
+ U("1581092160562-40aa08e78837"),
746
+ U("1621905251189-08b45d6a269e"),
747
+ U("1558618666-fcd25c85cd64"),
748
+ U("1585128792020-803d29415281")
749
+ ]
750
+ },
751
+ {
752
+ keywords: [
753
+ "legal",
754
+ "attorney",
755
+ "lawyer",
756
+ "account",
757
+ "bookkeep",
758
+ "consult",
759
+ "coaching",
760
+ "financ",
761
+ "insurance",
762
+ "realtor",
763
+ "estate",
764
+ "marketing",
765
+ "agency",
766
+ "office",
767
+ "business",
768
+ "desk"
769
+ ],
770
+ pool: [
771
+ U("1497366216548-37526070297c"),
772
+ U("1497366811353-6870744d04b2"),
773
+ U("1454165804606-c3d57bc86b40"),
774
+ U("1521791136064-7986c2920216"),
775
+ U("1556761175-b413da4baf72"),
776
+ U("1542744173-8e7e53415bb0")
777
+ ]
778
+ },
779
+ {
780
+ keywords: [
781
+ "wedding",
782
+ "event",
783
+ "party",
784
+ "celebrat",
785
+ "venue",
786
+ "community",
787
+ "nonprofit",
788
+ "charity",
789
+ "workshop",
790
+ "photograph",
791
+ "concert"
792
+ ],
793
+ pool: [
794
+ U("1511578314322-379afb476865"),
795
+ U("1501281668745-f7f57925c3b4"),
796
+ U("1523580494863-6f3031224c94"),
797
+ U("1540575467063-178a50c2df87"),
798
+ U("1505236858219-8359eb29e329"),
799
+ U("1528605248644-14dd04022da1")
800
+ ]
801
+ }
802
+ ];
803
+ function poolForSubject(subject) {
804
+ for (const theme of THEMED) {
805
+ if (theme.keywords.some((k) => subject.includes(k))) {
806
+ return theme.pool;
807
+ }
808
+ }
809
+ return GENERIC;
810
+ }
811
+ function mixedHash(text) {
812
+ let hash = 2166136261;
813
+ for (let i = 0; i < text.length; i++) {
814
+ hash ^= text.charCodeAt(i);
815
+ hash = Math.imul(hash, 16777619);
816
+ }
817
+ return hash >>> 16 & 65535;
818
+ }
819
+ function resolvePlaceholderRef(ref) {
820
+ const match = /^placeholder:([a-z0-9-]+)$/.exec(ref);
821
+ if (!match) return null;
822
+ const subject = match[1];
823
+ const pool = poolForSubject(subject.replace(/-\d+$/, ""));
824
+ return pool[mixedHash(ref) % pool.length];
825
+ }
826
+ function collectPlaceholderRefs(tree) {
827
+ const seen = /* @__PURE__ */ new Set();
828
+ for (const match of JSON.stringify(tree ?? null).matchAll(/"(placeholder:[a-z0-9-]+)"/gu)) {
829
+ seen.add(match[1]);
830
+ }
831
+ return [...seen];
832
+ }
833
+ function buildPlaceholderMap(tree) {
834
+ const map = {};
835
+ const cursor = /* @__PURE__ */ new Map();
836
+ for (const ref of collectPlaceholderRefs(tree)) {
837
+ const subject = ref.slice("placeholder:".length).replace(/-\d+$/, "");
838
+ const pool = poolForSubject(subject);
839
+ const start = cursor.get(pool) ?? mixedHash(ref) % pool.length;
840
+ map[ref] = pool[start % pool.length];
841
+ cursor.set(pool, start + 1);
842
+ }
843
+ return map;
844
+ }
845
+
846
+ // src/ui/ai-tree/AiTreeRenderer.tsx
474
847
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
475
848
  function lucideByName(name) {
476
849
  const pascal = name.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
@@ -484,6 +857,7 @@ var typeStyle = (spec, font) => ({
484
857
  fontWeight: spec.weight
485
858
  });
486
859
  var str = (value) => typeof value === "string" ? value : "";
860
+ var cardRadius = (slots) => slots.cornerStyle === "sharp" ? 0 : AI_TREE_TOKENS.radiusCard;
487
861
  var featureLines = (value) => value.split(/\n|<br\s*\/?>/i).map((line) => line.trim()).filter(Boolean);
488
862
  var CHECK_MASK = `data:image/svg+xml,${encodeURIComponent(
489
863
  '<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>'
@@ -543,6 +917,22 @@ function accentBandContext(brand) {
543
917
  function textAttrs(ctx, path) {
544
918
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
545
919
  }
920
+ var AI_RESPONSIVE_CSS = [
921
+ "@media (max-width: 960px) {",
922
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
923
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
924
+ "}",
925
+ "@media (max-width: 640px) {",
926
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
927
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
928
+ // Group containers flatten to a column on phones; span placements come along for free.
929
+ " [data-ai-responsive] [data-ai-group] { display: flex !important; flex-direction: column !important; }",
930
+ " [data-ai-responsive] [data-ai-group] > * { grid-column: auto !important; }",
931
+ " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
932
+ " [data-ai-responsive] { overflow-x: hidden; }",
933
+ " [data-ai-responsive] img { max-width: 100%; }",
934
+ "}"
935
+ ].join("\n");
546
936
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
547
937
  function MediaBox({
548
938
  refValue,
@@ -555,13 +945,17 @@ function MediaBox({
555
945
  const url = refValue ? ctx.resolveMedia(refValue) : null;
556
946
  const isIcon = /^(lucide|simple):/.test(refValue);
557
947
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
558
- const editAttrs = ctx.keyFor && editPath && !isIcon ? { "data-ohw-key": ctx.keyFor(editPath), "data-ohw-editable": "image" } : {};
948
+ const editAttrs = ctx.keyFor && editPath ? {
949
+ "data-ohw-key": ctx.keyFor(editPath),
950
+ "data-ohw-editable": isIcon ? "icon" : "image"
951
+ } : {};
559
952
  if (isIcon) {
560
953
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
561
954
  return /* @__PURE__ */ jsx(
562
955
  "span",
563
956
  {
564
957
  "data-ai-icon": refValue,
958
+ ...editAttrs,
565
959
  style: {
566
960
  display: "inline-flex",
567
961
  width: 48,
@@ -656,7 +1050,7 @@ function TextBlock({ slots, ctx, path }) {
656
1050
  }
657
1051
  function SectionHeaderBlock({ node, ctx, path }) {
658
1052
  const slots = node.slots ?? {};
659
- const align = slots.alignment === "center" ? "center" : "left";
1053
+ const align = node.align ?? (slots.alignment === "center" ? "center" : "left");
660
1054
  const children = node.children ?? [];
661
1055
  const buttonRowIdx = children.findIndex((c) => c.type === "button-row");
662
1056
  const buttonRow = buttonRowIdx >= 0 ? children[buttonRowIdx] : void 0;
@@ -700,7 +1094,7 @@ function SectionHeaderBlock({ node, ctx, path }) {
700
1094
  display: "flex",
701
1095
  gap: AI_TREE_TOKENS.spacing6,
702
1096
  marginTop: AI_TREE_TOKENS.spacing8,
703
- justifyContent: align === "center" ? "center" : "flex-start"
1097
+ justifyContent: align === "center" ? "center" : align === "right" ? "flex-end" : "flex-start"
704
1098
  },
705
1099
  children: (buttonRow.children ?? []).map((button, i) => /* @__PURE__ */ jsx(
706
1100
  ButtonEl,
@@ -806,10 +1200,11 @@ function PricingCard({ node, ctx, path }) {
806
1200
  return /* @__PURE__ */ jsxs(
807
1201
  "div",
808
1202
  {
1203
+ "data-ohw-card": "",
809
1204
  style: {
810
1205
  background: hasBg ? ctx.brand.palette.light : "transparent",
811
1206
  border: `1px solid ${dark}`,
812
- borderRadius: AI_TREE_TOKENS.radiusCard,
1207
+ borderRadius: cardRadius(slots),
813
1208
  padding: AI_TREE_TOKENS.paddingBlock,
814
1209
  display: "flex",
815
1210
  flexDirection: "column",
@@ -914,10 +1309,11 @@ function TestimonialCard({ node, ctx, path }) {
914
1309
  return /* @__PURE__ */ jsx(
915
1310
  "div",
916
1311
  {
1312
+ "data-ohw-card": "",
917
1313
  "data-ai-avatar-pos": avatarPos ?? void 0,
918
1314
  style: {
919
1315
  background: hasBg ? ctx.cardSurface : "transparent",
920
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1316
+ borderRadius: hasBg ? cardRadius(slots) : 0,
921
1317
  overflow: "hidden",
922
1318
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
923
1319
  minWidth: 0
@@ -952,10 +1348,11 @@ function TeamCard({ node, ctx, path }) {
952
1348
  return /* @__PURE__ */ jsxs(
953
1349
  "div",
954
1350
  {
1351
+ "data-ohw-card": "",
955
1352
  "data-ai-avatar-pos": avatarPos ?? void 0,
956
1353
  style: {
957
1354
  background: hasBg ? ctx.cardSurface : "transparent",
958
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1355
+ borderRadius: hasBg ? cardRadius(slots) : 0,
959
1356
  overflow: "hidden",
960
1357
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
961
1358
  minWidth: 0,
@@ -1033,7 +1430,7 @@ function CardBlock({ node, ctx, path }) {
1033
1430
  editPath: `${path}.media`
1034
1431
  }
1035
1432
  ) : null;
1036
- const centered = slots.alignment === "center";
1433
+ const centered = (node.align ?? slots.alignment) === "center";
1037
1434
  const content = /* @__PURE__ */ jsxs(
1038
1435
  "div",
1039
1436
  {
@@ -1126,9 +1523,10 @@ function CardBlock({ node, ctx, path }) {
1126
1523
  return /* @__PURE__ */ jsxs(
1127
1524
  "div",
1128
1525
  {
1526
+ "data-ohw-card": "",
1129
1527
  style: {
1130
1528
  background: hasBg ? ctx.cardSurface : "transparent",
1131
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1529
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1132
1530
  overflow: "hidden",
1133
1531
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1134
1532
  display: horizontal ? "flex" : "block",
@@ -1156,7 +1554,7 @@ function CardBlock({ node, ctx, path }) {
1156
1554
  ) : /* @__PURE__ */ jsx(
1157
1555
  "div",
1158
1556
  {
1159
- 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" },
1557
+ 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" },
1160
1558
  children: media
1161
1559
  }
1162
1560
  )),
@@ -1447,7 +1845,7 @@ function CollectionBlock({ node, ctx, path }) {
1447
1845
  return /* @__PURE__ */ jsx(
1448
1846
  "div",
1449
1847
  {
1450
- "data-ai-grid": "",
1848
+ "data-ai-grid": String(itemsPerRow),
1451
1849
  style: {
1452
1850
  display: "grid",
1453
1851
  gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
@@ -1567,6 +1965,32 @@ function renderNode(node, ctx, path) {
1567
1965
  if (child) {
1568
1966
  return renderNode(child, ctx, `${path}.c0`);
1569
1967
  }
1968
+ if (str(slots.provider) === "map" && str(slots.query)) {
1969
+ const query = str(slots.query);
1970
+ const mapAttrs = ctx.keyFor ? {
1971
+ "data-ohw-key": ctx.keyFor(`${path}.query`),
1972
+ "data-ohw-editable": "map",
1973
+ "data-ohw-map-query": query
1974
+ } : {};
1975
+ return /* @__PURE__ */ jsx(
1976
+ "iframe",
1977
+ {
1978
+ ...mapAttrs,
1979
+ "data-ai-embed": "map",
1980
+ title: str(slots.title) || "Map",
1981
+ src: `https://www.google.com/maps?q=${encodeURIComponent(query)}&output=embed`,
1982
+ loading: "lazy",
1983
+ referrerPolicy: "no-referrer-when-downgrade",
1984
+ style: {
1985
+ width: "100%",
1986
+ minHeight: 320,
1987
+ border: 0,
1988
+ borderRadius: AI_TREE_TOKENS.radiusCard,
1989
+ display: "block"
1990
+ }
1991
+ }
1992
+ );
1993
+ }
1570
1994
  return /* @__PURE__ */ jsx(
1571
1995
  "div",
1572
1996
  {
@@ -1723,9 +2147,14 @@ function AiTreeRenderer({
1723
2147
  const resolvedBrand = brand ?? AI_DEFAULT_BRAND;
1724
2148
  const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
1725
2149
  const blockBrand = band?.brand ?? resolvedBrand;
2150
+ const placeholderMap = buildPlaceholderMap(tree);
1726
2151
  const ctx = {
1727
2152
  brand: blockBrand,
1728
- resolveMedia: resolveMedia ?? (() => null),
2153
+ // An owner/library ref resolves through the host resolver; a `placeholder:<subject>` ref the
2154
+ // host cannot resolve falls back to real stock photography (the per-section map first, then a
2155
+ // standalone resolve), so generated galleries, image rows, and overlay backgrounds arrive with
2156
+ // photos instead of grey boxes.
2157
+ resolveMedia: (ref) => resolveMedia?.(ref) ?? placeholderMap[ref] ?? resolvePlaceholderRef(ref),
1729
2158
  cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
1730
2159
  keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
1731
2160
  sectionAlignment: (tree.settings ?? {}).alignment === "center" ? "center" : "left",
@@ -1752,11 +2181,25 @@ function AiTreeRenderer({
1752
2181
  }
1753
2182
  })();
1754
2183
  const distributed = !isOverlay && settings.textDistribution;
2184
+ const rowAlignItems = (rowAlign) => {
2185
+ if (rowAlign === "top") return "start";
2186
+ if (rowAlign === "bottom") return "end";
2187
+ if (rowAlign === "center" || rowAlign === "stretch") return rowAlign;
2188
+ if (distributed === "space-between") return "stretch";
2189
+ return (distributed ?? settings.verticalPosition) === "top" ? "start" : "center";
2190
+ };
2191
+ const cellAlignStyle = (blockAlign) => blockAlign ? {
2192
+ display: "flex",
2193
+ flexDirection: "column",
2194
+ alignItems: blockAlign === "left" ? "flex-start" : blockAlign === "right" ? "flex-end" : "center",
2195
+ textAlign: blockAlign
2196
+ } : {};
1755
2197
  return /* @__PURE__ */ jsxs(
1756
2198
  "section",
1757
2199
  {
1758
2200
  "data-ai-section": tree.tag ?? "",
1759
2201
  ...bgAttrs,
2202
+ "data-ai-responsive": "",
1760
2203
  style: {
1761
2204
  position: "relative",
1762
2205
  padding: `${pad}px 0`,
@@ -1767,12 +2210,13 @@ function AiTreeRenderer({
1767
2210
  color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
1768
2211
  },
1769
2212
  children: [
1770
- isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
2213
+ /* @__PURE__ */ jsx("style", { children: AI_RESPONSIVE_CSS }),
1771
2214
  /* @__PURE__ */ jsx("style", { children: AI_MOBILE_CSS }),
2215
+ isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
1772
2216
  /* @__PURE__ */ jsx(
1773
2217
  "div",
1774
2218
  {
1775
- "data-ai-container": "",
2219
+ "data-ai-section-inner": "",
1776
2220
  style: {
1777
2221
  position: "relative",
1778
2222
  maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
@@ -1783,12 +2227,12 @@ function AiTreeRenderer({
1783
2227
  children: tree.rows.map((row, r2) => /* @__PURE__ */ jsx(
1784
2228
  "div",
1785
2229
  {
1786
- "data-ai-row": "",
2230
+ "data-ai-columns": "",
1787
2231
  style: {
1788
2232
  display: "grid",
1789
2233
  gridTemplateColumns: "repeat(12, 1fr)",
1790
2234
  gap: AI_TREE_TOKENS.spacing6,
1791
- alignItems: distributed === "space-between" ? "stretch" : (distributed ?? settings.verticalPosition) === "top" ? "start" : "center",
2235
+ alignItems: rowAlignItems(row.align),
1792
2236
  marginTop: r2 > 0 ? AI_TREE_TOKENS.spacing8 : 0
1793
2237
  },
1794
2238
  children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ jsx(
@@ -1798,6 +2242,8 @@ function AiTreeRenderer({
1798
2242
  style: {
1799
2243
  gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`,
1800
2244
  minWidth: 0,
2245
+ // Horizontal placement of the block's content within its column.
2246
+ ...cellAlignStyle(block.align),
1801
2247
  // space-between: each column becomes a flex column whose content spreads over
1802
2248
  // the full row height instead of clumping at the top.
1803
2249
  ...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
@@ -7809,6 +8255,7 @@ function MediaOverlay({
7809
8255
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
7810
8256
  );
7811
8257
  }, [isVideo]);
8258
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
7812
8259
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
7813
8260
  const box = {
7814
8261
  position: "fixed",
@@ -7938,17 +8385,17 @@ function MediaOverlay({
7938
8385
  },
7939
8386
  children: [
7940
8387
  isVideo ? /* @__PURE__ */ jsx15(Film, { size: 14 }) : /* @__PURE__ */ jsx15(ImageIcon, { size: 14 }),
7941
- isVideo ? "Replace video" : "Replace image"
8388
+ replaceLabel
7942
8389
  ]
7943
8390
  }
7944
8391
  ),
7945
- replaceMode === "none" ? null : /* @__PURE__ */ jsxs7(
8392
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ jsxs7(
7946
8393
  Button,
7947
8394
  {
7948
8395
  "data-ohw-media-overlay": "",
7949
8396
  variant: "outline",
7950
8397
  size: "sm",
7951
- "aria-label": isVideo ? "Replace video" : "Replace image",
8398
+ "aria-label": replaceLabel,
7952
8399
  className: "gap-1.5 cursor-pointer hover:bg-background",
7953
8400
  style: {
7954
8401
  ...OVERLAY_BUTTON_STYLE,
@@ -7971,7 +8418,7 @@ function MediaOverlay({
7971
8418
  },
7972
8419
  children: [
7973
8420
  isVideo ? /* @__PURE__ */ jsx15(Film, { size: 14 }) : /* @__PURE__ */ jsx15(ImageIcon, { size: 14 }),
7974
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
8421
+ replaceMode === "full" ? replaceLabel : null
7975
8422
  ]
7976
8423
  }
7977
8424
  )
@@ -8177,6 +8624,27 @@ function deleteSectionInstance(instanceId, currentPath, existingEntries) {
8177
8624
  function restoreSectionInstance(instanceId, currentPath, existingEntries) {
8178
8625
  return setSectionRemoved(instanceId, currentPath, existingEntries, false);
8179
8626
  }
8627
+ function duplicateSectionInstance(instanceId, newId, currentPath, existingEntries) {
8628
+ const original = findByInstanceId(instanceId);
8629
+ if (!original) return null;
8630
+ const clone = original.cloneNode(true);
8631
+ clone.setAttribute("data-ohw-instance", newId);
8632
+ const keyRekeys = rekeySectionSubtree(clone, newId);
8633
+ original.insertAdjacentElement("afterend", clone);
8634
+ const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
8635
+ const entries = topLevelSections().map((el, order) => {
8636
+ const id = instanceIdOf(el);
8637
+ return {
8638
+ instanceId: id,
8639
+ type: el.getAttribute("data-ohw-section") ?? "",
8640
+ order,
8641
+ pagePath: currentPath,
8642
+ ...byId.get(id)?.removed ? { removed: true } : {}
8643
+ };
8644
+ });
8645
+ applyPersistedOrder(entries);
8646
+ return { entries, keyRekeys };
8647
+ }
8180
8648
  function newInstanceId() {
8181
8649
  return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `instance-${Date.now()}-${Math.random().toString(36).slice(2)}`;
8182
8650
  }
@@ -8191,14 +8659,20 @@ function getPageSectionOrderEntries(raw, currentPath) {
8191
8659
  }
8192
8660
  function rekeySectionSubtree(root, instanceId) {
8193
8661
  const suffix = `::${instanceId}`;
8662
+ const pairs = [];
8194
8663
  const rekey = (el, attr) => {
8195
8664
  const current = el.getAttribute(attr);
8196
- if (current) el.setAttribute(attr, `${current}${suffix}`);
8665
+ if (!current) return;
8666
+ const base = current.includes("::") ? current.slice(0, current.indexOf("::")) : current;
8667
+ const next = `${base}${suffix}`;
8668
+ el.setAttribute(attr, next);
8669
+ pairs.push({ from: current, to: next });
8197
8670
  };
8198
8671
  if (root.hasAttribute("data-ohw-key")) rekey(root, "data-ohw-key");
8199
8672
  if (root.hasAttribute("data-ohw-href-key")) rekey(root, "data-ohw-href-key");
8200
8673
  root.querySelectorAll("[data-ohw-key]").forEach((el) => rekey(el, "data-ohw-key"));
8201
8674
  root.querySelectorAll("[data-ohw-href-key]").forEach((el) => rekey(el, "data-ohw-href-key"));
8675
+ return pairs;
8202
8676
  }
8203
8677
  function initSectionInstancesFromContent(content, currentPath) {
8204
8678
  document.querySelectorAll("[data-ohw-section]:not([data-ohw-instance])").forEach((el) => {
@@ -12936,6 +13410,7 @@ function readLogoSizeState(content, placement) {
12936
13410
  function getLogoElement(el) {
12937
13411
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
12938
13412
  if (marked) return marked;
13413
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
12939
13414
  const root = el.closest("nav, [data-ohw-nav-root], footer");
12940
13415
  if (!root) return null;
12941
13416
  const anchor = el.closest("a");
@@ -14317,6 +14792,9 @@ function collectEditableNodes(extraContent, root = document) {
14317
14792
  if (el.dataset.ohwEditable === "link") {
14318
14793
  return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref3(el) };
14319
14794
  }
14795
+ if (el.dataset.ohwEditable === "map") {
14796
+ return { key: el.dataset.ohwKey ?? "", type: "map", text: el.dataset.ohwMapQuery ?? "" };
14797
+ }
14320
14798
  return {
14321
14799
  key: el.dataset.ohwKey ?? "",
14322
14800
  type: el.dataset.ohwEditable ?? "text",
@@ -14870,21 +15348,10 @@ function parseSchedulingInsertAfter(insertAfter) {
14870
15348
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
14871
15349
  };
14872
15350
  }
14873
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
14874
- const parsed = parseSchedulingInsertAfter(insertAfter);
14875
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
14876
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
14877
- return { effectiveInsertAfter, insertBefore };
14878
- }
14879
- function getSchedulingMountPoint(insertAfter) {
14880
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
14881
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
14882
- if (!anchorEl && anchor === "scheduling") {
14883
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
14884
- anchorEl = widgets.at(-1) ?? null;
14885
- }
14886
- if (!anchorEl) return null;
14887
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
15351
+ function resolveEntryAnchor(entry) {
15352
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
15353
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
15354
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
14888
15355
  }
14889
15356
  function schedulingMountDepth(insertAfter) {
14890
15357
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -14901,8 +15368,7 @@ function getPageSchedulingEntries(raw) {
14901
15368
  }
14902
15369
  }
14903
15370
  function isSchedulingWidgetMissing(entry) {
14904
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
14905
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
15371
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
14906
15372
  }
14907
15373
  function hasMissingSchedulingWidgets(entries) {
14908
15374
  return entries.some(isSchedulingWidgetMissing);
@@ -14932,16 +15398,17 @@ function initSectionsFromContent(content, removeExisting = false) {
14932
15398
  } catch {
14933
15399
  }
14934
15400
  }
14935
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
14936
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
14937
- const sectionId = schedulingSectionId(effectiveInsertAfter);
15401
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
15402
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
15403
+ const sectionId = schedulingSectionId(widgetId);
14938
15404
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
14939
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
14940
- if (!mountPoint) return false;
15405
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
15406
+ if (!anchorEl) return false;
15407
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
14941
15408
  const container = document.createElement("div");
14942
15409
  container.dataset.ohwSectionContainer = "scheduling";
14943
- if (insertBefore) {
14944
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
15410
+ if (beforeId) {
15411
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
14945
15412
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
14946
15413
  if (!beforePoint) return false;
14947
15414
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -14952,19 +15419,25 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
14952
15419
  }
14953
15420
  tail.insertAdjacentElement("afterend", container);
14954
15421
  }
14955
- const root = createRoot2(container);
14956
- flushSync2(() => {
14957
- root.render(
14958
- /* @__PURE__ */ jsx33(
14959
- SchedulingWidget,
14960
- {
14961
- notifyOnConnect,
14962
- initialScheduleId: scheduleId,
14963
- insertAfter: effectiveInsertAfter
14964
- }
14965
- )
14966
- );
14967
- });
15422
+ try {
15423
+ const root = createRoot2(container);
15424
+ flushSync2(() => {
15425
+ root.render(
15426
+ /* @__PURE__ */ jsx33(
15427
+ SchedulingWidget,
15428
+ {
15429
+ notifyOnConnect,
15430
+ initialScheduleId: scheduleId,
15431
+ insertAfter: widgetId
15432
+ }
15433
+ )
15434
+ );
15435
+ });
15436
+ } catch (err) {
15437
+ console.error("[ow:scheduling] render threw", err);
15438
+ container.remove();
15439
+ return false;
15440
+ }
14968
15441
  const tracker = getSectionsTracker();
14969
15442
  let sections = [];
14970
15443
  try {
@@ -14972,10 +15445,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
14972
15445
  } catch {
14973
15446
  }
14974
15447
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
14975
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
15448
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
14976
15449
  sections.push({
14977
15450
  type: "scheduling",
14978
- insertAfter: effectiveInsertAfter,
15451
+ insertAfter: widgetId,
15452
+ anchorId,
15453
+ beforeId: beforeId ?? null,
14979
15454
  pagePath: window.location.pathname,
14980
15455
  ...scheduleId ? { scheduleId } : {}
14981
15456
  });
@@ -14989,7 +15464,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
14989
15464
  for (let i = pending.length - 1; i >= 0; i--) {
14990
15465
  const entry = pending[i];
14991
15466
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
14992
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId ?? null)) {
15467
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
15468
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
14993
15469
  pending.splice(i, 1);
14994
15470
  }
14995
15471
  }
@@ -15081,7 +15557,7 @@ var EDITOR_CHROME_SELECTOR = '[data-ohw-toolbar], [data-ohw-form-toolbar], [data
15081
15557
  function isOverEditorChrome(x, y) {
15082
15558
  return document.elementsFromPoint(x, y).some((el) => el instanceof HTMLElement && el.matches(EDITOR_CHROME_SELECTOR));
15083
15559
  }
15084
- 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"])';
15560
+ 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"])';
15085
15561
  function getVideoEl2(el) {
15086
15562
  return el instanceof HTMLVideoElement ? el : el.querySelector("video");
15087
15563
  }
@@ -15137,6 +15613,12 @@ function applyVideoSettingNode(key, val) {
15137
15613
  });
15138
15614
  return true;
15139
15615
  }
15616
+ function applyMapQuery(el, val) {
15617
+ if (!(el instanceof HTMLIFrameElement)) return;
15618
+ const nextSrc = `https://www.google.com/maps?q=${encodeURIComponent(val)}&output=embed`;
15619
+ if (el.src !== nextSrc) el.src = nextSrc;
15620
+ el.setAttribute("data-ohw-map-query", val);
15621
+ }
15140
15622
  function applyLinkByKey(key, val) {
15141
15623
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
15142
15624
  if (el.dataset.ohwEditable === "link") applyLinkHref(el, val);
@@ -15147,6 +15629,11 @@ function applyLinkByKey(key, val) {
15147
15629
  hrefAnchors.forEach((el) => applyLinkHref(el, val));
15148
15630
  }
15149
15631
  }
15632
+ function isInsideLinkEditor(target) {
15633
+ return Boolean(
15634
+ 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"]')
15635
+ );
15636
+ }
15150
15637
  function isInsideFloatingPanel(target) {
15151
15638
  return Boolean(target.closest("[data-ohw-floating-panel]"));
15152
15639
  }
@@ -15154,11 +15641,6 @@ function isPointOverFloatingPanel(clientX, clientY) {
15154
15641
  const el = document.elementFromPoint(clientX, clientY);
15155
15642
  return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
15156
15643
  }
15157
- function isInsideLinkEditor(target) {
15158
- return Boolean(
15159
- 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"]')
15160
- );
15161
- }
15162
15644
  function getHrefKeyFromElement(el) {
15163
15645
  if (!el) return null;
15164
15646
  const anchor = el.closest("[data-ohw-href-key]");
@@ -15417,7 +15899,7 @@ function getNavigationSelectionParent(el) {
15417
15899
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
15418
15900
  return getFooterLinksContainer();
15419
15901
  }
15420
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
15902
+ 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)) {
15421
15903
  return getNavigationRoot(el);
15422
15904
  }
15423
15905
  return null;
@@ -15632,7 +16114,6 @@ var ICONS = {
15632
16114
  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"/>',
15633
16115
  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"/>'
15634
16116
  };
15635
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
15636
16117
  var SELECTION_CHROME_GAP2 = 4;
15637
16118
  var TOOLBAR_STROKE_GAP2 = 4;
15638
16119
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -16012,6 +16493,8 @@ function StateToggle({
16012
16493
  );
16013
16494
  }
16014
16495
  var contentCache = /* @__PURE__ */ new Map();
16496
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
16497
+ var brandingCache = /* @__PURE__ */ new Map();
16015
16498
  var OHW_LOADER_STYLE = {
16016
16499
  position: "fixed",
16017
16500
  inset: 0,
@@ -16049,6 +16532,89 @@ function OhwLoaderSpinner() {
16049
16532
  )
16050
16533
  ] });
16051
16534
  }
16535
+ function OhwBrandMark() {
16536
+ return /* @__PURE__ */ jsxs20(
16537
+ "svg",
16538
+ {
16539
+ width: "16",
16540
+ height: "16",
16541
+ viewBox: "0 0 48 48",
16542
+ fill: "none",
16543
+ "aria-hidden": true,
16544
+ style: { display: "block", flexShrink: 0 },
16545
+ xmlns: "http://www.w3.org/2000/svg",
16546
+ children: [
16547
+ /* @__PURE__ */ jsx33(
16548
+ "mask",
16549
+ {
16550
+ id: "ohw-badge-mark",
16551
+ style: { maskType: "luminance" },
16552
+ maskUnits: "userSpaceOnUse",
16553
+ x: "0",
16554
+ y: "0",
16555
+ width: "48",
16556
+ height: "48",
16557
+ children: /* @__PURE__ */ jsx33("path", { d: "M23.8741 48C37.0594 48 47.7481 37.2548 47.7481 24C47.7481 10.7452 37.0594 0 23.8741 0C10.6888 0 0 10.7452 0 24C0 37.2548 10.6888 48 23.8741 48Z", fill: "white" })
16558
+ }
16559
+ ),
16560
+ /* @__PURE__ */ jsxs20("g", { mask: "url(#ohw-badge-mark)", children: [
16561
+ /* @__PURE__ */ jsx33("path", { d: "M23.8731 48.0497C37.0584 48.0497 47.7472 37.3046 47.7472 24.0497C47.7472 10.7949 37.0584 0.0497208 23.8731 0.0497208C10.6878 0.0497208 -0.000976562 10.7949 -0.000976562 24.0497C-0.000976562 37.3046 10.6878 48.0497 23.8731 48.0497Z", fill: "#0078E5" }),
16562
+ /* @__PURE__ */ jsx33("path", { d: "M17.1307 14.7172C13.1687 14.7172 9.38102 18.1154 8.65114 22.34C8.38885 23.8488 8.5598 25.2581 9.06929 26.4451C6.20005 29.1677 1.77216 27.8721 -1.40212 26.1536C-2.73618 25.4317 -3.92695 27.4745 -2.59037 28.1981C1.33389 30.3226 6.86037 31.6621 10.4402 28.4188C11.4718 29.3859 12.867 29.9621 14.4894 29.9621C18.4161 29.9621 22.2038 26.5318 22.9337 22.34C23.6636 18.1162 21.0566 14.7172 17.1298 14.7172H17.1307ZM19.9798 22.34C19.5281 25.0399 17.2689 27.231 14.9754 27.231C12.6466 27.231 11.1877 25.0399 11.6394 22.34C12.1262 19.6401 14.3151 17.4482 16.6438 17.4482C18.9374 17.4482 20.4667 19.6401 19.9798 22.34Z", fill: "white" }),
16563
+ /* @__PURE__ */ jsx33("path", { d: "M40.0017 27.0262C39.1797 27.081 38.2721 26.995 37.4668 26.7415C37.3344 26.6993 37.28 26.5401 37.3529 26.4205C37.5959 26.0212 37.8255 25.6152 38.009 25.1889C38.1071 24.9918 38.2018 24.793 38.2897 24.5908C38.3274 24.5041 38.4163 24.451 38.5101 24.4619C38.63 24.4754 38.7054 24.4821 38.8881 24.4821L39.1529 24.4796L39.8283 24.4543C45.9229 24.0492 50.4765 20.4319 54.8466 16.9014C56.0172 15.9554 57.6932 17.6208 56.5116 18.5752C51.7687 22.4065 47.1966 26.5081 40.9319 26.9689", fill: "white" }),
16564
+ /* @__PURE__ */ jsx33("path", { d: "M37.9687 24.27C38.4472 23.1319 38.7656 21.9045 38.9609 20.6991C39.5927 16.76 38.5058 14.2193 36.1553 14.2193C34.1835 14.2193 33.1469 17.2427 32.9199 18.8694C32.743 19.9872 32.5914 22.1219 33.6028 23.9524C33.7553 24.259 34.15 24.7712 34.471 25.1259C34.5447 25.2067 34.6746 25.2 34.7349 25.1082C34.9528 24.7788 35.1615 24.4039 35.3584 24.0257C35.5444 23.6677 35.5888 23.6138 35.8587 23.0207C35.8838 22.966 35.8813 22.9002 35.8478 22.8505C35.5888 22.4597 35.2168 21.9787 35.1204 21.4614C34.9184 20.4455 34.9436 19.2257 35.2218 18.1078C35.4413 17.3118 35.7195 16.7844 35.9039 16.5106C35.9466 16.4466 36.0279 16.4129 36.0975 16.4449C36.369 16.5671 36.5827 16.8838 36.7385 17.396C37.0167 18.2089 37.0167 19.3782 36.814 20.6999C36.6991 21.5271 36.4771 22.3729 36.1746 23.1673C36.1293 23.308 36.0757 23.4461 36.0187 23.5826C36.0187 23.5868 36.0187 23.591 36.0187 23.5961C35.9911 23.6946 35.9207 23.8067 35.8846 23.901C35.5536 24.5497 35.2344 25.1697 34.8439 25.7838C34.8388 25.7863 34.8346 25.7914 34.8296 25.7931C34.6528 26.0525 34.4718 26.2901 34.2866 26.4965C34.2774 26.5099 34.2682 26.5234 34.2589 26.5369C34.2405 26.5638 34.2179 26.5815 34.1944 26.595C33.5064 27.3212 32.7665 27.6893 32.0123 27.6893C31.8606 27.6893 31.6838 27.664 31.507 27.4349C31.3042 27.1299 30.85 26.0879 31.2539 22.9112C31.4785 21.3949 31.8011 20.0268 31.931 19.5408C31.9579 19.4413 31.8908 19.3419 31.7886 19.3293L30.0062 19.1162C29.9241 19.1061 29.8478 19.1566 29.8252 19.2366C29.2704 21.1615 27.0305 27.6885 24.9599 27.6885C24.328 27.6885 24.1512 26.6211 24.1001 26.2909C23.775 23.6264 25.528 18.5492 29.5302 16.2267C29.6048 16.1838 29.635 16.0928 29.5998 16.0136L28.9042 14.467C28.8632 14.3752 28.7501 14.3389 28.6638 14.3886C25.8079 16.0414 24.1847 18.5231 23.2915 20.3436C22.2046 22.5793 21.6993 25.0189 21.9515 26.8738C22.1795 28.7794 23.1398 29.872 24.6054 29.872C26.0543 29.872 27.4369 28.9066 28.7098 27.0187C28.7953 26.8915 28.9889 26.9311 29.0149 27.0819C29.2646 28.5283 29.9811 29.872 31.6579 29.872C33.5282 29.872 35.3232 28.7288 36.7134 26.6447C36.7712 26.5874 36.7972 26.5411 36.8181 26.4906C36.8232 26.4931 36.8282 26.4948 36.8332 26.4973C37.01 26.2025 37.181 25.9051 37.3444 25.6027C37.4508 25.4005 37.5572 25.1992 37.6586 24.9945C37.7181 24.874 37.7743 24.7527 37.8262 24.6289C37.838 24.6002 37.8547 24.5665 37.8706 24.5337", fill: "white" }),
16565
+ /* @__PURE__ */ jsx33("path", { d: "M30.5839 31.6397C25.7546 34.8577 19.4773 34.7853 14.6907 31.5243C13.5368 30.7384 12.5044 32.6498 13.6474 33.4281C19.034 37.0985 26.3077 37.096 31.7218 33.488C32.8791 32.7172 31.7478 30.8639 30.5839 31.6397Z", fill: "white" })
16566
+ ] })
16567
+ ]
16568
+ }
16569
+ );
16570
+ }
16571
+ var OHW_BADGE_STYLE = {
16572
+ position: "fixed",
16573
+ left: 20,
16574
+ bottom: 20,
16575
+ zIndex: 2147483e3,
16576
+ boxSizing: "border-box",
16577
+ display: "inline-flex",
16578
+ alignItems: "center",
16579
+ gap: 0,
16580
+ padding: "6px 8px",
16581
+ margin: 0,
16582
+ background: "#ffffff",
16583
+ border: "1px solid #e7e5e4",
16584
+ borderRadius: 9999,
16585
+ boxShadow: "0 1px 3px rgba(0, 0, 0, 0.1)",
16586
+ color: "#0c0a09",
16587
+ textDecoration: "none",
16588
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
16589
+ };
16590
+ var OHW_BADGE_LABEL_STYLE = {
16591
+ padding: "0 4px",
16592
+ fontSize: 14,
16593
+ lineHeight: "24px",
16594
+ fontWeight: 500,
16595
+ fontStyle: "normal",
16596
+ letterSpacing: "normal",
16597
+ textTransform: "none",
16598
+ color: "#0c0a09",
16599
+ whiteSpace: "nowrap"
16600
+ };
16601
+ function MadeWithOhhWells() {
16602
+ return /* @__PURE__ */ jsxs20(
16603
+ "a",
16604
+ {
16605
+ href: "https://ohhwells.com",
16606
+ target: "_blank",
16607
+ rel: "noopener noreferrer",
16608
+ "aria-label": "Made with OhhWells",
16609
+ "data-ohw-badge": "",
16610
+ style: OHW_BADGE_STYLE,
16611
+ children: [
16612
+ /* @__PURE__ */ jsx33(OhwBrandMark, {}),
16613
+ /* @__PURE__ */ jsx33("span", { style: OHW_BADGE_LABEL_STYLE, children: "Made with OhhWells" })
16614
+ ]
16615
+ }
16616
+ );
16617
+ }
16052
16618
  var OHW_LOADER_PREHYDRATE_SCRIPT = `(function(){try{var p=location.hostname.split(".");var fromHost=p.length>=3&&p[0]!=="www"?p[0]:"";var fromQuery=new URLSearchParams(location.search).get("subdomain")||"";if(!fromHost&&!fromQuery)return;var e=document.getElementById("ohw-loader");if(e)e.style.display="flex"}catch(e){}})();`;
16053
16619
  function resolveSubdomain(subdomainFromQuery) {
16054
16620
  if (subdomainFromQuery) return subdomainFromQuery;
@@ -16108,6 +16674,7 @@ function OhhwellsBridge() {
16108
16674
  }
16109
16675
  }, []);
16110
16676
  const [fetchState, setFetchState] = useState13("idle");
16677
+ const [showBranding, setShowBranding] = useState13(false);
16111
16678
  const autoSaveTimers = useRef10(/* @__PURE__ */ new Map());
16112
16679
  const activeElRef = useRef10(null);
16113
16680
  const pointerHeldRef = useRef10(false);
@@ -16456,13 +17023,6 @@ function OhhwellsBridge() {
16456
17023
  const [isItemDragging, setIsItemDragging] = useState13(false);
16457
17024
  const [isFooterFrameSelection, setIsFooterFrameSelection] = useState13(false);
16458
17025
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
16459
- const [floatingPanel, setFloatingPanel] = useState13(null);
16460
- const floatingPanelOpenRef = useRef10(false);
16461
- floatingPanelOpenRef.current = floatingPanel !== null;
16462
- const [floatingPanelPos, setFloatingPanelPos] = useState13(null);
16463
- const [logoSizeDraft, setLogoSizeDraft] = useState13(null);
16464
- const [editorViewport, setEditorViewport] = useState13("desktop");
16465
- const [parentScrollSnap, setParentScrollSnap] = useState13(null);
16466
17026
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = useState13(null);
16467
17027
  const [footerHeadingVisible, setFooterHeadingVisible] = useState13(null);
16468
17028
  const footerDragRef = useRef10(null);
@@ -16480,6 +17040,13 @@ function OhhwellsBridge() {
16480
17040
  const brandKitRef = useRef10("");
16481
17041
  const stylesRef = useRef10("");
16482
17042
  const pendingDeleteUndoRef = useRef10(null);
17043
+ const [floatingPanel, setFloatingPanel] = useState13(null);
17044
+ const floatingPanelOpenRef = useRef10(false);
17045
+ const setFloatingPanelRef = useRef10(setFloatingPanel);
17046
+ const [floatingPanelPos, setFloatingPanelPos] = useState13(null);
17047
+ const [logoSizeDraft, setLogoSizeDraft] = useState13(null);
17048
+ const [editorViewport, setEditorViewport] = useState13("desktop");
17049
+ const [parentScrollSnap, setParentScrollSnap] = useState13(null);
16483
17050
  const [sitePages, setSitePages] = useState13([]);
16484
17051
  const [sectionsByPath, setSectionsByPath] = useState13({});
16485
17052
  const sectionsPrefetchGenRef = useRef10(0);
@@ -16488,7 +17055,18 @@ function OhhwellsBridge() {
16488
17055
  const linkPopoverOpenRef = useRef10(false);
16489
17056
  const linkPopoverGraceUntilRef = useRef10(0);
16490
17057
  setLinkPopoverRef.current = setLinkPopover;
17058
+ setFloatingPanelRef.current = setFloatingPanel;
16491
17059
  linkPopoverSessionRef.current = linkPopover;
17060
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
17061
+ useEffect13(() => {
17062
+ const syncViewport = () => {
17063
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
17064
+ setEditorViewport((prev) => prev === next ? prev : next);
17065
+ };
17066
+ syncViewport();
17067
+ window.addEventListener("resize", syncViewport);
17068
+ return () => window.removeEventListener("resize", syncViewport);
17069
+ }, []);
16492
17070
  const {
16493
17071
  navDragRef,
16494
17072
  navDropSlots,
@@ -17813,17 +18391,19 @@ function OhhwellsBridge() {
17813
18391
  }
17814
18392
  if (typeof content[STYLE_STORE_KEY] === "string") {
17815
18393
  stylesRef.current = content[STYLE_STORE_KEY];
18394
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
17816
18395
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
17817
18396
  }
17818
18397
  applyBrandChrome(content);
18398
+ initSectionInstancesFromContent(content, window.location.pathname);
17819
18399
  for (const [key, val] of Object.entries(content)) {
17820
18400
  if (key === "__ohw_sections") continue;
17821
18401
  if (key === AI_SECTIONS_KEY) continue;
18402
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
18403
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
17822
18404
  if (key === BRAND_KIT_KEY) continue;
17823
18405
  if (key === STYLE_STORE_KEY) continue;
17824
18406
  if (BRAND_CHROME_KEYS.has(key)) continue;
17825
- if (key === LOGO_PLACEHOLDER_KEY) continue;
17826
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
17827
18407
  if (applyVideoSettingNode(key, val)) continue;
17828
18408
  if (applyCarouselNode(key, val)) continue;
17829
18409
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -17851,6 +18431,8 @@ function OhhwellsBridge() {
17851
18431
  }
17852
18432
  } else if (el.dataset.ohwEditable === "link") {
17853
18433
  applyLinkHref(el, val);
18434
+ } else if (el.dataset.ohwEditable === "map") {
18435
+ applyMapQuery(el, val);
17854
18436
  } else if (el.dataset.ohwEditable === "icon") {
17855
18437
  applyIconMarkup(el, val);
17856
18438
  } else if (el.dataset.ohwEditable === "form") {
@@ -17871,7 +18453,6 @@ function OhhwellsBridge() {
17871
18453
  if (isEditModeRef.current) requestMissingSocialIconsRef.current();
17872
18454
  enforceLinkHrefs();
17873
18455
  initSectionsFromContent(content, true);
17874
- initSectionInstancesFromContent(content, window.location.pathname);
17875
18456
  sectionsLoadedRef.current = true;
17876
18457
  pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
17877
18458
  if (imageLoads.length === 0) return Promise.resolve();
@@ -17883,16 +18464,22 @@ function OhhwellsBridge() {
17883
18464
  };
17884
18465
  const cached = contentCache.get(subdomain);
17885
18466
  if (cached) {
18467
+ setShowBranding(brandingCache.get(subdomain) ?? false);
17886
18468
  applyContent(cached).finally(() => setFetchState("done"));
17887
18469
  return;
17888
18470
  }
17889
18471
  let cancelled = false;
17890
18472
  setFetchState("loading");
17891
18473
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
17892
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18474
+ const initialPath = pathname;
18475
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
18476
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
17893
18477
  if (cancelled) return;
17894
18478
  const content = data?.content ?? {};
18479
+ const branding = Boolean(data?.showBranding);
17895
18480
  contentCache.set(subdomain, content);
18481
+ brandingCache.set(subdomain, branding);
18482
+ setShowBranding(branding);
17896
18483
  return applyContent(content);
17897
18484
  }).catch(() => {
17898
18485
  }).finally(() => {
@@ -18020,16 +18607,17 @@ function OhhwellsBridge() {
18020
18607
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
18021
18608
  }
18022
18609
  if (typeof content[STYLE_STORE_KEY] === "string") {
18610
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
18023
18611
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
18024
18612
  }
18025
18613
  for (const [key, val] of Object.entries(content)) {
18026
18614
  if (key === "__ohw_sections") continue;
18027
18615
  if (key === AI_SECTIONS_KEY) continue;
18616
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
18617
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
18028
18618
  if (key === BRAND_KIT_KEY) continue;
18029
18619
  if (key === STYLE_STORE_KEY) continue;
18030
18620
  if (BRAND_CHROME_KEYS.has(key)) continue;
18031
- if (key === LOGO_PLACEHOLDER_KEY) continue;
18032
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
18033
18621
  if (applyVideoSettingNode(key, val)) continue;
18034
18622
  if (applyCarouselNode(key, val)) continue;
18035
18623
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -18044,6 +18632,8 @@ function OhhwellsBridge() {
18044
18632
  if (video && video.src !== val) applyVideoSrc(video, val);
18045
18633
  } else if (el.dataset.ohwEditable === "link") {
18046
18634
  applyLinkHref(el, val);
18635
+ } else if (el.dataset.ohwEditable === "map") {
18636
+ applyMapQuery(el, val);
18047
18637
  } else if (el.dataset.ohwEditable === "form") {
18048
18638
  } else if (isIconMarkupValue(val)) {
18049
18639
  } else if (el.innerHTML !== val) {
@@ -18075,6 +18665,17 @@ function OhhwellsBridge() {
18075
18665
  debounceTimer = setTimeout(applyFromCache, 150);
18076
18666
  };
18077
18667
  applyFromCache();
18668
+ const pathCacheKey = `${subdomain}::${pathname}`;
18669
+ if (!fetchedContentPaths.has(pathCacheKey)) {
18670
+ fetchedContentPaths.add(pathCacheKey);
18671
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
18672
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
18673
+ if (!data?.content) return;
18674
+ contentCache.set(subdomain, data.content);
18675
+ applyFromCache();
18676
+ }).catch(() => {
18677
+ });
18678
+ }
18078
18679
  observer = new MutationObserver(scheduleApply);
18079
18680
  observer.observe(document.body, { childList: true, subtree: true });
18080
18681
  return () => {
@@ -18190,26 +18791,11 @@ function OhhwellsBridge() {
18190
18791
  const t2 = setTimeout(measure, 500);
18191
18792
  const ro = new ResizeObserver(schedule);
18192
18793
  ro.observe(document.body);
18193
- let lastWidth = window.innerWidth;
18194
- let resizeTimers = [];
18195
- const clearResizeTimers = () => {
18196
- resizeTimers.forEach(clearTimeout);
18197
- resizeTimers = [];
18198
- };
18199
- const handleResize = () => {
18200
- if (window.innerWidth === lastWidth) return;
18201
- lastWidth = window.innerWidth;
18202
- clearResizeTimers();
18203
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
18204
- };
18205
- window.addEventListener("resize", handleResize);
18206
18794
  return () => {
18207
18795
  clearTimeout(t1);
18208
18796
  clearTimeout(t2);
18209
18797
  if (raf != null) cancelAnimationFrame(raf);
18210
18798
  ro.disconnect();
18211
- clearResizeTimers();
18212
- window.removeEventListener("resize", handleResize);
18213
18799
  };
18214
18800
  }, [pathname, isEditMode, postToParent2]);
18215
18801
  useEffect13(() => {
@@ -18455,9 +19041,6 @@ function OhhwellsBridge() {
18455
19041
  if (target.closest("[data-ohw-state-toggle]")) return;
18456
19042
  if (target.closest("[data-ohw-max-badge]")) return;
18457
19043
  if (isInsideLinkEditor(target)) return;
18458
- if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
18459
- clearMediaSelectionRef.current();
18460
- }
18461
19044
  if (isInsideFloatingPanel(target)) return;
18462
19045
  if (target.closest("[data-ohw-form-toolbar]")) return;
18463
19046
  if (target.closest(
@@ -18465,6 +19048,9 @@ function OhhwellsBridge() {
18465
19048
  )) {
18466
19049
  return;
18467
19050
  }
19051
+ if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
19052
+ clearMediaSelectionRef.current();
19053
+ }
18468
19054
  {
18469
19055
  const formEl = getFormElement(target);
18470
19056
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -18616,14 +19202,6 @@ function OhhwellsBridge() {
18616
19202
  }
18617
19203
  const clickedButton = findClosestButtonLike(target);
18618
19204
  const buttonOnMedia = Boolean(clickedButton && isMediaEditable(editable) && editable.contains(clickedButton));
18619
- console.log("[click-debug]", {
18620
- editableType: editable.dataset.ohwEditable,
18621
- editableTag: editable.tagName,
18622
- targetTag: target.tagName,
18623
- clickedButtonTag: clickedButton?.tagName ?? null,
18624
- buttonOnMedia,
18625
- isMediaEditableEditable: isMediaEditable(editable)
18626
- });
18627
19205
  if (isMediaEditable(editable) && !buttonOnMedia) {
18628
19206
  e.preventDefault();
18629
19207
  e.stopPropagation();
@@ -18650,11 +19228,6 @@ function OhhwellsBridge() {
18650
19228
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
18651
19229
  const hrefCtx = getHrefKeyFromElement(hrefLookupTarget);
18652
19230
  const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
18653
- console.log("[click-debug 2]", {
18654
- hrefLookupTargetTag: hrefLookupTarget.tagName,
18655
- hrefCtx: hrefCtx ? { key: hrefCtx.key } : null,
18656
- navAnchorTag: navAnchor?.tagName ?? null
18657
- });
18658
19231
  if (navAnchor) {
18659
19232
  e.preventDefault();
18660
19233
  e.stopPropagation();
@@ -18824,6 +19397,9 @@ function OhhwellsBridge() {
18824
19397
  setHoveredItemRect(null);
18825
19398
  hoveredNavContainerRef.current = null;
18826
19399
  setHoveredNavContainerRect(null);
19400
+ siblingHintElRef.current = null;
19401
+ setSiblingHintRect(null);
19402
+ setSiblingHintRects([]);
18827
19403
  return;
18828
19404
  }
18829
19405
  {
@@ -18942,7 +19518,6 @@ function OhhwellsBridge() {
18942
19518
  hoveredNavContainerRef.current = null;
18943
19519
  setHoveredNavContainerRect(null);
18944
19520
  hoveredItemElRef.current = editable;
18945
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
18946
19521
  }
18947
19522
  }
18948
19523
  }
@@ -19239,7 +19814,7 @@ function OhhwellsBridge() {
19239
19814
  }
19240
19815
  };
19241
19816
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
19242
- if (linkPopoverOpenRef.current) {
19817
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
19243
19818
  if (hoveredImageRef.current) {
19244
19819
  hoveredImageRef.current = null;
19245
19820
  hoveredImageHasTextOverlapRef.current = false;
@@ -19604,8 +20179,7 @@ function OhhwellsBridge() {
19604
20179
  };
19605
20180
  const handleMouseMove = (e) => {
19606
20181
  const { clientX, clientY } = e;
19607
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
19608
- if (isOverEditorChrome(clientX, clientY)) {
20182
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || isOverEditorChrome(clientX, clientY)) {
19609
20183
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
19610
20184
  formHoverElRef.current = null;
19611
20185
  setFormHoverRect(null);
@@ -19613,6 +20187,12 @@ function OhhwellsBridge() {
19613
20187
  setHoveredItemRect(null);
19614
20188
  hoveredNavContainerRef.current = null;
19615
20189
  setHoveredNavContainerRect(null);
20190
+ siblingHintElRef.current = null;
20191
+ setSiblingHintRect(null);
20192
+ setSiblingHintRects([]);
20193
+ dismissImageHover();
20194
+ clearImageHover();
20195
+ setSectionGap(null);
19616
20196
  return;
19617
20197
  }
19618
20198
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -19624,7 +20204,11 @@ function OhhwellsBridge() {
19624
20204
  if (e.data?.type !== "ow:pointer-sync") return;
19625
20205
  const { clientX, clientY } = e.data;
19626
20206
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
19627
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
20207
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
20208
+ dismissImageHover();
20209
+ clearImageHover();
20210
+ return;
20211
+ }
19628
20212
  if (probeSocialsRowAt(clientX, clientY)) return;
19629
20213
  probeSectionGapAt(clientX, clientY);
19630
20214
  probeImageAt(clientX, clientY);
@@ -19921,9 +20505,11 @@ function OhhwellsBridge() {
19921
20505
  }
19922
20506
  if (typeof content[STYLE_STORE_KEY] === "string") {
19923
20507
  stylesRef.current = content[STYLE_STORE_KEY];
20508
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
19924
20509
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
19925
20510
  }
19926
20511
  applyBrandChrome(content);
20512
+ initSectionInstancesFromContent(content, window.location.pathname);
19927
20513
  let sectionsJson = null;
19928
20514
  for (const [key, val] of Object.entries(content)) {
19929
20515
  if (key === "__ohw_sections") {
@@ -19931,11 +20517,11 @@ function OhhwellsBridge() {
19931
20517
  continue;
19932
20518
  }
19933
20519
  if (key === AI_SECTIONS_KEY) continue;
20520
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
20521
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
19934
20522
  if (key === BRAND_KIT_KEY) continue;
19935
20523
  if (key === STYLE_STORE_KEY) continue;
19936
20524
  if (BRAND_CHROME_KEYS.has(key)) continue;
19937
- if (key === LOGO_PLACEHOLDER_KEY) continue;
19938
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
19939
20525
  if (applyVideoSettingNode(key, val)) continue;
19940
20526
  if (applyCarouselNode(key, val)) continue;
19941
20527
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -19949,6 +20535,8 @@ function OhhwellsBridge() {
19949
20535
  if (video && video.src !== val) applyVideoSrc(video, val);
19950
20536
  } else if (el.dataset.ohwEditable === "link") {
19951
20537
  applyLinkHref(el, val);
20538
+ } else if (el.dataset.ohwEditable === "map") {
20539
+ applyMapQuery(el, val);
19952
20540
  } else if (el.dataset.ohwEditable === "icon") {
19953
20541
  applyIconMarkup(el, val);
19954
20542
  } else if (isIconMarkupValue(val)) {
@@ -19965,7 +20553,6 @@ function OhhwellsBridge() {
19965
20553
  sectionsLoadedRef.current = true;
19966
20554
  pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
19967
20555
  }
19968
- initSectionInstancesFromContent(content, window.location.pathname);
19969
20556
  editContentRef.current = { ...editContentRef.current, ...content };
19970
20557
  reconcileNavbarItemsFromContent(editContentRef.current);
19971
20558
  reconcileFooterOrderFromContent(editContentRef.current);
@@ -20110,12 +20697,35 @@ function OhhwellsBridge() {
20110
20697
  window.addEventListener("message", handleAiSetBrand);
20111
20698
  const handleAiSetStyles = (e) => {
20112
20699
  if (e.data?.type !== "ow:ai-set-styles") return;
20113
- const value = typeof e.data.value === "string" ? e.data.value : "";
20700
+ let value = typeof e.data.value === "string" ? e.data.value : "";
20114
20701
  const previous = stylesRef.current;
20702
+ let previousSections;
20703
+ const store = parseStyleStore(value);
20704
+ if (store) {
20705
+ const folded = foldAlignIntoTrees(parseAiSectionsState(aiSectionsRef.current), store);
20706
+ if (folded.changed) {
20707
+ const nextSections = serializeAiSectionsState(folded.state);
20708
+ if (nextSections !== aiSectionsRef.current) {
20709
+ previousSections = aiSectionsRef.current;
20710
+ aiSectionsRef.current = nextSections;
20711
+ applyAiSectionsToDom(folded.state);
20712
+ postToParentRef.current({
20713
+ type: "ow:change",
20714
+ nodes: [{ key: AI_SECTIONS_KEY, text: nextSections }]
20715
+ });
20716
+ }
20717
+ value = JSON.stringify(folded.store);
20718
+ }
20719
+ }
20115
20720
  stylesRef.current = value;
20116
20721
  applyStylesToDom(parseStyleStore(value));
20117
20722
  postToParentRef.current({ type: "ow:change", nodes: [{ key: STYLE_STORE_KEY, text: value }] });
20118
- postToParentRef.current({ type: "ow:ai-styles-applied", previous, value });
20723
+ postToParentRef.current({
20724
+ type: "ow:ai-styles-applied",
20725
+ previous,
20726
+ value,
20727
+ ...previousSections !== void 0 ? { previousSections } : {}
20728
+ });
20119
20729
  };
20120
20730
  window.addEventListener("message", handleAiSetStyles);
20121
20731
  const handleGetBrand = (e) => {
@@ -20187,6 +20797,34 @@ function OhhwellsBridge() {
20187
20797
  });
20188
20798
  };
20189
20799
  window.addEventListener("message", handleDeleteSection);
20800
+ const handleDuplicateSection = (e) => {
20801
+ if (e.data?.type !== "ow:duplicate-section") return;
20802
+ const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
20803
+ if (!instanceId) return;
20804
+ const newId = newInstanceId();
20805
+ const currentEntries = getPageSectionOrderEntries(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname);
20806
+ const result = duplicateSectionInstance(instanceId, newId, window.location.pathname, currentEntries);
20807
+ if (!result) return;
20808
+ const { entries, keyRekeys } = result;
20809
+ const orderJson = JSON.stringify(entries);
20810
+ const nodes = [{ key: SECTION_ORDER_KEY, text: orderJson }];
20811
+ for (const { from, to } of keyRekeys) {
20812
+ const inherited = editContentRef.current[from];
20813
+ if (inherited !== void 0) nodes.push({ key: to, text: inherited });
20814
+ }
20815
+ editContentRef.current = {
20816
+ ...editContentRef.current,
20817
+ ...Object.fromEntries(nodes.map(({ key, text }) => [key, text]))
20818
+ };
20819
+ setAiSectionOrder(orderJson, window.location.pathname);
20820
+ postToParentRef.current({ type: "ow:change", nodes });
20821
+ window.dispatchEvent(new Event("resize"));
20822
+ const duplicateHeight = document.body.scrollHeight;
20823
+ if (duplicateHeight > 50) postToParentRef.current({ type: "ow:height", height: duplicateHeight });
20824
+ const clone = document.querySelector(`[data-ohw-instance="${CSS.escape(newId)}"]`);
20825
+ if (clone) aiSectionApiRef.current?.selectFromElement(clone);
20826
+ };
20827
+ window.addEventListener("message", handleDuplicateSection);
20190
20828
  const handleDeactivate = (e) => {
20191
20829
  if (e.data?.type !== "ow:deactivate") return;
20192
20830
  if (document.documentElement.hasAttribute("data-ohw-panel-dragging")) return;
@@ -20196,6 +20834,12 @@ function OhhwellsBridge() {
20196
20834
  closeLinkPopoverRef.current();
20197
20835
  return;
20198
20836
  }
20837
+ if (floatingPanelOpenRef.current) {
20838
+ setFloatingPanelRef.current(null);
20839
+ deselectRef.current();
20840
+ deactivateRef.current();
20841
+ return;
20842
+ }
20199
20843
  deselectRef.current();
20200
20844
  deactivateRef.current();
20201
20845
  clearMediaSelectionRef.current();
@@ -20470,8 +21114,12 @@ function OhhwellsBridge() {
20470
21114
  if (inserted) {
20471
21115
  const tracker = getSectionsTracker();
20472
21116
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
20473
- const h = document.body.scrollHeight;
20474
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
21117
+ const reportHeight = () => {
21118
+ const h = document.body.scrollHeight;
21119
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
21120
+ };
21121
+ reportHeight();
21122
+ setTimeout(reportHeight, 500);
20475
21123
  }
20476
21124
  };
20477
21125
  const handleSwitchSchedule = (e) => {
@@ -20873,11 +21521,12 @@ function OhhwellsBridge() {
20873
21521
  window.removeEventListener("message", handleMoveSection);
20874
21522
  window.removeEventListener("message", handlePanelDragging);
20875
21523
  window.removeEventListener("message", handleDeleteSection);
21524
+ window.removeEventListener("message", handleDuplicateSection);
20876
21525
  window.removeEventListener("message", handleDeactivate);
20877
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
20878
21526
  window.removeEventListener("message", handleToastAction);
20879
21527
  window.removeEventListener("message", handleFormCount);
20880
21528
  window.removeEventListener("message", handleUiEscape);
21529
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
20881
21530
  autoSaveTimers.current.forEach(clearTimeout);
20882
21531
  autoSaveTimers.current.clear();
20883
21532
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -21080,7 +21729,7 @@ function OhhwellsBridge() {
21080
21729
  postToParent2({
21081
21730
  type: "ow:ready",
21082
21731
  version: "1",
21083
- bridgeVersion: "0.1.83",
21732
+ bridgeVersion: "0.1.85",
21084
21733
  path: pathname,
21085
21734
  nodes: collectEditableNodes(editContentRef.current),
21086
21735
  sections
@@ -21543,6 +22192,7 @@ function OhhwellsBridge() {
21543
22192
  return /* @__PURE__ */ jsxs20(Fragment8, { children: [
21544
22193
  /* @__PURE__ */ jsx33("div", { id: "ohw-loader", suppressHydrationWarning: true, style: { ...OHW_LOADER_STYLE, display: "none" }, children: /* @__PURE__ */ jsx33(OhwLoaderSpinner, {}) }),
21545
22194
  /* @__PURE__ */ jsx33("script", { suppressHydrationWarning: true, dangerouslySetInnerHTML: { __html: OHW_LOADER_PREHYDRATE_SCRIPT } }),
22195
+ subdomain && !isEditMode && showBranding && /* @__PURE__ */ jsx33(MadeWithOhhWells, {}),
21546
22196
  bridgeRoot ? createPortal2(
21547
22197
  /* @__PURE__ */ jsxs20(Fragment8, { children: [
21548
22198
  /* @__PURE__ */ jsx33("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
@@ -21998,6 +22648,59 @@ function OhhwellsBridge() {
21998
22648
  ) : null
21999
22649
  ] });
22000
22650
  }
22651
+
22652
+ // src/ui/EmptySection.tsx
22653
+ import Link3 from "next/link";
22654
+ import { Fragment as Fragment9, jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
22655
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
22656
+ return /* @__PURE__ */ jsxs21(Fragment9, { children: [
22657
+ /* @__PURE__ */ jsx34(
22658
+ "p",
22659
+ {
22660
+ style: {
22661
+ fontFamily: "var(--brand-font-body)",
22662
+ fontSize: "0.75rem",
22663
+ fontWeight: 500,
22664
+ letterSpacing: "0.15em",
22665
+ textTransform: "uppercase",
22666
+ color: "var(--brand-accent)",
22667
+ marginBottom: "1.5rem"
22668
+ },
22669
+ children: /* @__PURE__ */ jsx34(Link3, { href: homeHref, style: { color: "inherit" }, children: /* @__PURE__ */ jsx34("span", { ...eyebrowKey ? { "data-ohw-editable": "text", "data-ohw-key": eyebrowKey } : {}, children: "Home" }) })
22670
+ }
22671
+ ),
22672
+ /* @__PURE__ */ jsx34(
22673
+ "h1",
22674
+ {
22675
+ style: {
22676
+ fontFamily: "var(--brand-font-heading)",
22677
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
22678
+ lineHeight: 1.1,
22679
+ letterSpacing: "-0.025em",
22680
+ color: "var(--brand-text)",
22681
+ marginBottom: "1rem"
22682
+ },
22683
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
22684
+ children: title
22685
+ }
22686
+ ),
22687
+ /* @__PURE__ */ jsx34(
22688
+ "p",
22689
+ {
22690
+ style: {
22691
+ fontFamily: "var(--brand-font-body)",
22692
+ fontSize: "1rem",
22693
+ lineHeight: 1.7,
22694
+ fontWeight: 300,
22695
+ color: "var(--brand-text-muted)",
22696
+ maxWidth: "340px"
22697
+ },
22698
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
22699
+ children: "This page doesn't have any content yet."
22700
+ }
22701
+ )
22702
+ ] });
22703
+ }
22001
22704
  export {
22002
22705
  AI_DEFAULT_BRAND,
22003
22706
  AI_TREE_SCHEMA_VERSIONS,
@@ -22014,6 +22717,7 @@ export {
22014
22717
  DropdownMenuItem,
22015
22718
  DropdownMenuSeparator,
22016
22719
  DropdownMenuTrigger,
22720
+ EmptySection,
22017
22721
  ItemActionToolbar,
22018
22722
  ItemInteractionLayer,
22019
22723
  LinkEditorPanel,