@ohhwells/bridge 0.1.84-next.249 → 0.1.84-next.250

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
@@ -462,6 +462,9 @@ function styleSheetCss() {
462
462
  `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px}px !important; padding-bottom: ${px}px !important; }`
463
463
  );
464
464
  }
465
+ rules.push(
466
+ `[data-ohw-style-corners="sharp"] :is(.card, [data-ohw-card]) { border-radius: 0 !important; }`
467
+ );
465
468
  for (const align of ["left", "center", "right"]) {
466
469
  rules.push(`[data-ohw-style-align="${align}"] { text-align: ${align} !important; }`);
467
470
  }
@@ -492,6 +495,7 @@ var SECTION_ATTRS = {
492
495
  headlineScale: "data-ohw-style-headline",
493
496
  imageAspect: "data-ohw-style-aspect",
494
497
  spacing: "data-ohw-style-spacing",
498
+ cornerStyle: "data-ohw-style-corners",
495
499
  align: "data-ohw-style-align"
496
500
  };
497
501
  var NODE_WROTE_ATTR = "data-ohw-style-node";
@@ -636,6 +640,284 @@ var import_client = require("react-dom/client");
636
640
  // src/ui/ai-tree/AiTreeRenderer.tsx
637
641
  var import_react = __toESM(require("react"), 1);
638
642
  var import_lucide_react = require("lucide-react");
643
+
644
+ // src/lib/placeholder-imagery.ts
645
+ var U = (id) => `https://images.unsplash.com/photo-${id}?auto=format&fit=crop&w=1600&q=80`;
646
+ var GENERIC = [
647
+ U("1441986300917-64674bd600d8"),
648
+ U("1486406146926-c627a92ad1ab"),
649
+ U("1497032628192-86f99bcd76bc"),
650
+ U("1521737604893-d14cc237f11d"),
651
+ U("1522071820081-009f0129c71c"),
652
+ U("1519389950473-47ba0277781c"),
653
+ U("1460925895917-afdab827c52f"),
654
+ U("1504384308090-c894fdcc538d")
655
+ ];
656
+ var PEOPLE = [
657
+ U("1500648767791-00dcc994a43e"),
658
+ U("1494790108377-be9c29b29330"),
659
+ U("1507003211169-0a1dd7228f2d"),
660
+ U("1438761681033-6461ffad8d80"),
661
+ U("1544005313-94ddf0286df2"),
662
+ U("1472099645785-5658abf4ff4e"),
663
+ U("1519085360753-af0119f7cbe7"),
664
+ U("1534528741775-53994a69daeb")
665
+ ];
666
+ var THEMED = [
667
+ {
668
+ keywords: ["portrait", "headshot", "person", "people", "team", "staff", "avatar", "founder", "face"],
669
+ pool: PEOPLE
670
+ },
671
+ {
672
+ keywords: ["pet", "dog", "cat", "puppy", "kitten", "vet", "animal"],
673
+ pool: [
674
+ U("1548199973-03cce0bbc87b"),
675
+ U("1450778869180-41d0601e046e"),
676
+ U("1583511655857-d19b40a7a54e"),
677
+ U("1587300003388-59208cc962cb"),
678
+ U("1517849845537-4d257902454a"),
679
+ U("1601758228041-f3b2795255f1")
680
+ ]
681
+ },
682
+ {
683
+ keywords: [
684
+ "baker",
685
+ "bakery",
686
+ "cafe",
687
+ "coffee",
688
+ "latte",
689
+ "restaurant",
690
+ "pastr",
691
+ "bread",
692
+ "cake",
693
+ "cater",
694
+ "chef",
695
+ "kitchen",
696
+ "food",
697
+ "pizza",
698
+ "dessert",
699
+ "brunch",
700
+ "bistro",
701
+ "deli",
702
+ "dish",
703
+ "menu"
704
+ ],
705
+ pool: [
706
+ U("1509440159596-0249088772ff"),
707
+ U("1555507036-ab1f4038808a"),
708
+ U("1517433670267-08bbd4be890f"),
709
+ U("1486427944299-d1955d23e34d"),
710
+ U("1504754524776-8f4f37790ca0"),
711
+ U("1495474472287-4d71bcdd2085"),
712
+ U("1521017432531-fbd92d768814"),
713
+ U("1556909114-f6e7ad7d3136")
714
+ ]
715
+ },
716
+ {
717
+ keywords: [
718
+ "shop",
719
+ "store",
720
+ "boutique",
721
+ "retail",
722
+ "clothing",
723
+ "fashion",
724
+ "jewel",
725
+ "gift",
726
+ "florist",
727
+ "market",
728
+ "grocer",
729
+ "product",
730
+ "storefront"
731
+ ],
732
+ pool: [
733
+ U("1441984904996-e0b6ba687e04"),
734
+ U("1472851294608-062f824d29cc"),
735
+ U("1523381210434-271e8be1f52b"),
736
+ U("1534452203293-494d7ddbf7e0"),
737
+ U("1445205170230-053b83016050"),
738
+ U("1560243563-062bfc001d68")
739
+ ]
740
+ },
741
+ {
742
+ keywords: [
743
+ "yoga",
744
+ "pilates",
745
+ "fitness",
746
+ "gym",
747
+ "workout",
748
+ "trainer",
749
+ "wellness",
750
+ "meditat",
751
+ "massage",
752
+ "therap",
753
+ "physio",
754
+ "chiro",
755
+ "nutrition",
756
+ "spa",
757
+ "studio"
758
+ ],
759
+ pool: [
760
+ U("1544367567-0f2fcb009e0b"),
761
+ U("1506126613408-eca07ce68773"),
762
+ U("1545205597-3d9d02c29597"),
763
+ U("1552196563-55cd4e45efb3"),
764
+ U("1518611012118-696072aa579a"),
765
+ U("1571019613454-1cb2f99b2d8b"),
766
+ U("1540555700478-4be289fbecef"),
767
+ U("1519824145371-296894a0daa9")
768
+ ]
769
+ },
770
+ {
771
+ keywords: [
772
+ "salon",
773
+ "hairdress",
774
+ "haircut",
775
+ "barber",
776
+ "manicure",
777
+ "pedicure",
778
+ "nails",
779
+ "beauty",
780
+ "makeup",
781
+ "cosmetic",
782
+ "eyelash",
783
+ "eyebrow",
784
+ "skincare",
785
+ "esthetic",
786
+ "waxing",
787
+ "hair"
788
+ ],
789
+ pool: [
790
+ U("1560066984-138dadb4c035"),
791
+ U("1522337660859-02fbefca4702"),
792
+ U("1562322140-8baeececf3df"),
793
+ U("1521590832167-7bcbfaa6381f"),
794
+ U("1487412947147-5cebf100ffc2"),
795
+ U("1526045478516-99145907023c")
796
+ ]
797
+ },
798
+ {
799
+ keywords: [
800
+ "cleaning",
801
+ "plumb",
802
+ "electric",
803
+ "landscap",
804
+ "contractor",
805
+ "handyman",
806
+ "renov",
807
+ "hvac",
808
+ "roofing",
809
+ "painting",
810
+ "carpentry",
811
+ "flooring",
812
+ "movers",
813
+ "construction",
814
+ "tools"
815
+ ],
816
+ pool: [
817
+ U("1581578731548-c64695cc6952"),
818
+ U("1504307651254-35680f356dfd"),
819
+ U("1581092160562-40aa08e78837"),
820
+ U("1621905251189-08b45d6a269e"),
821
+ U("1558618666-fcd25c85cd64"),
822
+ U("1585128792020-803d29415281")
823
+ ]
824
+ },
825
+ {
826
+ keywords: [
827
+ "legal",
828
+ "attorney",
829
+ "lawyer",
830
+ "account",
831
+ "bookkeep",
832
+ "consult",
833
+ "coaching",
834
+ "financ",
835
+ "insurance",
836
+ "realtor",
837
+ "estate",
838
+ "marketing",
839
+ "agency",
840
+ "office",
841
+ "business",
842
+ "desk"
843
+ ],
844
+ pool: [
845
+ U("1497366216548-37526070297c"),
846
+ U("1497366811353-6870744d04b2"),
847
+ U("1454165804606-c3d57bc86b40"),
848
+ U("1521791136064-7986c2920216"),
849
+ U("1556761175-b413da4baf72"),
850
+ U("1542744173-8e7e53415bb0")
851
+ ]
852
+ },
853
+ {
854
+ keywords: [
855
+ "wedding",
856
+ "event",
857
+ "party",
858
+ "celebrat",
859
+ "venue",
860
+ "community",
861
+ "nonprofit",
862
+ "charity",
863
+ "workshop",
864
+ "photograph",
865
+ "concert"
866
+ ],
867
+ pool: [
868
+ U("1511578314322-379afb476865"),
869
+ U("1501281668745-f7f57925c3b4"),
870
+ U("1523580494863-6f3031224c94"),
871
+ U("1540575467063-178a50c2df87"),
872
+ U("1505236858219-8359eb29e329"),
873
+ U("1528605248644-14dd04022da1")
874
+ ]
875
+ }
876
+ ];
877
+ function poolForSubject(subject) {
878
+ for (const theme of THEMED) {
879
+ if (theme.keywords.some((k) => subject.includes(k))) {
880
+ return theme.pool;
881
+ }
882
+ }
883
+ return GENERIC;
884
+ }
885
+ function mixedHash(text) {
886
+ let hash = 2166136261;
887
+ for (let i = 0; i < text.length; i++) {
888
+ hash ^= text.charCodeAt(i);
889
+ hash = Math.imul(hash, 16777619);
890
+ }
891
+ return hash >>> 16 & 65535;
892
+ }
893
+ function resolvePlaceholderRef(ref) {
894
+ const match = /^placeholder:([a-z0-9-]+)$/.exec(ref);
895
+ if (!match) return null;
896
+ const subject = match[1];
897
+ const pool = poolForSubject(subject.replace(/-\d+$/, ""));
898
+ return pool[mixedHash(ref) % pool.length];
899
+ }
900
+ function collectPlaceholderRefs(tree) {
901
+ const seen = /* @__PURE__ */ new Set();
902
+ for (const match of JSON.stringify(tree ?? null).matchAll(/"(placeholder:[a-z0-9-]+)"/gu)) {
903
+ seen.add(match[1]);
904
+ }
905
+ return [...seen];
906
+ }
907
+ function buildPlaceholderMap(tree) {
908
+ const map = {};
909
+ const cursor = /* @__PURE__ */ new Map();
910
+ for (const ref of collectPlaceholderRefs(tree)) {
911
+ const subject = ref.slice("placeholder:".length).replace(/-\d+$/, "");
912
+ const pool = poolForSubject(subject);
913
+ const start = cursor.get(pool) ?? mixedHash(ref) % pool.length;
914
+ map[ref] = pool[start % pool.length];
915
+ cursor.set(pool, start + 1);
916
+ }
917
+ return map;
918
+ }
919
+
920
+ // src/ui/ai-tree/AiTreeRenderer.tsx
639
921
  var import_jsx_runtime = require("react/jsx-runtime");
640
922
  function lucideByName(name) {
641
923
  const pascal = name.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
@@ -649,6 +931,7 @@ var typeStyle = (spec, font) => ({
649
931
  fontWeight: spec.weight
650
932
  });
651
933
  var str = (value) => typeof value === "string" ? value : "";
934
+ var cardRadius = (slots) => slots.cornerStyle === "sharp" ? 0 : AI_TREE_TOKENS.radiusCard;
652
935
  var featureLines = (value) => value.split(/\n|<br\s*\/?>/i).map((line) => line.trim()).filter(Boolean);
653
936
  var CHECK_MASK = `data:image/svg+xml,${encodeURIComponent(
654
937
  '<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>'
@@ -991,10 +1274,11 @@ function PricingCard({ node, ctx, path }) {
991
1274
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
992
1275
  "div",
993
1276
  {
1277
+ "data-ohw-card": "",
994
1278
  style: {
995
1279
  background: hasBg ? ctx.brand.palette.light : "transparent",
996
1280
  border: `1px solid ${dark}`,
997
- borderRadius: AI_TREE_TOKENS.radiusCard,
1281
+ borderRadius: cardRadius(slots),
998
1282
  padding: AI_TREE_TOKENS.paddingBlock,
999
1283
  display: "flex",
1000
1284
  flexDirection: "column",
@@ -1099,10 +1383,11 @@ function TestimonialCard({ node, ctx, path }) {
1099
1383
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1100
1384
  "div",
1101
1385
  {
1386
+ "data-ohw-card": "",
1102
1387
  "data-ai-avatar-pos": avatarPos ?? void 0,
1103
1388
  style: {
1104
1389
  background: hasBg ? ctx.cardSurface : "transparent",
1105
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1390
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1106
1391
  overflow: "hidden",
1107
1392
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1108
1393
  minWidth: 0
@@ -1137,10 +1422,11 @@ function TeamCard({ node, ctx, path }) {
1137
1422
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1138
1423
  "div",
1139
1424
  {
1425
+ "data-ohw-card": "",
1140
1426
  "data-ai-avatar-pos": avatarPos ?? void 0,
1141
1427
  style: {
1142
1428
  background: hasBg ? ctx.cardSurface : "transparent",
1143
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1429
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1144
1430
  overflow: "hidden",
1145
1431
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1146
1432
  minWidth: 0,
@@ -1311,9 +1597,10 @@ function CardBlock({ node, ctx, path }) {
1311
1597
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1312
1598
  "div",
1313
1599
  {
1600
+ "data-ohw-card": "",
1314
1601
  style: {
1315
1602
  background: hasBg ? ctx.cardSurface : "transparent",
1316
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1603
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1317
1604
  overflow: "hidden",
1318
1605
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1319
1606
  display: horizontal ? "flex" : "block",
@@ -1341,7 +1628,7 @@ function CardBlock({ node, ctx, path }) {
1341
1628
  ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1342
1629
  "div",
1343
1630
  {
1344
- 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" },
1631
+ 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" },
1345
1632
  children: media
1346
1633
  }
1347
1634
  )),
@@ -1752,6 +2039,32 @@ function renderNode(node, ctx, path) {
1752
2039
  if (child) {
1753
2040
  return renderNode(child, ctx, `${path}.c0`);
1754
2041
  }
2042
+ if (str(slots.provider) === "map" && str(slots.query)) {
2043
+ const query = str(slots.query);
2044
+ const mapAttrs = ctx.keyFor ? {
2045
+ "data-ohw-key": ctx.keyFor(`${path}.query`),
2046
+ "data-ohw-editable": "map",
2047
+ "data-ohw-map-query": query
2048
+ } : {};
2049
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2050
+ "iframe",
2051
+ {
2052
+ ...mapAttrs,
2053
+ "data-ai-embed": "map",
2054
+ title: str(slots.title) || "Map",
2055
+ src: `https://www.google.com/maps?q=${encodeURIComponent(query)}&output=embed`,
2056
+ loading: "lazy",
2057
+ referrerPolicy: "no-referrer-when-downgrade",
2058
+ style: {
2059
+ width: "100%",
2060
+ minHeight: 320,
2061
+ border: 0,
2062
+ borderRadius: AI_TREE_TOKENS.radiusCard,
2063
+ display: "block"
2064
+ }
2065
+ }
2066
+ );
2067
+ }
1755
2068
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1756
2069
  "div",
1757
2070
  {
@@ -1908,9 +2221,14 @@ function AiTreeRenderer({
1908
2221
  const resolvedBrand = brand ?? AI_DEFAULT_BRAND;
1909
2222
  const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
1910
2223
  const blockBrand = band?.brand ?? resolvedBrand;
2224
+ const placeholderMap = buildPlaceholderMap(tree);
1911
2225
  const ctx = {
1912
2226
  brand: blockBrand,
1913
- resolveMedia: resolveMedia ?? (() => null),
2227
+ // An owner/library ref resolves through the host resolver; a `placeholder:<subject>` ref the
2228
+ // host cannot resolve falls back to real stock photography (the per-section map first, then a
2229
+ // standalone resolve), so generated galleries, image rows, and overlay backgrounds arrive with
2230
+ // photos instead of grey boxes.
2231
+ resolveMedia: (ref) => resolveMedia?.(ref) ?? placeholderMap[ref] ?? resolvePlaceholderRef(ref),
1914
2232
  cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
1915
2233
  keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
1916
2234
  sectionAlignment: (tree.settings ?? {}).alignment === "center" ? "center" : "left",
@@ -14542,6 +14860,9 @@ function collectEditableNodes(extraContent, root = document) {
14542
14860
  if (el.dataset.ohwEditable === "link") {
14543
14861
  return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref3(el) };
14544
14862
  }
14863
+ if (el.dataset.ohwEditable === "map") {
14864
+ return { key: el.dataset.ohwKey ?? "", type: "map", text: el.dataset.ohwMapQuery ?? "" };
14865
+ }
14545
14866
  return {
14546
14867
  key: el.dataset.ohwKey ?? "",
14547
14868
  type: el.dataset.ohwEditable ?? "text",
@@ -15304,7 +15625,7 @@ var EDITOR_CHROME_SELECTOR = '[data-ohw-toolbar], [data-ohw-form-toolbar], [data
15304
15625
  function isOverEditorChrome(x, y) {
15305
15626
  return document.elementsFromPoint(x, y).some((el) => el instanceof HTMLElement && el.matches(EDITOR_CHROME_SELECTOR));
15306
15627
  }
15307
- 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"])';
15628
+ 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"])';
15308
15629
  function getVideoEl2(el) {
15309
15630
  return el instanceof HTMLVideoElement ? el : el.querySelector("video");
15310
15631
  }
@@ -15360,6 +15681,12 @@ function applyVideoSettingNode(key, val) {
15360
15681
  });
15361
15682
  return true;
15362
15683
  }
15684
+ function applyMapQuery(el, val) {
15685
+ if (!(el instanceof HTMLIFrameElement)) return;
15686
+ const nextSrc = `https://www.google.com/maps?q=${encodeURIComponent(val)}&output=embed`;
15687
+ if (el.src !== nextSrc) el.src = nextSrc;
15688
+ el.setAttribute("data-ohw-map-query", val);
15689
+ }
15363
15690
  function applyLinkByKey(key, val) {
15364
15691
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
15365
15692
  if (el.dataset.ohwEditable === "link") applyLinkHref(el, val);
@@ -18171,6 +18498,8 @@ function OhhwellsBridge() {
18171
18498
  }
18172
18499
  } else if (el.dataset.ohwEditable === "link") {
18173
18500
  applyLinkHref(el, val);
18501
+ } else if (el.dataset.ohwEditable === "map") {
18502
+ applyMapQuery(el, val);
18174
18503
  } else if (el.dataset.ohwEditable === "icon") {
18175
18504
  applyIconMarkup(el, val);
18176
18505
  } else if (el.dataset.ohwEditable === "form") {
@@ -18369,6 +18698,8 @@ function OhhwellsBridge() {
18369
18698
  if (video && video.src !== val) applyVideoSrc(video, val);
18370
18699
  } else if (el.dataset.ohwEditable === "link") {
18371
18700
  applyLinkHref(el, val);
18701
+ } else if (el.dataset.ohwEditable === "map") {
18702
+ applyMapQuery(el, val);
18372
18703
  } else if (el.dataset.ohwEditable === "form") {
18373
18704
  } else if (isIconMarkupValue(val)) {
18374
18705
  } else if (el.innerHTML !== val) {
@@ -20269,6 +20600,8 @@ function OhhwellsBridge() {
20269
20600
  if (video && video.src !== val) applyVideoSrc(video, val);
20270
20601
  } else if (el.dataset.ohwEditable === "link") {
20271
20602
  applyLinkHref(el, val);
20603
+ } else if (el.dataset.ohwEditable === "map") {
20604
+ applyMapQuery(el, val);
20272
20605
  } else if (el.dataset.ohwEditable === "icon") {
20273
20606
  applyIconMarkup(el, val);
20274
20607
  } else if (isIconMarkupValue(val)) {