@ohhwells/bridge 0.1.96 → 0.1.97

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
@@ -491,6 +491,10 @@ function styleSheetCss() {
491
491
  `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px}px !important; padding-bottom: ${px}px !important; }`
492
492
  );
493
493
  }
494
+ rules.push(
495
+ `[data-ohw-style-corners="sharp"] :is(.card, [data-ohw-card], img, picture, figure, [data-ohw-editable="bg-image"]) { border-radius: 0 !important; }`,
496
+ `[data-ohw-style-corners="sharp"] :has(> img) { border-radius: 0 !important; }`
497
+ );
494
498
  for (const align of ["left", "center", "right"]) {
495
499
  rules.push(`[data-ohw-style-align="${align}"] { text-align: ${align} !important; }`);
496
500
  }
@@ -521,6 +525,7 @@ var SECTION_ATTRS = {
521
525
  headlineScale: "data-ohw-style-headline",
522
526
  imageAspect: "data-ohw-style-aspect",
523
527
  spacing: "data-ohw-style-spacing",
528
+ cornerStyle: "data-ohw-style-corners",
524
529
  align: "data-ohw-style-align"
525
530
  };
526
531
  var NODE_WROTE_ATTR = "data-ohw-style-node";
@@ -913,6 +918,284 @@ function initSectionInstancesFromContent(content, currentPath) {
913
918
  // src/ui/ai-tree/AiTreeRenderer.tsx
914
919
  var import_react = __toESM(require("react"), 1);
915
920
  var import_lucide_react = require("lucide-react");
921
+
922
+ // src/lib/placeholder-imagery.ts
923
+ var U = (id) => `https://images.unsplash.com/photo-${id}?auto=format&fit=crop&w=1600&q=80`;
924
+ var GENERIC = [
925
+ U("1441986300917-64674bd600d8"),
926
+ U("1486406146926-c627a92ad1ab"),
927
+ U("1497032628192-86f99bcd76bc"),
928
+ U("1521737604893-d14cc237f11d"),
929
+ U("1522071820081-009f0129c71c"),
930
+ U("1519389950473-47ba0277781c"),
931
+ U("1460925895917-afdab827c52f"),
932
+ U("1504384308090-c894fdcc538d")
933
+ ];
934
+ var PEOPLE = [
935
+ U("1500648767791-00dcc994a43e"),
936
+ U("1494790108377-be9c29b29330"),
937
+ U("1507003211169-0a1dd7228f2d"),
938
+ U("1438761681033-6461ffad8d80"),
939
+ U("1544005313-94ddf0286df2"),
940
+ U("1472099645785-5658abf4ff4e"),
941
+ U("1519085360753-af0119f7cbe7"),
942
+ U("1534528741775-53994a69daeb")
943
+ ];
944
+ var THEMED = [
945
+ {
946
+ keywords: ["portrait", "headshot", "person", "people", "team", "staff", "avatar", "founder", "face"],
947
+ pool: PEOPLE
948
+ },
949
+ {
950
+ keywords: ["pet", "dog", "cat", "puppy", "kitten", "vet", "animal"],
951
+ pool: [
952
+ U("1548199973-03cce0bbc87b"),
953
+ U("1450778869180-41d0601e046e"),
954
+ U("1583511655857-d19b40a7a54e"),
955
+ U("1587300003388-59208cc962cb"),
956
+ U("1517849845537-4d257902454a"),
957
+ U("1601758228041-f3b2795255f1")
958
+ ]
959
+ },
960
+ {
961
+ keywords: [
962
+ "baker",
963
+ "bakery",
964
+ "cafe",
965
+ "coffee",
966
+ "latte",
967
+ "restaurant",
968
+ "pastr",
969
+ "bread",
970
+ "cake",
971
+ "cater",
972
+ "chef",
973
+ "kitchen",
974
+ "food",
975
+ "pizza",
976
+ "dessert",
977
+ "brunch",
978
+ "bistro",
979
+ "deli",
980
+ "dish",
981
+ "menu"
982
+ ],
983
+ pool: [
984
+ U("1509440159596-0249088772ff"),
985
+ U("1555507036-ab1f4038808a"),
986
+ U("1517433670267-08bbd4be890f"),
987
+ U("1486427944299-d1955d23e34d"),
988
+ U("1504754524776-8f4f37790ca0"),
989
+ U("1495474472287-4d71bcdd2085"),
990
+ U("1521017432531-fbd92d768814"),
991
+ U("1556909114-f6e7ad7d3136")
992
+ ]
993
+ },
994
+ {
995
+ keywords: [
996
+ "shop",
997
+ "store",
998
+ "boutique",
999
+ "retail",
1000
+ "clothing",
1001
+ "fashion",
1002
+ "jewel",
1003
+ "gift",
1004
+ "florist",
1005
+ "market",
1006
+ "grocer",
1007
+ "product",
1008
+ "storefront"
1009
+ ],
1010
+ pool: [
1011
+ U("1441984904996-e0b6ba687e04"),
1012
+ U("1472851294608-062f824d29cc"),
1013
+ U("1523381210434-271e8be1f52b"),
1014
+ U("1534452203293-494d7ddbf7e0"),
1015
+ U("1445205170230-053b83016050"),
1016
+ U("1560243563-062bfc001d68")
1017
+ ]
1018
+ },
1019
+ {
1020
+ keywords: [
1021
+ "yoga",
1022
+ "pilates",
1023
+ "fitness",
1024
+ "gym",
1025
+ "workout",
1026
+ "trainer",
1027
+ "wellness",
1028
+ "meditat",
1029
+ "massage",
1030
+ "therap",
1031
+ "physio",
1032
+ "chiro",
1033
+ "nutrition",
1034
+ "spa",
1035
+ "studio"
1036
+ ],
1037
+ pool: [
1038
+ U("1544367567-0f2fcb009e0b"),
1039
+ U("1506126613408-eca07ce68773"),
1040
+ U("1545205597-3d9d02c29597"),
1041
+ U("1552196563-55cd4e45efb3"),
1042
+ U("1518611012118-696072aa579a"),
1043
+ U("1571019613454-1cb2f99b2d8b"),
1044
+ U("1540555700478-4be289fbecef"),
1045
+ U("1519824145371-296894a0daa9")
1046
+ ]
1047
+ },
1048
+ {
1049
+ keywords: [
1050
+ "salon",
1051
+ "hairdress",
1052
+ "haircut",
1053
+ "barber",
1054
+ "manicure",
1055
+ "pedicure",
1056
+ "nails",
1057
+ "beauty",
1058
+ "makeup",
1059
+ "cosmetic",
1060
+ "eyelash",
1061
+ "eyebrow",
1062
+ "skincare",
1063
+ "esthetic",
1064
+ "waxing",
1065
+ "hair"
1066
+ ],
1067
+ pool: [
1068
+ U("1560066984-138dadb4c035"),
1069
+ U("1522337660859-02fbefca4702"),
1070
+ U("1562322140-8baeececf3df"),
1071
+ U("1521590832167-7bcbfaa6381f"),
1072
+ U("1487412947147-5cebf100ffc2"),
1073
+ U("1526045478516-99145907023c")
1074
+ ]
1075
+ },
1076
+ {
1077
+ keywords: [
1078
+ "cleaning",
1079
+ "plumb",
1080
+ "electric",
1081
+ "landscap",
1082
+ "contractor",
1083
+ "handyman",
1084
+ "renov",
1085
+ "hvac",
1086
+ "roofing",
1087
+ "painting",
1088
+ "carpentry",
1089
+ "flooring",
1090
+ "movers",
1091
+ "construction",
1092
+ "tools"
1093
+ ],
1094
+ pool: [
1095
+ U("1581578731548-c64695cc6952"),
1096
+ U("1504307651254-35680f356dfd"),
1097
+ U("1581092160562-40aa08e78837"),
1098
+ U("1621905251189-08b45d6a269e"),
1099
+ U("1558618666-fcd25c85cd64"),
1100
+ U("1585128792020-803d29415281")
1101
+ ]
1102
+ },
1103
+ {
1104
+ keywords: [
1105
+ "legal",
1106
+ "attorney",
1107
+ "lawyer",
1108
+ "account",
1109
+ "bookkeep",
1110
+ "consult",
1111
+ "coaching",
1112
+ "financ",
1113
+ "insurance",
1114
+ "realtor",
1115
+ "estate",
1116
+ "marketing",
1117
+ "agency",
1118
+ "office",
1119
+ "business",
1120
+ "desk"
1121
+ ],
1122
+ pool: [
1123
+ U("1497366216548-37526070297c"),
1124
+ U("1497366811353-6870744d04b2"),
1125
+ U("1454165804606-c3d57bc86b40"),
1126
+ U("1521791136064-7986c2920216"),
1127
+ U("1556761175-b413da4baf72"),
1128
+ U("1542744173-8e7e53415bb0")
1129
+ ]
1130
+ },
1131
+ {
1132
+ keywords: [
1133
+ "wedding",
1134
+ "event",
1135
+ "party",
1136
+ "celebrat",
1137
+ "venue",
1138
+ "community",
1139
+ "nonprofit",
1140
+ "charity",
1141
+ "workshop",
1142
+ "photograph",
1143
+ "concert"
1144
+ ],
1145
+ pool: [
1146
+ U("1511578314322-379afb476865"),
1147
+ U("1501281668745-f7f57925c3b4"),
1148
+ U("1523580494863-6f3031224c94"),
1149
+ U("1540575467063-178a50c2df87"),
1150
+ U("1505236858219-8359eb29e329"),
1151
+ U("1528605248644-14dd04022da1")
1152
+ ]
1153
+ }
1154
+ ];
1155
+ function poolForSubject(subject) {
1156
+ for (const theme of THEMED) {
1157
+ if (theme.keywords.some((k) => subject.includes(k))) {
1158
+ return theme.pool;
1159
+ }
1160
+ }
1161
+ return GENERIC;
1162
+ }
1163
+ function mixedHash(text) {
1164
+ let hash = 2166136261;
1165
+ for (let i = 0; i < text.length; i++) {
1166
+ hash ^= text.charCodeAt(i);
1167
+ hash = Math.imul(hash, 16777619);
1168
+ }
1169
+ return hash >>> 16 & 65535;
1170
+ }
1171
+ function resolvePlaceholderRef(ref) {
1172
+ const match = /^placeholder:([a-z0-9-]+)$/.exec(ref);
1173
+ if (!match) return null;
1174
+ const subject = match[1];
1175
+ const pool = poolForSubject(subject.replace(/-\d+$/, ""));
1176
+ return pool[mixedHash(ref) % pool.length];
1177
+ }
1178
+ function collectPlaceholderRefs(tree) {
1179
+ const seen = /* @__PURE__ */ new Set();
1180
+ for (const match of JSON.stringify(tree ?? null).matchAll(/"(placeholder:[a-z0-9-]+)"/gu)) {
1181
+ seen.add(match[1]);
1182
+ }
1183
+ return [...seen];
1184
+ }
1185
+ function buildPlaceholderMap(tree) {
1186
+ const map = {};
1187
+ const cursor = /* @__PURE__ */ new Map();
1188
+ for (const ref of collectPlaceholderRefs(tree)) {
1189
+ const subject = ref.slice("placeholder:".length).replace(/-\d+$/, "");
1190
+ const pool = poolForSubject(subject);
1191
+ const start = cursor.get(pool) ?? mixedHash(ref) % pool.length;
1192
+ map[ref] = pool[start % pool.length];
1193
+ cursor.set(pool, start + 1);
1194
+ }
1195
+ return map;
1196
+ }
1197
+
1198
+ // src/ui/ai-tree/AiTreeRenderer.tsx
916
1199
  var import_jsx_runtime = require("react/jsx-runtime");
917
1200
  function lucideByName(name) {
918
1201
  const pascal = name.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
@@ -926,6 +1209,7 @@ var typeStyle = (spec, font) => ({
926
1209
  fontWeight: spec.weight
927
1210
  });
928
1211
  var str = (value) => typeof value === "string" ? value : "";
1212
+ var cardRadius = (slots) => slots.cornerStyle === "sharp" ? 0 : AI_TREE_TOKENS.radiusCard;
929
1213
  var featureLines = (value) => value.split(/\n|<br\s*\/?>/i).map((line) => line.trim()).filter(Boolean);
930
1214
  var CHECK_MASK = `data:image/svg+xml,${encodeURIComponent(
931
1215
  '<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>'
@@ -1271,10 +1555,11 @@ function PricingCard({ node, ctx, path }) {
1271
1555
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1272
1556
  "div",
1273
1557
  {
1558
+ "data-ohw-card": "",
1274
1559
  style: {
1275
1560
  background: hasBg ? ctx.brand.palette.light : "transparent",
1276
1561
  border: `1px solid ${dark}`,
1277
- borderRadius: AI_TREE_TOKENS.radiusCard,
1562
+ borderRadius: cardRadius(slots),
1278
1563
  padding: AI_TREE_TOKENS.paddingBlock,
1279
1564
  display: "flex",
1280
1565
  flexDirection: "column",
@@ -1379,10 +1664,11 @@ function TestimonialCard({ node, ctx, path }) {
1379
1664
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1380
1665
  "div",
1381
1666
  {
1667
+ "data-ohw-card": "",
1382
1668
  "data-ai-avatar-pos": avatarPos ?? void 0,
1383
1669
  style: {
1384
1670
  background: hasBg ? ctx.cardSurface : "transparent",
1385
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1671
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1386
1672
  overflow: "hidden",
1387
1673
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1388
1674
  minWidth: 0
@@ -1417,10 +1703,11 @@ function TeamCard({ node, ctx, path }) {
1417
1703
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1418
1704
  "div",
1419
1705
  {
1706
+ "data-ohw-card": "",
1420
1707
  "data-ai-avatar-pos": avatarPos ?? void 0,
1421
1708
  style: {
1422
1709
  background: hasBg ? ctx.cardSurface : "transparent",
1423
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1710
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1424
1711
  overflow: "hidden",
1425
1712
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1426
1713
  minWidth: 0,
@@ -1591,9 +1878,10 @@ function CardBlock({ node, ctx, path }) {
1591
1878
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1592
1879
  "div",
1593
1880
  {
1881
+ "data-ohw-card": "",
1594
1882
  style: {
1595
1883
  background: hasBg ? ctx.cardSurface : "transparent",
1596
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1884
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1597
1885
  overflow: "hidden",
1598
1886
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1599
1887
  display: horizontal ? "flex" : "block",
@@ -1621,7 +1909,7 @@ function CardBlock({ node, ctx, path }) {
1621
1909
  ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1622
1910
  "div",
1623
1911
  {
1624
- 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" },
1912
+ 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" },
1625
1913
  children: media
1626
1914
  }
1627
1915
  )),
@@ -2032,6 +2320,32 @@ function renderNode(node, ctx, path) {
2032
2320
  if (child) {
2033
2321
  return renderNode(child, ctx, `${path}.c0`);
2034
2322
  }
2323
+ if (str(slots.provider) === "map" && str(slots.query)) {
2324
+ const query = str(slots.query);
2325
+ const mapAttrs = ctx.keyFor ? {
2326
+ "data-ohw-key": ctx.keyFor(`${path}.query`),
2327
+ "data-ohw-editable": "map",
2328
+ "data-ohw-map-query": query
2329
+ } : {};
2330
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2331
+ "iframe",
2332
+ {
2333
+ ...mapAttrs,
2334
+ "data-ai-embed": "map",
2335
+ title: str(slots.title) || "Map",
2336
+ src: `https://www.google.com/maps?q=${encodeURIComponent(query)}&output=embed`,
2337
+ loading: "lazy",
2338
+ referrerPolicy: "no-referrer-when-downgrade",
2339
+ style: {
2340
+ width: "100%",
2341
+ minHeight: 320,
2342
+ border: 0,
2343
+ borderRadius: AI_TREE_TOKENS.radiusCard,
2344
+ display: "block"
2345
+ }
2346
+ }
2347
+ );
2348
+ }
2035
2349
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2036
2350
  "div",
2037
2351
  {
@@ -2185,9 +2499,14 @@ function AiTreeRenderer({
2185
2499
  const resolvedBrand = brand ?? AI_DEFAULT_BRAND;
2186
2500
  const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
2187
2501
  const blockBrand = band?.brand ?? resolvedBrand;
2502
+ const placeholderMap = buildPlaceholderMap(tree);
2188
2503
  const ctx = {
2189
2504
  brand: blockBrand,
2190
- resolveMedia: resolveMedia ?? (() => null),
2505
+ // An owner/library ref resolves through the host resolver; a `placeholder:<subject>` ref the
2506
+ // host cannot resolve falls back to real stock photography (the per-section map first, then a
2507
+ // standalone resolve), so generated galleries, image rows, and overlay backgrounds arrive with
2508
+ // photos instead of grey boxes.
2509
+ resolveMedia: (ref) => resolveMedia?.(ref) ?? placeholderMap[ref] ?? resolvePlaceholderRef(ref),
2191
2510
  cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
2192
2511
  keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
2193
2512
  sectionAlignment: (tree.settings ?? {}).alignment === "center" ? "center" : "left",
@@ -14665,6 +14984,9 @@ function collectEditableNodes(extraContent, root = document) {
14665
14984
  if (el.dataset.ohwEditable === "link") {
14666
14985
  return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref3(el) };
14667
14986
  }
14987
+ if (el.dataset.ohwEditable === "map") {
14988
+ return { key: el.dataset.ohwKey ?? "", type: "map", text: el.dataset.ohwMapQuery ?? "" };
14989
+ }
14668
14990
  return {
14669
14991
  key: el.dataset.ohwKey ?? "",
14670
14992
  type: el.dataset.ohwEditable ?? "text",
@@ -15448,7 +15770,7 @@ var EDITOR_CHROME_SELECTOR = '[data-ohw-toolbar], [data-ohw-form-toolbar], [data
15448
15770
  function isOverEditorChrome(x, y) {
15449
15771
  return document.elementsFromPoint(x, y).some((el) => el instanceof HTMLElement && el.matches(EDITOR_CHROME_SELECTOR));
15450
15772
  }
15451
- 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"])';
15773
+ 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"])';
15452
15774
  function getVideoEl2(el) {
15453
15775
  return el instanceof HTMLVideoElement ? el : el.querySelector("video");
15454
15776
  }
@@ -15504,6 +15826,12 @@ function applyVideoSettingNode(key, val) {
15504
15826
  });
15505
15827
  return true;
15506
15828
  }
15829
+ function applyMapQuery(el, val) {
15830
+ if (!(el instanceof HTMLIFrameElement)) return;
15831
+ const nextSrc = `https://www.google.com/maps?q=${encodeURIComponent(val)}&output=embed`;
15832
+ if (el.src !== nextSrc) el.src = nextSrc;
15833
+ el.setAttribute("data-ohw-map-query", val);
15834
+ }
15507
15835
  function applyLinkByKey(key, val) {
15508
15836
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
15509
15837
  if (el.dataset.ohwEditable === "link") applyLinkHref(el, val);
@@ -18304,6 +18632,8 @@ function OhhwellsBridge() {
18304
18632
  }
18305
18633
  } else if (el.dataset.ohwEditable === "link") {
18306
18634
  applyLinkHref(el, val);
18635
+ } else if (el.dataset.ohwEditable === "map") {
18636
+ applyMapQuery(el, val);
18307
18637
  } else if (el.dataset.ohwEditable === "icon") {
18308
18638
  applyIconMarkup(el, val);
18309
18639
  } else if (el.dataset.ohwEditable === "form") {
@@ -18500,6 +18830,8 @@ function OhhwellsBridge() {
18500
18830
  if (video && video.src !== val) applyVideoSrc(video, val);
18501
18831
  } else if (el.dataset.ohwEditable === "link") {
18502
18832
  applyLinkHref(el, val);
18833
+ } else if (el.dataset.ohwEditable === "map") {
18834
+ applyMapQuery(el, val);
18503
18835
  } else if (el.dataset.ohwEditable === "form") {
18504
18836
  } else if (isIconMarkupValue(val)) {
18505
18837
  } else if (el.innerHTML !== val) {
@@ -20473,6 +20805,8 @@ function OhhwellsBridge() {
20473
20805
  if (video && video.src !== val) applyVideoSrc(video, val);
20474
20806
  } else if (el.dataset.ohwEditable === "link") {
20475
20807
  applyLinkHref(el, val);
20808
+ } else if (el.dataset.ohwEditable === "map") {
20809
+ applyMapQuery(el, val);
20476
20810
  } else if (el.dataset.ohwEditable === "icon") {
20477
20811
  applyIconMarkup(el, val);
20478
20812
  } else if (isIconMarkupValue(val)) {
@@ -21709,7 +22043,7 @@ function OhhwellsBridge() {
21709
22043
  postToParent2({
21710
22044
  type: "ow:ready",
21711
22045
  version: "1",
21712
- bridgeVersion: "0.1.95",
22046
+ bridgeVersion: "0.1.96",
21713
22047
  path: pathname,
21714
22048
  nodes: collectEditableNodes(editContentRef.current),
21715
22049
  sections