@ohhwells/bridge 0.1.95 → 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.js CHANGED
@@ -418,6 +418,10 @@ function styleSheetCss() {
418
418
  `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px}px !important; padding-bottom: ${px}px !important; }`
419
419
  );
420
420
  }
421
+ rules.push(
422
+ `[data-ohw-style-corners="sharp"] :is(.card, [data-ohw-card], img, picture, figure, [data-ohw-editable="bg-image"]) { border-radius: 0 !important; }`,
423
+ `[data-ohw-style-corners="sharp"] :has(> img) { border-radius: 0 !important; }`
424
+ );
421
425
  for (const align of ["left", "center", "right"]) {
422
426
  rules.push(`[data-ohw-style-align="${align}"] { text-align: ${align} !important; }`);
423
427
  }
@@ -448,6 +452,7 @@ var SECTION_ATTRS = {
448
452
  headlineScale: "data-ohw-style-headline",
449
453
  imageAspect: "data-ohw-style-aspect",
450
454
  spacing: "data-ohw-style-spacing",
455
+ cornerStyle: "data-ohw-style-corners",
451
456
  align: "data-ohw-style-align"
452
457
  };
453
458
  var NODE_WROTE_ATTR = "data-ohw-style-node";
@@ -840,6 +845,284 @@ function initSectionInstancesFromContent(content, currentPath) {
840
845
  // src/ui/ai-tree/AiTreeRenderer.tsx
841
846
  import React from "react";
842
847
  import { ArrowLeft, ArrowRight, ChevronDown, icons as lucideIcons } from "lucide-react";
848
+
849
+ // src/lib/placeholder-imagery.ts
850
+ var U = (id) => `https://images.unsplash.com/photo-${id}?auto=format&fit=crop&w=1600&q=80`;
851
+ var GENERIC = [
852
+ U("1441986300917-64674bd600d8"),
853
+ U("1486406146926-c627a92ad1ab"),
854
+ U("1497032628192-86f99bcd76bc"),
855
+ U("1521737604893-d14cc237f11d"),
856
+ U("1522071820081-009f0129c71c"),
857
+ U("1519389950473-47ba0277781c"),
858
+ U("1460925895917-afdab827c52f"),
859
+ U("1504384308090-c894fdcc538d")
860
+ ];
861
+ var PEOPLE = [
862
+ U("1500648767791-00dcc994a43e"),
863
+ U("1494790108377-be9c29b29330"),
864
+ U("1507003211169-0a1dd7228f2d"),
865
+ U("1438761681033-6461ffad8d80"),
866
+ U("1544005313-94ddf0286df2"),
867
+ U("1472099645785-5658abf4ff4e"),
868
+ U("1519085360753-af0119f7cbe7"),
869
+ U("1534528741775-53994a69daeb")
870
+ ];
871
+ var THEMED = [
872
+ {
873
+ keywords: ["portrait", "headshot", "person", "people", "team", "staff", "avatar", "founder", "face"],
874
+ pool: PEOPLE
875
+ },
876
+ {
877
+ keywords: ["pet", "dog", "cat", "puppy", "kitten", "vet", "animal"],
878
+ pool: [
879
+ U("1548199973-03cce0bbc87b"),
880
+ U("1450778869180-41d0601e046e"),
881
+ U("1583511655857-d19b40a7a54e"),
882
+ U("1587300003388-59208cc962cb"),
883
+ U("1517849845537-4d257902454a"),
884
+ U("1601758228041-f3b2795255f1")
885
+ ]
886
+ },
887
+ {
888
+ keywords: [
889
+ "baker",
890
+ "bakery",
891
+ "cafe",
892
+ "coffee",
893
+ "latte",
894
+ "restaurant",
895
+ "pastr",
896
+ "bread",
897
+ "cake",
898
+ "cater",
899
+ "chef",
900
+ "kitchen",
901
+ "food",
902
+ "pizza",
903
+ "dessert",
904
+ "brunch",
905
+ "bistro",
906
+ "deli",
907
+ "dish",
908
+ "menu"
909
+ ],
910
+ pool: [
911
+ U("1509440159596-0249088772ff"),
912
+ U("1555507036-ab1f4038808a"),
913
+ U("1517433670267-08bbd4be890f"),
914
+ U("1486427944299-d1955d23e34d"),
915
+ U("1504754524776-8f4f37790ca0"),
916
+ U("1495474472287-4d71bcdd2085"),
917
+ U("1521017432531-fbd92d768814"),
918
+ U("1556909114-f6e7ad7d3136")
919
+ ]
920
+ },
921
+ {
922
+ keywords: [
923
+ "shop",
924
+ "store",
925
+ "boutique",
926
+ "retail",
927
+ "clothing",
928
+ "fashion",
929
+ "jewel",
930
+ "gift",
931
+ "florist",
932
+ "market",
933
+ "grocer",
934
+ "product",
935
+ "storefront"
936
+ ],
937
+ pool: [
938
+ U("1441984904996-e0b6ba687e04"),
939
+ U("1472851294608-062f824d29cc"),
940
+ U("1523381210434-271e8be1f52b"),
941
+ U("1534452203293-494d7ddbf7e0"),
942
+ U("1445205170230-053b83016050"),
943
+ U("1560243563-062bfc001d68")
944
+ ]
945
+ },
946
+ {
947
+ keywords: [
948
+ "yoga",
949
+ "pilates",
950
+ "fitness",
951
+ "gym",
952
+ "workout",
953
+ "trainer",
954
+ "wellness",
955
+ "meditat",
956
+ "massage",
957
+ "therap",
958
+ "physio",
959
+ "chiro",
960
+ "nutrition",
961
+ "spa",
962
+ "studio"
963
+ ],
964
+ pool: [
965
+ U("1544367567-0f2fcb009e0b"),
966
+ U("1506126613408-eca07ce68773"),
967
+ U("1545205597-3d9d02c29597"),
968
+ U("1552196563-55cd4e45efb3"),
969
+ U("1518611012118-696072aa579a"),
970
+ U("1571019613454-1cb2f99b2d8b"),
971
+ U("1540555700478-4be289fbecef"),
972
+ U("1519824145371-296894a0daa9")
973
+ ]
974
+ },
975
+ {
976
+ keywords: [
977
+ "salon",
978
+ "hairdress",
979
+ "haircut",
980
+ "barber",
981
+ "manicure",
982
+ "pedicure",
983
+ "nails",
984
+ "beauty",
985
+ "makeup",
986
+ "cosmetic",
987
+ "eyelash",
988
+ "eyebrow",
989
+ "skincare",
990
+ "esthetic",
991
+ "waxing",
992
+ "hair"
993
+ ],
994
+ pool: [
995
+ U("1560066984-138dadb4c035"),
996
+ U("1522337660859-02fbefca4702"),
997
+ U("1562322140-8baeececf3df"),
998
+ U("1521590832167-7bcbfaa6381f"),
999
+ U("1487412947147-5cebf100ffc2"),
1000
+ U("1526045478516-99145907023c")
1001
+ ]
1002
+ },
1003
+ {
1004
+ keywords: [
1005
+ "cleaning",
1006
+ "plumb",
1007
+ "electric",
1008
+ "landscap",
1009
+ "contractor",
1010
+ "handyman",
1011
+ "renov",
1012
+ "hvac",
1013
+ "roofing",
1014
+ "painting",
1015
+ "carpentry",
1016
+ "flooring",
1017
+ "movers",
1018
+ "construction",
1019
+ "tools"
1020
+ ],
1021
+ pool: [
1022
+ U("1581578731548-c64695cc6952"),
1023
+ U("1504307651254-35680f356dfd"),
1024
+ U("1581092160562-40aa08e78837"),
1025
+ U("1621905251189-08b45d6a269e"),
1026
+ U("1558618666-fcd25c85cd64"),
1027
+ U("1585128792020-803d29415281")
1028
+ ]
1029
+ },
1030
+ {
1031
+ keywords: [
1032
+ "legal",
1033
+ "attorney",
1034
+ "lawyer",
1035
+ "account",
1036
+ "bookkeep",
1037
+ "consult",
1038
+ "coaching",
1039
+ "financ",
1040
+ "insurance",
1041
+ "realtor",
1042
+ "estate",
1043
+ "marketing",
1044
+ "agency",
1045
+ "office",
1046
+ "business",
1047
+ "desk"
1048
+ ],
1049
+ pool: [
1050
+ U("1497366216548-37526070297c"),
1051
+ U("1497366811353-6870744d04b2"),
1052
+ U("1454165804606-c3d57bc86b40"),
1053
+ U("1521791136064-7986c2920216"),
1054
+ U("1556761175-b413da4baf72"),
1055
+ U("1542744173-8e7e53415bb0")
1056
+ ]
1057
+ },
1058
+ {
1059
+ keywords: [
1060
+ "wedding",
1061
+ "event",
1062
+ "party",
1063
+ "celebrat",
1064
+ "venue",
1065
+ "community",
1066
+ "nonprofit",
1067
+ "charity",
1068
+ "workshop",
1069
+ "photograph",
1070
+ "concert"
1071
+ ],
1072
+ pool: [
1073
+ U("1511578314322-379afb476865"),
1074
+ U("1501281668745-f7f57925c3b4"),
1075
+ U("1523580494863-6f3031224c94"),
1076
+ U("1540575467063-178a50c2df87"),
1077
+ U("1505236858219-8359eb29e329"),
1078
+ U("1528605248644-14dd04022da1")
1079
+ ]
1080
+ }
1081
+ ];
1082
+ function poolForSubject(subject) {
1083
+ for (const theme of THEMED) {
1084
+ if (theme.keywords.some((k) => subject.includes(k))) {
1085
+ return theme.pool;
1086
+ }
1087
+ }
1088
+ return GENERIC;
1089
+ }
1090
+ function mixedHash(text) {
1091
+ let hash = 2166136261;
1092
+ for (let i = 0; i < text.length; i++) {
1093
+ hash ^= text.charCodeAt(i);
1094
+ hash = Math.imul(hash, 16777619);
1095
+ }
1096
+ return hash >>> 16 & 65535;
1097
+ }
1098
+ function resolvePlaceholderRef(ref) {
1099
+ const match = /^placeholder:([a-z0-9-]+)$/.exec(ref);
1100
+ if (!match) return null;
1101
+ const subject = match[1];
1102
+ const pool = poolForSubject(subject.replace(/-\d+$/, ""));
1103
+ return pool[mixedHash(ref) % pool.length];
1104
+ }
1105
+ function collectPlaceholderRefs(tree) {
1106
+ const seen = /* @__PURE__ */ new Set();
1107
+ for (const match of JSON.stringify(tree ?? null).matchAll(/"(placeholder:[a-z0-9-]+)"/gu)) {
1108
+ seen.add(match[1]);
1109
+ }
1110
+ return [...seen];
1111
+ }
1112
+ function buildPlaceholderMap(tree) {
1113
+ const map = {};
1114
+ const cursor = /* @__PURE__ */ new Map();
1115
+ for (const ref of collectPlaceholderRefs(tree)) {
1116
+ const subject = ref.slice("placeholder:".length).replace(/-\d+$/, "");
1117
+ const pool = poolForSubject(subject);
1118
+ const start = cursor.get(pool) ?? mixedHash(ref) % pool.length;
1119
+ map[ref] = pool[start % pool.length];
1120
+ cursor.set(pool, start + 1);
1121
+ }
1122
+ return map;
1123
+ }
1124
+
1125
+ // src/ui/ai-tree/AiTreeRenderer.tsx
843
1126
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
844
1127
  function lucideByName(name) {
845
1128
  const pascal = name.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
@@ -853,6 +1136,7 @@ var typeStyle = (spec, font) => ({
853
1136
  fontWeight: spec.weight
854
1137
  });
855
1138
  var str = (value) => typeof value === "string" ? value : "";
1139
+ var cardRadius = (slots) => slots.cornerStyle === "sharp" ? 0 : AI_TREE_TOKENS.radiusCard;
856
1140
  var featureLines = (value) => value.split(/\n|<br\s*\/?>/i).map((line) => line.trim()).filter(Boolean);
857
1141
  var CHECK_MASK = `data:image/svg+xml,${encodeURIComponent(
858
1142
  '<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>'
@@ -879,6 +1163,25 @@ var FEATURE_LINE_CSS = [
879
1163
  `background-color:currentColor;-webkit-mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px;`,
880
1164
  `mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px}`
881
1165
  ].join("");
1166
+ function buttonShellStyle(ctx, fullWidth) {
1167
+ const bs = ctx.buttonStyle;
1168
+ if (bs) {
1169
+ return {
1170
+ borderRadius: bs.radius,
1171
+ ...bs.padding ? { padding: bs.padding } : {},
1172
+ ...bs.fontFamily ? { fontFamily: bs.fontFamily } : { fontFamily: ctx.brand.fonts.body },
1173
+ ...bs.fontSize ? { fontSize: bs.fontSize } : {},
1174
+ ...bs.fontWeight ? { fontWeight: bs.fontWeight } : {},
1175
+ ...bs.letterSpacing && bs.letterSpacing !== "normal" ? { letterSpacing: bs.letterSpacing } : {},
1176
+ ...bs.textTransform && bs.textTransform !== "none" ? { textTransform: bs.textTransform } : {}
1177
+ };
1178
+ }
1179
+ return {
1180
+ borderRadius: AI_TREE_TOKENS.radiusButton,
1181
+ padding: fullWidth ? `${AI_TREE_TOKENS.spacing2}px ${AI_TREE_TOKENS.spacing4}px` : `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`,
1182
+ ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
1183
+ };
1184
+ }
882
1185
  function hexLuminance(color) {
883
1186
  const m = /^#([0-9a-f]{6})$/i.exec(color.trim());
884
1187
  if (!m) return null;
@@ -895,6 +1198,12 @@ function hexContrast(a, b) {
895
1198
  const [hi, lo] = la > lb ? [la, lb] : [lb, la];
896
1199
  return (hi + 0.05) / (lo + 0.05);
897
1200
  }
1201
+ function primaryButtonLabel(brand) {
1202
+ const darkC = hexContrast(brand.palette.primary, brand.palette.dark);
1203
+ const lightC = hexContrast(brand.palette.primary, AI_TREE_TOKENS.textPrimaryForeground);
1204
+ if (darkC === null || lightC === null) return AI_TREE_TOKENS.textPrimaryForeground;
1205
+ return darkC > lightC ? brand.palette.dark : AI_TREE_TOKENS.textPrimaryForeground;
1206
+ }
898
1207
  function accentBandContext(brand) {
899
1208
  const p = brand.palette;
900
1209
  const lightWins = (hexContrast(p.light, p.primary) ?? 99) >= (hexContrast(p.dark, p.primary) ?? 0);
@@ -994,12 +1303,10 @@ function ButtonEl({
994
1303
  width: fullWidth ? "100%" : void 0,
995
1304
  alignItems: "center",
996
1305
  justifyContent: "center",
997
- padding: fullWidth ? `${AI_TREE_TOKENS.spacing2}px ${AI_TREE_TOKENS.spacing4}px` : `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`,
998
- borderRadius: AI_TREE_TOKENS.radiusButton,
999
1306
  textDecoration: "none",
1000
1307
  cursor: "pointer",
1001
- ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body),
1002
- ...secondary ? { border: `1px solid ${ctx.brand.palette.dark}`, color: ctx.brand.palette.dark } : { background: ctx.brand.palette.primary, color: ctx.buttonLabel ?? AI_TREE_TOKENS.textPrimaryForeground }
1308
+ ...buttonShellStyle(ctx, fullWidth),
1309
+ ...secondary ? { border: `1px solid ${ctx.brand.palette.dark}`, color: ctx.brand.palette.dark } : { background: ctx.brand.palette.primary, color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand) }
1003
1310
  },
1004
1311
  children: /* @__PURE__ */ jsx("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
1005
1312
  }
@@ -1175,10 +1482,11 @@ function PricingCard({ node, ctx, path }) {
1175
1482
  return /* @__PURE__ */ jsxs(
1176
1483
  "div",
1177
1484
  {
1485
+ "data-ohw-card": "",
1178
1486
  style: {
1179
1487
  background: hasBg ? ctx.brand.palette.light : "transparent",
1180
1488
  border: `1px solid ${dark}`,
1181
- borderRadius: AI_TREE_TOKENS.radiusCard,
1489
+ borderRadius: cardRadius(slots),
1182
1490
  padding: AI_TREE_TOKENS.paddingBlock,
1183
1491
  display: "flex",
1184
1492
  flexDirection: "column",
@@ -1283,10 +1591,11 @@ function TestimonialCard({ node, ctx, path }) {
1283
1591
  return /* @__PURE__ */ jsx(
1284
1592
  "div",
1285
1593
  {
1594
+ "data-ohw-card": "",
1286
1595
  "data-ai-avatar-pos": avatarPos ?? void 0,
1287
1596
  style: {
1288
1597
  background: hasBg ? ctx.cardSurface : "transparent",
1289
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1598
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1290
1599
  overflow: "hidden",
1291
1600
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1292
1601
  minWidth: 0
@@ -1321,10 +1630,11 @@ function TeamCard({ node, ctx, path }) {
1321
1630
  return /* @__PURE__ */ jsxs(
1322
1631
  "div",
1323
1632
  {
1633
+ "data-ohw-card": "",
1324
1634
  "data-ai-avatar-pos": avatarPos ?? void 0,
1325
1635
  style: {
1326
1636
  background: hasBg ? ctx.cardSurface : "transparent",
1327
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1637
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1328
1638
  overflow: "hidden",
1329
1639
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1330
1640
  minWidth: 0,
@@ -1495,9 +1805,10 @@ function CardBlock({ node, ctx, path }) {
1495
1805
  return /* @__PURE__ */ jsxs(
1496
1806
  "div",
1497
1807
  {
1808
+ "data-ohw-card": "",
1498
1809
  style: {
1499
1810
  background: hasBg ? ctx.cardSurface : "transparent",
1500
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1811
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1501
1812
  overflow: "hidden",
1502
1813
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1503
1814
  display: horizontal ? "flex" : "block",
@@ -1525,7 +1836,7 @@ function CardBlock({ node, ctx, path }) {
1525
1836
  ) : /* @__PURE__ */ jsx(
1526
1837
  "div",
1527
1838
  {
1528
- 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" },
1839
+ 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" },
1529
1840
  children: media
1530
1841
  }
1531
1842
  )),
@@ -1936,6 +2247,32 @@ function renderNode(node, ctx, path) {
1936
2247
  if (child) {
1937
2248
  return renderNode(child, ctx, `${path}.c0`);
1938
2249
  }
2250
+ if (str(slots.provider) === "map" && str(slots.query)) {
2251
+ const query = str(slots.query);
2252
+ const mapAttrs = ctx.keyFor ? {
2253
+ "data-ohw-key": ctx.keyFor(`${path}.query`),
2254
+ "data-ohw-editable": "map",
2255
+ "data-ohw-map-query": query
2256
+ } : {};
2257
+ return /* @__PURE__ */ jsx(
2258
+ "iframe",
2259
+ {
2260
+ ...mapAttrs,
2261
+ "data-ai-embed": "map",
2262
+ title: str(slots.title) || "Map",
2263
+ src: `https://www.google.com/maps?q=${encodeURIComponent(query)}&output=embed`,
2264
+ loading: "lazy",
2265
+ referrerPolicy: "no-referrer-when-downgrade",
2266
+ style: {
2267
+ width: "100%",
2268
+ minHeight: 320,
2269
+ border: 0,
2270
+ borderRadius: AI_TREE_TOKENS.radiusCard,
2271
+ display: "block"
2272
+ }
2273
+ }
2274
+ );
2275
+ }
1939
2276
  return /* @__PURE__ */ jsx(
1940
2277
  "div",
1941
2278
  {
@@ -2039,15 +2376,12 @@ function renderNode(node, ctx, path) {
2039
2376
  alignSelf: submitAlign,
2040
2377
  border: "none",
2041
2378
  cursor: "pointer",
2042
- padding: "12px 24px",
2043
- // Corner radius follows the host template's own buttons (measured from a template
2044
- // CTA); 8px only when the page has no template button to match.
2045
- borderRadius: ctx.buttonRadius ?? 8,
2379
+ // Shape/padding/typography follow the host template's own buttons.
2380
+ ...buttonShellStyle(ctx),
2046
2381
  // Brand-styled: primary fill, brand-derived label colour (not a fixed token) so it
2047
2382
  // reads correctly on custom palettes.
2048
2383
  background: ctx.brand.palette.primary,
2049
- color: ctx.buttonLabel ?? ctx.brand.palette.light,
2050
- ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
2384
+ color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand)
2051
2385
  },
2052
2386
  children: /* @__PURE__ */ jsx("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
2053
2387
  },
@@ -2082,7 +2416,7 @@ function renderNode(node, ctx, path) {
2082
2416
  function AiTreeRenderer({
2083
2417
  tree,
2084
2418
  brand,
2085
- buttonRadius,
2419
+ buttonStyle,
2086
2420
  resolveMedia,
2087
2421
  editKeyPrefix
2088
2422
  }) {
@@ -2092,13 +2426,18 @@ function AiTreeRenderer({
2092
2426
  const resolvedBrand = brand ?? AI_DEFAULT_BRAND;
2093
2427
  const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
2094
2428
  const blockBrand = band?.brand ?? resolvedBrand;
2429
+ const placeholderMap = buildPlaceholderMap(tree);
2095
2430
  const ctx = {
2096
2431
  brand: blockBrand,
2097
- resolveMedia: resolveMedia ?? (() => null),
2432
+ // An owner/library ref resolves through the host resolver; a `placeholder:<subject>` ref the
2433
+ // host cannot resolve falls back to real stock photography (the per-section map first, then a
2434
+ // standalone resolve), so generated galleries, image rows, and overlay backgrounds arrive with
2435
+ // photos instead of grey boxes.
2436
+ resolveMedia: (ref) => resolveMedia?.(ref) ?? placeholderMap[ref] ?? resolvePlaceholderRef(ref),
2098
2437
  cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
2099
2438
  keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
2100
2439
  sectionAlignment: (tree.settings ?? {}).alignment === "center" ? "center" : "left",
2101
- buttonRadius,
2440
+ buttonStyle,
2102
2441
  ...band ? { buttonLabel: band.buttonLabel } : {}
2103
2442
  };
2104
2443
  const settings = tree.settings ?? {};
@@ -2228,13 +2567,13 @@ function deriveBrandOverride() {
2228
2567
  };
2229
2568
  }
2230
2569
  function deriveTemplateBrand() {
2231
- const dark = readRootVar("--color-dark");
2232
- const primary = readRootVar("--color-primary");
2233
- const light = readRootVar("--color-light");
2570
+ const primary = readRootVar("--brand-primary") || readRootVar("--color-primary");
2571
+ const dark = readRootVar("--brand-text") || readRootVar("--color-dark");
2572
+ const light = readRootVar("--brand-background") || readRootVar("--color-light");
2234
2573
  if (!dark || !primary || !light) return null;
2235
- const accent = readRootVar("--color-accent");
2236
- const heading = readRootVar("--font-heading") || readRootVar("--font-display");
2237
- const body = readRootVar("--font-body");
2574
+ const accent = readRootVar("--brand-accent") || readRootVar("--color-accent");
2575
+ const heading = readRootVar("--brand-font-heading") || readRootVar("--font-heading") || readRootVar("--font-display");
2576
+ const body = readRootVar("--brand-font-body") || readRootVar("--font-body");
2238
2577
  return {
2239
2578
  palette: { dark, primary, accent: accent || dark, light },
2240
2579
  fonts: {
@@ -2243,12 +2582,32 @@ function deriveTemplateBrand() {
2243
2582
  }
2244
2583
  };
2245
2584
  }
2246
- function deriveTemplateButtonRadius() {
2585
+ function deriveTemplateButtonStyle() {
2247
2586
  if (typeof document === "undefined") return null;
2248
- const btn = document.querySelector('[data-ohw-role="button"]');
2587
+ const btn = Array.from(document.querySelectorAll('[data-ohw-role="button"]')).find(
2588
+ (el) => !el.closest(`[${CONTAINER_ATTR}]`)
2589
+ );
2249
2590
  if (!btn) return null;
2250
- const radius = getComputedStyle(btn).borderTopLeftRadius;
2251
- return radius || null;
2591
+ const cs = getComputedStyle(btn);
2592
+ const corners = [
2593
+ cs.borderTopLeftRadius,
2594
+ cs.borderTopRightRadius,
2595
+ cs.borderBottomRightRadius,
2596
+ cs.borderBottomLeftRadius
2597
+ ].map((v) => v || "0px");
2598
+ const radius = corners.every((v) => v === corners[0]) ? corners[0] : corners.join(" ");
2599
+ const px = (v) => parseFloat(v) || 0;
2600
+ const padY = Math.max(px(cs.paddingTop), px(cs.paddingBottom));
2601
+ const padX = Math.max(px(cs.paddingLeft), px(cs.paddingRight));
2602
+ return {
2603
+ radius: radius || "10px",
2604
+ padding: `${padY}px ${padX}px`,
2605
+ fontFamily: cs.fontFamily || "",
2606
+ fontSize: cs.fontSize || "",
2607
+ fontWeight: cs.fontWeight || "",
2608
+ letterSpacing: cs.letterSpacing || "",
2609
+ textTransform: cs.textTransform || ""
2610
+ };
2252
2611
  }
2253
2612
  var mounted = /* @__PURE__ */ new Map();
2254
2613
  function findTemplateSection(id) {
@@ -2419,7 +2778,7 @@ function applyAiSectionsToDom(state, options) {
2419
2778
  if (typeof document === "undefined") return;
2420
2779
  const brandOverride = deriveBrandOverride();
2421
2780
  const templateBrand = deriveTemplateBrand();
2422
- const templateButtonRadius = deriveTemplateButtonRadius();
2781
+ const templateButtonStyle = deriveTemplateButtonStyle();
2423
2782
  const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
2424
2783
  const pagePath = window.location.pathname;
2425
2784
  const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
@@ -2459,7 +2818,7 @@ function applyAiSectionsToDom(state, options) {
2459
2818
  {
2460
2819
  tree: entry.tree,
2461
2820
  brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
2462
- buttonRadius: templateButtonRadius,
2821
+ buttonStyle: templateButtonStyle,
2463
2822
  resolveMedia,
2464
2823
  editKeyPrefix: `ai.${entry.id}`
2465
2824
  }
@@ -14558,6 +14917,9 @@ function collectEditableNodes(extraContent, root = document) {
14558
14917
  if (el.dataset.ohwEditable === "link") {
14559
14918
  return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref3(el) };
14560
14919
  }
14920
+ if (el.dataset.ohwEditable === "map") {
14921
+ return { key: el.dataset.ohwKey ?? "", type: "map", text: el.dataset.ohwMapQuery ?? "" };
14922
+ }
14561
14923
  return {
14562
14924
  key: el.dataset.ohwKey ?? "",
14563
14925
  type: el.dataset.ohwEditable ?? "text",
@@ -15341,7 +15703,7 @@ var EDITOR_CHROME_SELECTOR = '[data-ohw-toolbar], [data-ohw-form-toolbar], [data
15341
15703
  function isOverEditorChrome(x, y) {
15342
15704
  return document.elementsFromPoint(x, y).some((el) => el instanceof HTMLElement && el.matches(EDITOR_CHROME_SELECTOR));
15343
15705
  }
15344
- 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"])';
15706
+ 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"])';
15345
15707
  function getVideoEl2(el) {
15346
15708
  return el instanceof HTMLVideoElement ? el : el.querySelector("video");
15347
15709
  }
@@ -15397,6 +15759,12 @@ function applyVideoSettingNode(key, val) {
15397
15759
  });
15398
15760
  return true;
15399
15761
  }
15762
+ function applyMapQuery(el, val) {
15763
+ if (!(el instanceof HTMLIFrameElement)) return;
15764
+ const nextSrc = `https://www.google.com/maps?q=${encodeURIComponent(val)}&output=embed`;
15765
+ if (el.src !== nextSrc) el.src = nextSrc;
15766
+ el.setAttribute("data-ohw-map-query", val);
15767
+ }
15400
15768
  function applyLinkByKey(key, val) {
15401
15769
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
15402
15770
  if (el.dataset.ohwEditable === "link") applyLinkHref(el, val);
@@ -18197,6 +18565,8 @@ function OhhwellsBridge() {
18197
18565
  }
18198
18566
  } else if (el.dataset.ohwEditable === "link") {
18199
18567
  applyLinkHref(el, val);
18568
+ } else if (el.dataset.ohwEditable === "map") {
18569
+ applyMapQuery(el, val);
18200
18570
  } else if (el.dataset.ohwEditable === "icon") {
18201
18571
  applyIconMarkup(el, val);
18202
18572
  } else if (el.dataset.ohwEditable === "form") {
@@ -18393,6 +18763,8 @@ function OhhwellsBridge() {
18393
18763
  if (video && video.src !== val) applyVideoSrc(video, val);
18394
18764
  } else if (el.dataset.ohwEditable === "link") {
18395
18765
  applyLinkHref(el, val);
18766
+ } else if (el.dataset.ohwEditable === "map") {
18767
+ applyMapQuery(el, val);
18396
18768
  } else if (el.dataset.ohwEditable === "form") {
18397
18769
  } else if (isIconMarkupValue(val)) {
18398
18770
  } else if (el.innerHTML !== val) {
@@ -20366,6 +20738,8 @@ function OhhwellsBridge() {
20366
20738
  if (video && video.src !== val) applyVideoSrc(video, val);
20367
20739
  } else if (el.dataset.ohwEditable === "link") {
20368
20740
  applyLinkHref(el, val);
20741
+ } else if (el.dataset.ohwEditable === "map") {
20742
+ applyMapQuery(el, val);
20369
20743
  } else if (el.dataset.ohwEditable === "icon") {
20370
20744
  applyIconMarkup(el, val);
20371
20745
  } else if (isIconMarkupValue(val)) {
@@ -21602,7 +21976,7 @@ function OhhwellsBridge() {
21602
21976
  postToParent2({
21603
21977
  type: "ow:ready",
21604
21978
  version: "1",
21605
- bridgeVersion: "0.1.94",
21979
+ bridgeVersion: "0.1.96",
21606
21980
  path: pathname,
21607
21981
  nodes: collectEditableNodes(editContentRef.current),
21608
21982
  sections