@ohhwells/bridge 0.1.142 → 0.1.144

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
@@ -1428,6 +1428,52 @@ function assignmentFor(el, content, tokens) {
1428
1428
  function labelElement(el) {
1429
1429
  return el.querySelector('[data-ohw-editable="text"], [data-ohw-editable="plain"]') ?? (el.matches('[data-ohw-editable="text"], [data-ohw-editable="plain"]') ? el : null);
1430
1430
  }
1431
+ var BRAND_TRACK_PRECEDENCE = {
1432
+ background: ["primary", "accent", "background", "surface", "on-primary", "text", "border", "text-muted"],
1433
+ color: ["on-primary", "text", "primary", "accent", "text-muted", "background", "surface", "border"],
1434
+ borderColor: ["border", "primary", "accent", "text", "on-primary", "background", "surface", "text-muted"]
1435
+ };
1436
+ var FONT_TRACK_VARS = ["--font-body", "--font-heading", "--font-display", "--brand-font-body", "--brand-font-heading"];
1437
+ function readRootVar(doc, name) {
1438
+ const root = doc.documentElement;
1439
+ return root.style.getPropertyValue(name).trim() || getComputedStyle(root).getPropertyValue(name).trim();
1440
+ }
1441
+ function sameColor(a, b) {
1442
+ return Math.round(a.r) === Math.round(b.r) && Math.round(a.g) === Math.round(b.g) && Math.round(a.b) === Math.round(b.b) && Math.abs(a.a - b.a) < 0.02;
1443
+ }
1444
+ function brandTrackedColor(doc, color, roles) {
1445
+ if (!color || color.includes("var(")) return color;
1446
+ const parsed = parseColor(color);
1447
+ if (!parsed || isTransparent(parsed)) return color;
1448
+ for (const role of roles) {
1449
+ const name = `--brand-${role}`;
1450
+ const token = parseColor(readRootVar(doc, name));
1451
+ if (token && !isTransparent(token) && sameColor(parsed, token)) return `var(${name}, ${color})`;
1452
+ }
1453
+ return color;
1454
+ }
1455
+ function brandTrackedChrome(doc, chrome) {
1456
+ return {
1457
+ ...chrome,
1458
+ background: brandTrackedColor(doc, chrome.background, BRAND_TRACK_PRECEDENCE.background),
1459
+ color: brandTrackedColor(doc, chrome.color, BRAND_TRACK_PRECEDENCE.color),
1460
+ borderColor: brandTrackedColor(doc, chrome.borderColor, BRAND_TRACK_PRECEDENCE.borderColor)
1461
+ };
1462
+ }
1463
+ function firstFamily(stack) {
1464
+ const first = stack.split(",")[0]?.trim() ?? "";
1465
+ return first.replace(/^['"]|['"]$/g, "").toLowerCase();
1466
+ }
1467
+ function brandTrackedFontFamily(doc, stack) {
1468
+ if (!stack || stack.includes("var(")) return stack;
1469
+ const family = firstFamily(stack);
1470
+ if (!family) return stack;
1471
+ for (const name of FONT_TRACK_VARS) {
1472
+ const raw = readRootVar(doc, name);
1473
+ if (raw && firstFamily(raw) === family) return `var(${name}, ${stack})`;
1474
+ }
1475
+ return stack;
1476
+ }
1431
1477
  var paintLockByEl = /* @__PURE__ */ new WeakMap();
1432
1478
  var paintObserverByEl = /* @__PURE__ */ new WeakMap();
1433
1479
  var paintingButton = false;
@@ -1454,7 +1500,7 @@ function paintButtonChrome(el, assignment, tokens) {
1454
1500
  el.style.borderRadius = px(origin.size.borderRadius);
1455
1501
  el.style.gap = geometry.gap;
1456
1502
  el.style.height = px(geometry.height);
1457
- el.style.fontFamily = typography.fontFamily;
1503
+ el.style.fontFamily = brandTrackedFontFamily(el.ownerDocument, typography.fontFamily);
1458
1504
  el.style.fontWeight = typography.fontWeight;
1459
1505
  el.style.letterSpacing = typography.letterSpacing;
1460
1506
  el.style.textTransform = typography.textTransform;
@@ -1483,9 +1529,18 @@ function paintedTokensOf(el) {
1483
1529
  return paintLockByEl.get(el)?.tokens ?? null;
1484
1530
  }
1485
1531
  function applyButtonStyle(el, assignment, tokens) {
1486
- paintLockByEl.set(el, { assignment, tokens });
1532
+ const doc = el.ownerDocument;
1533
+ const tracked = {
1534
+ ...tokens,
1535
+ variants: {
1536
+ primary: brandTrackedChrome(doc, tokens.variants.primary),
1537
+ secondary: brandTrackedChrome(doc, tokens.variants.secondary),
1538
+ ghost: brandTrackedChrome(doc, tokens.variants.ghost)
1539
+ }
1540
+ };
1541
+ paintLockByEl.set(el, { assignment, tokens: tracked });
1487
1542
  paintingButton = true;
1488
- paintButtonChrome(el, assignment, tokens);
1543
+ paintButtonChrome(el, assignment, tracked);
1489
1544
  paintingButton = false;
1490
1545
  guardTemplateHover(el);
1491
1546
  }
@@ -4939,10 +4994,9 @@ function getLinkHref3(el) {
4939
4994
  }
4940
4995
  function collectEditableNodes(extraContent, root = document) {
4941
4996
  const isScoped = root !== document;
4942
- const editableEls = Array.from(
4943
- root.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="form"])')
4944
- );
4945
- if (isScoped && root instanceof HTMLElement && root.matches('[data-ohw-editable]:not([data-ohw-editable="form"])')) {
4997
+ const EDITABLE_SELECTOR = '[data-ohw-editable]:not([data-ohw-editable="form"]):not([data-ai-icon])';
4998
+ const editableEls = Array.from(root.querySelectorAll(EDITABLE_SELECTOR));
4999
+ if (isScoped && root instanceof HTMLElement && root.matches(EDITABLE_SELECTOR)) {
4946
5000
  editableEls.unshift(root);
4947
5001
  }
4948
5002
  const nodes = editableEls.map((el) => {
@@ -6209,6 +6263,281 @@ function reapRemovedAiSections(state, store, removedIds, excludeIds = /* @__PURE
6209
6263
  return { state: nextState, store: nextStore, reapedIds, slotPrefixes, changed: true };
6210
6264
  }
6211
6265
 
6266
+ // src/lib/ai-pricing-tree.ts
6267
+ var PRICING_MIN_CARDS = 1;
6268
+ var PRICING_MAX_CARDS = 4;
6269
+ function isPricingCard(node) {
6270
+ return typeof node === "object" && node !== null && node.type === "card" && node.slots?.preset === "pricing";
6271
+ }
6272
+ function isPricingCollection(node) {
6273
+ return node.type === "collection" && Array.isArray(node.children) && node.children.length > 0 && isPricingCard(node.children[0]);
6274
+ }
6275
+ function findCollection(node) {
6276
+ if (isPricingCollection(node)) return node;
6277
+ for (const child of node.children ?? []) {
6278
+ const found = findCollection(child);
6279
+ if (found) return found;
6280
+ }
6281
+ return null;
6282
+ }
6283
+ function findPricingCollection(tree) {
6284
+ for (const row of tree.rows ?? []) {
6285
+ for (const block of row.blocks ?? []) {
6286
+ const found = findCollection(block);
6287
+ if (found) return found;
6288
+ }
6289
+ }
6290
+ return null;
6291
+ }
6292
+ function pricingCardCount(tree) {
6293
+ return findPricingCollection(tree)?.children?.length ?? 0;
6294
+ }
6295
+ function withPricingCollection(tree, mutate) {
6296
+ const next = JSON.parse(JSON.stringify(tree));
6297
+ const collection = findPricingCollection(next);
6298
+ if (!collection || !collection.children) return null;
6299
+ for (const card of collection.children) {
6300
+ if (isPricingCard(card)) upgradeLegacyPricingSlots(card);
6301
+ }
6302
+ return mutate(collection) ? next : null;
6303
+ }
6304
+ function upgradeLegacyPricingSlots(card) {
6305
+ const slots = card.slots = card.slots ?? {};
6306
+ if (typeof slots.features === "string") {
6307
+ const rows = pricingFeatureList(card);
6308
+ if (rows.length > 0) slots.features = rows.slice(0, PRICING_FEATURES_MAX);
6309
+ }
6310
+ if (slots.featured === true) slots.featured = { enabled: true };
6311
+ else if (slots.featured === false) delete slots.featured;
6312
+ }
6313
+ function addPricingTier(tree) {
6314
+ return withPricingCollection(tree, (collection) => {
6315
+ const children = collection.children;
6316
+ if (children.length >= PRICING_MAX_CARDS) return false;
6317
+ const clone = JSON.parse(JSON.stringify(children[children.length - 1]));
6318
+ clone.slots = clone.slots ?? {};
6319
+ delete clone.slots.featured;
6320
+ clone.slots.heading = "New tier";
6321
+ children.push(clone);
6322
+ collection.slots = { ...collection.slots, itemsPerRow: children.length };
6323
+ return true;
6324
+ });
6325
+ }
6326
+ function removePricingTier(tree, index) {
6327
+ return withPricingCollection(tree, (collection) => {
6328
+ const children = collection.children;
6329
+ if (children.length <= PRICING_MIN_CARDS) return false;
6330
+ if (index < 0 || index >= children.length) return false;
6331
+ children.splice(index, 1);
6332
+ collection.slots = { ...collection.slots, itemsPerRow: children.length };
6333
+ return true;
6334
+ });
6335
+ }
6336
+ var PRICING_CARD_STYLES = [
6337
+ { value: "outline", label: "Outline", treatment: null },
6338
+ { value: "lift", label: "Lift", treatment: "lifted" },
6339
+ { value: "invert", label: "Invert", treatment: "inverted" },
6340
+ { value: "header", label: "Header", treatment: "accent-header" }
6341
+ ];
6342
+ var DEFAULT_FEATURED_BADGE = "Most popular";
6343
+ function featuredOf(target) {
6344
+ const raw = target?.slots?.featured;
6345
+ if (raw === true) return { enabled: true, treatment: "outlined" };
6346
+ return typeof raw === "object" && raw !== null ? raw : {};
6347
+ }
6348
+ function badgeOf(featured) {
6349
+ return typeof featured.badge === "string" ? featured.badge.trim() : "";
6350
+ }
6351
+ function pricingCardStyle(tree, index) {
6352
+ const featured = featuredOf(findPricingCollection(tree)?.children?.[index]);
6353
+ if (featured.enabled !== true) return "outline";
6354
+ return PRICING_CARD_STYLES.find((s) => s.treatment === featured.treatment)?.value ?? "outline";
6355
+ }
6356
+ function pricingCardFeaturedTag(tree, index) {
6357
+ const featured = featuredOf(findPricingCollection(tree)?.children?.[index]);
6358
+ return featured.enabled === true && badgeOf(featured).length > 0;
6359
+ }
6360
+ function composeFeatured(style, tagOn, badgeText) {
6361
+ const treatment = PRICING_CARD_STYLES.find((s) => s.value === style)?.treatment ?? null;
6362
+ if (tagOn) return { enabled: true, badge: badgeText || DEFAULT_FEATURED_BADGE, treatment: treatment ?? "outlined" };
6363
+ if (treatment) return { enabled: true, treatment };
6364
+ return badgeText.length > 0 ? { enabled: false, badge: badgeText } : { enabled: false };
6365
+ }
6366
+ function withPricingCard(tree, index, mutate) {
6367
+ return withPricingCollection(tree, (collection) => {
6368
+ const children = collection.children;
6369
+ if (index < 0 || index >= children.length) return false;
6370
+ const target = children[index];
6371
+ target.slots = target.slots ?? {};
6372
+ mutate(target);
6373
+ return true;
6374
+ });
6375
+ }
6376
+ function setPricingCardStyle(tree, index, style) {
6377
+ if (!PRICING_CARD_STYLES.some((s) => s.value === style)) return null;
6378
+ return withPricingCard(tree, index, (target) => {
6379
+ const prev = featuredOf(target);
6380
+ const tagOn = prev.enabled === true && badgeOf(prev).length > 0;
6381
+ target.slots.featured = composeFeatured(style, tagOn, badgeOf(prev));
6382
+ });
6383
+ }
6384
+ function setPricingCardFeaturedTag(tree, index, on) {
6385
+ return withPricingCollection(tree, (collection) => {
6386
+ const children = collection.children;
6387
+ if (index < 0 || index >= children.length) return false;
6388
+ if (on) {
6389
+ children.forEach((card, i) => {
6390
+ if (i === index) return;
6391
+ const prev2 = featuredOf(card);
6392
+ if (prev2.enabled !== true || badgeOf(prev2).length === 0) return;
6393
+ card.slots = card.slots ?? {};
6394
+ card.slots.featured = composeFeatured(pricingCardStyleOf(prev2), false, badgeOf(prev2));
6395
+ });
6396
+ }
6397
+ const target = children[index];
6398
+ target.slots = target.slots ?? {};
6399
+ const prev = featuredOf(target);
6400
+ target.slots.featured = composeFeatured(pricingCardStyleOf(prev), on, badgeOf(prev));
6401
+ return true;
6402
+ });
6403
+ }
6404
+ function pricingCardStyleOf(featured) {
6405
+ if (featured.enabled !== true) return "outline";
6406
+ return PRICING_CARD_STYLES.find((s) => s.treatment === featured.treatment)?.value ?? "outline";
6407
+ }
6408
+ function addPricingCardButton(tree, index, label) {
6409
+ return withPricingCollection(tree, (collection) => {
6410
+ const children = collection.children;
6411
+ if (index < 0 || index >= children.length) return false;
6412
+ const target = children[index];
6413
+ const buttons = (target.children ?? []).filter((c) => c.type === "button");
6414
+ if (buttons.length >= 2) return false;
6415
+ const hasPrimary = buttons.some((b) => b.slots?.variant === "primary");
6416
+ target.children = [
6417
+ ...target.children ?? [],
6418
+ { type: "button", span: 12, slots: { label, href: "#", variant: hasPrimary ? "secondary" : "primary" } }
6419
+ ];
6420
+ return true;
6421
+ });
6422
+ }
6423
+ function removePricingCardButton(tree, index) {
6424
+ return withPricingCollection(tree, (collection) => {
6425
+ const children = collection.children;
6426
+ if (index < 0 || index >= children.length) return false;
6427
+ const target = children[index];
6428
+ const buttons = (target.children ?? []).filter((c) => c.type === "button");
6429
+ if (buttons.length === 0) return false;
6430
+ const lastButton = buttons[buttons.length - 1];
6431
+ target.children = (target.children ?? []).filter((c) => c !== lastButton);
6432
+ return true;
6433
+ });
6434
+ }
6435
+ var PRICING_FEATURES_MIN = 1;
6436
+ var PRICING_FEATURES_MAX = 10;
6437
+ var PRICING_FEATURE_ICONS = [
6438
+ "check",
6439
+ "check-circle",
6440
+ "x",
6441
+ "star",
6442
+ "zap",
6443
+ "shield",
6444
+ "clock",
6445
+ "users",
6446
+ "infinity"
6447
+ ];
6448
+ function pricingFeatureList(target) {
6449
+ const raw = target.slots?.features;
6450
+ if (Array.isArray(raw)) {
6451
+ return raw.filter((item) => !!item && typeof item === "object" && typeof item.text === "string").map((item) => ({
6452
+ text: item.text,
6453
+ ...typeof item.icon === "string" ? { icon: item.icon } : {},
6454
+ ...typeof item.iconColor === "string" ? { iconColor: item.iconColor } : {}
6455
+ }));
6456
+ }
6457
+ if (typeof raw === "string") {
6458
+ return raw.split(/\n|<br\s*\/?>/i).map((line) => line.replace(/<[^>]+>/g, "").replace(/^[-*•]\s*/, "").trim().slice(0, 120)).filter(Boolean).map((text) => ({ text }));
6459
+ }
6460
+ return [];
6461
+ }
6462
+ function pricingCardFeatureCount(tree, index) {
6463
+ const target = findPricingCollection(tree)?.children?.[index];
6464
+ return target ? pricingFeatureList(target).length : 0;
6465
+ }
6466
+ function addPricingCardFeature(tree, index, text) {
6467
+ return withPricingCollection(tree, (collection) => {
6468
+ const children = collection.children;
6469
+ if (index < 0 || index >= children.length) return false;
6470
+ const target = children[index];
6471
+ const features = pricingFeatureList(target);
6472
+ if (features.length >= PRICING_FEATURES_MAX) return false;
6473
+ target.slots = target.slots ?? {};
6474
+ target.slots.features = [...features, { text }];
6475
+ return true;
6476
+ });
6477
+ }
6478
+ function setPricingCardFeatureIcon(tree, index, featureIndex, icon) {
6479
+ if (!PRICING_FEATURE_ICONS.includes(icon)) return null;
6480
+ return withPricingCollection(tree, (collection) => {
6481
+ const children = collection.children;
6482
+ if (index < 0 || index >= children.length) return false;
6483
+ const target = children[index];
6484
+ const features = pricingFeatureList(target);
6485
+ if (featureIndex < 0 || featureIndex >= features.length) return false;
6486
+ target.slots = target.slots ?? {};
6487
+ target.slots.features = features.map((row, i) => i === featureIndex ? { ...row, icon } : row);
6488
+ return true;
6489
+ });
6490
+ }
6491
+ function removePricingCardFeature(tree, index) {
6492
+ return withPricingCollection(tree, (collection) => {
6493
+ const children = collection.children;
6494
+ if (index < 0 || index >= children.length) return false;
6495
+ const target = children[index];
6496
+ const features = pricingFeatureList(target);
6497
+ if (features.length <= PRICING_FEATURES_MIN) return false;
6498
+ target.slots = target.slots ?? {};
6499
+ target.slots.features = features.slice(0, -1);
6500
+ return true;
6501
+ });
6502
+ }
6503
+
6504
+ // src/lib/ai-icon-edit.ts
6505
+ var FEATURE_ICON_PATH = /\.c(\d+)\.features\.(\d+)\.icon$/;
6506
+ var NODE_SEGMENT = /^[rbc]\d+$/;
6507
+ function nodeAtPath(tree, segments) {
6508
+ if (segments.length < 2 || segments.some((s) => !NODE_SEGMENT.test(s))) return null;
6509
+ if (!segments[0].startsWith("r") || !segments[1].startsWith("b")) return null;
6510
+ let node = tree.rows?.[Number(segments[0].slice(1))]?.blocks?.[Number(segments[1].slice(1))];
6511
+ for (const segment of segments.slice(2)) {
6512
+ if (!segment.startsWith("c")) return null;
6513
+ node = node?.children?.[Number(segment.slice(1))];
6514
+ }
6515
+ return node ?? null;
6516
+ }
6517
+ function applyIconNameToTree(tree, path, icon) {
6518
+ const feature = path.match(FEATURE_ICON_PATH);
6519
+ if (feature) {
6520
+ return setPricingCardFeatureIcon(tree, Number(feature[1]), Number(feature[2]), icon);
6521
+ }
6522
+ return rewriteIconSlot(tree, path, `lucide:${icon}`);
6523
+ }
6524
+ function applyIconUploadToTree(tree, path, mediaRef) {
6525
+ if (FEATURE_ICON_PATH.test(path)) return null;
6526
+ return rewriteIconSlot(tree, path, `icon:${mediaRef}`);
6527
+ }
6528
+ function rewriteIconSlot(tree, path, ref) {
6529
+ const segments = path.split(".");
6530
+ const slot = segments.pop();
6531
+ if (!slot || NODE_SEGMENT.test(slot)) return null;
6532
+ const next = JSON.parse(JSON.stringify(tree));
6533
+ const node = nodeAtPath(next, segments);
6534
+ if (!node) return null;
6535
+ const current = node.slots?.[slot];
6536
+ if (typeof current !== "string" || !/^(lucide|simple|icon):/.test(current)) return null;
6537
+ node.slots = { ...node.slots, [slot]: ref };
6538
+ return next;
6539
+ }
6540
+
6212
6541
  // src/lib/ai-footer-tree.ts
6213
6542
  var FOOTER_COLUMN_SPANS = [12, 6, 4, 3];
6214
6543
  var FOOTER_COLUMN_BLOCKS = [
@@ -6692,8 +7021,8 @@ function applyBrandToDom(kit) {
6692
7021
  if (kit.fonts) {
6693
7022
  appliedFonts = applyFonts ? kit.fonts : null;
6694
7023
  }
7024
+ for (const name of BUTTON_VAR_NAMES) root.style.removeProperty(name);
6695
7025
  if (kit.buttons) {
6696
- for (const name of BUTTON_VAR_NAMES) root.style.removeProperty(name);
6697
7026
  for (const [name, value] of Object.entries(buttonStyleVars({ ...kit, buttons: kit.buttons }))) root.style.setProperty(name, value);
6698
7027
  }
6699
7028
  syncButtonRules(kit.buttons ? kit : null);
@@ -7565,7 +7894,7 @@ function MediaBox({
7565
7894
  onError
7566
7895
  }) {
7567
7896
  const url = refValue ? ctx.resolveMedia(refValue) : null;
7568
- const isIcon = /^(lucide|simple):/.test(refValue);
7897
+ const isIcon = /^(lucide|simple|icon):/.test(refValue);
7569
7898
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
7570
7899
  const editAttrs = ctx.keyFor && editPath && !isIcon ? {
7571
7900
  "data-ohw-key": ctx.keyFor(editPath),
@@ -7573,10 +7902,13 @@ function MediaBox({
7573
7902
  } : {};
7574
7903
  if (isIcon) {
7575
7904
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
7905
+ const uploadedIconUrl = refValue.startsWith("icon:") ? ctx.resolveMedia(refValue.slice(5)) : null;
7906
+ const iconEditAttrs = ctx.keyFor && editPath && genericImageEdit ? { "data-ohw-key": ctx.keyFor(editPath), "data-ohw-editable": "icon" } : {};
7576
7907
  return /* @__PURE__ */ jsx(
7577
7908
  "span",
7578
7909
  {
7579
7910
  "data-ai-icon": refValue,
7911
+ ...iconEditAttrs,
7580
7912
  style: {
7581
7913
  display: "inline-flex",
7582
7914
  width: 48,
@@ -7585,7 +7917,7 @@ function MediaBox({
7585
7917
  justifyContent: "center",
7586
7918
  color: ctx.brand.palette.dark
7587
7919
  },
7588
- children: Icon ? /* @__PURE__ */ jsx(Icon, { size: 48, color: ctx.brand.palette.dark, strokeWidth: 1.5 }) : /* @__PURE__ */ jsx("span", { style: { fontSize: 24 }, children: "\u25C6" })
7920
+ children: uploadedIconUrl ? /* @__PURE__ */ jsx("img", { src: uploadedIconUrl, alt: "", style: { width: 48, height: 48, objectFit: "contain" } }) : Icon ? /* @__PURE__ */ jsx(Icon, { size: 48, color: ctx.brand.palette.dark, strokeWidth: 1.5 }) : /* @__PURE__ */ jsx("span", { style: { fontSize: 24 }, children: "\u25C6" })
7589
7921
  }
7590
7922
  );
7591
7923
  }
@@ -8073,8 +8405,20 @@ function PricingCard({ node, ctx, path }) {
8073
8405
  /* @__PURE__ */ jsx("div", { "data-ai-features": "", style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing4, padding: `${AI_TREE_TOKENS.spacing2}px 0` }, children: features.map((item, i) => {
8074
8406
  const Icon = lucideByName(item.icon || "check");
8075
8407
  const iconColor = pricingIconColor(item.iconColor, ctx.brand, fg);
8408
+ const iconAttrs = ctx.keyFor && !item.html ? {
8409
+ "data-ohw-key": ctx.keyFor(`${path}.features.${i}.icon`),
8410
+ "data-ohw-editable": "icon"
8411
+ } : {};
8076
8412
  return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "flex-start", gap: AI_TREE_TOKENS.spacing3 }, children: [
8077
- Icon && /* @__PURE__ */ jsx(Icon, { size: 20, color: iconColor }),
8413
+ Icon && /* @__PURE__ */ jsx(
8414
+ "span",
8415
+ {
8416
+ "data-ai-icon": `lucide:${item.icon || "check"}`,
8417
+ ...iconAttrs,
8418
+ style: { display: "inline-flex", flexShrink: 0 },
8419
+ children: /* @__PURE__ */ jsx(Icon, { size: 20, color: iconColor })
8420
+ }
8421
+ ),
8078
8422
  item.html ? /* @__PURE__ */ jsx(
8079
8423
  "span",
8080
8424
  {
@@ -10079,7 +10423,7 @@ function isChromeSection2(el) {
10079
10423
  if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) return true;
10080
10424
  return el.tagName === "HEADER" || el.tagName === "FOOTER";
10081
10425
  }
10082
- function readRootVar(name) {
10426
+ function readRootVar2(name) {
10083
10427
  if (typeof document === "undefined") return "";
10084
10428
  return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
10085
10429
  }
@@ -10119,14 +10463,14 @@ function humanizeFontFamily(name) {
10119
10463
  return name.replace(/^__/, "").replace(/_[0-9a-f]{6}$/i, "").replace(/_/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").trim().replace(/\s+/g, " ").replace(/(^|\s)([a-z])/g, (_, sep, c) => sep + c.toUpperCase());
10120
10464
  }
10121
10465
  function readFontVar(name) {
10122
- return humanizeFontFamily(primaryFontFamily(readRootVar(name)));
10466
+ return humanizeFontFamily(primaryFontFamily(readRootVar2(name)));
10123
10467
  }
10124
10468
  function deriveBrandOverride() {
10125
- const dark = readRootVar("--ohw-brand-dark");
10126
- const primary = readRootVar("--ohw-brand-primary");
10127
- const light = readRootVar("--ohw-brand-light");
10469
+ const dark = readRootVar2("--ohw-brand-dark");
10470
+ const primary = readRootVar2("--ohw-brand-primary");
10471
+ const light = readRootVar2("--ohw-brand-light");
10128
10472
  if (!dark || !primary || !light) return null;
10129
- const accent = readRootVar("--ohw-brand-accent");
10473
+ const accent = readRootVar2("--ohw-brand-accent");
10130
10474
  const heading = readFontVar("--font-heading") || readFontVar("--font-display");
10131
10475
  const body = readFontVar("--font-body");
10132
10476
  return {
@@ -10138,11 +10482,11 @@ function deriveBrandOverride() {
10138
10482
  };
10139
10483
  }
10140
10484
  function deriveTemplateBrandLive() {
10141
- const primary = readRootVar("--brand-primary") || readRootVar("--color-primary");
10142
- const dark = readRootVar("--brand-text") || readRootVar("--color-dark");
10143
- const light = readRootVar("--brand-background") || readRootVar("--color-light");
10485
+ const primary = readRootVar2("--brand-primary") || readRootVar2("--color-primary");
10486
+ const dark = readRootVar2("--brand-text") || readRootVar2("--color-dark");
10487
+ const light = readRootVar2("--brand-background") || readRootVar2("--color-light");
10144
10488
  if (!dark || !primary || !light) return null;
10145
- const accent = readRootVar("--brand-accent") || readRootVar("--color-accent");
10489
+ const accent = readRootVar2("--brand-accent") || readRootVar2("--color-accent");
10146
10490
  const heading = readFontVar("--brand-font-heading") || readFontVar("--font-heading") || readFontVar("--font-display");
10147
10491
  const body = readFontVar("--brand-font-body") || readFontVar("--font-body");
10148
10492
  return {
@@ -18317,222 +18661,6 @@ function AiMediaStripOverlay({
18317
18661
  // src/ui/ai-section/AiPricingCardOverlay.tsx
18318
18662
  import { useCallback as useCallback4, useEffect as useEffect7, useLayoutEffect as useLayoutEffect4, useRef as useRef6, useState as useState9 } from "react";
18319
18663
  import { Check as Check2, ChevronDown as ChevronDown3, Minus, Plus as Plus2, Star as Star2 } from "lucide-react";
18320
-
18321
- // src/lib/ai-pricing-tree.ts
18322
- var PRICING_MIN_CARDS = 1;
18323
- var PRICING_MAX_CARDS = 4;
18324
- function isPricingCard(node) {
18325
- return typeof node === "object" && node !== null && node.type === "card" && node.slots?.preset === "pricing";
18326
- }
18327
- function isPricingCollection(node) {
18328
- return node.type === "collection" && Array.isArray(node.children) && node.children.length > 0 && isPricingCard(node.children[0]);
18329
- }
18330
- function findCollection(node) {
18331
- if (isPricingCollection(node)) return node;
18332
- for (const child of node.children ?? []) {
18333
- const found = findCollection(child);
18334
- if (found) return found;
18335
- }
18336
- return null;
18337
- }
18338
- function findPricingCollection(tree) {
18339
- for (const row of tree.rows ?? []) {
18340
- for (const block of row.blocks ?? []) {
18341
- const found = findCollection(block);
18342
- if (found) return found;
18343
- }
18344
- }
18345
- return null;
18346
- }
18347
- function pricingCardCount(tree) {
18348
- return findPricingCollection(tree)?.children?.length ?? 0;
18349
- }
18350
- function withPricingCollection(tree, mutate) {
18351
- const next = JSON.parse(JSON.stringify(tree));
18352
- const collection = findPricingCollection(next);
18353
- if (!collection || !collection.children) return null;
18354
- for (const card of collection.children) {
18355
- if (isPricingCard(card)) upgradeLegacyPricingSlots(card);
18356
- }
18357
- return mutate(collection) ? next : null;
18358
- }
18359
- function upgradeLegacyPricingSlots(card) {
18360
- const slots = card.slots = card.slots ?? {};
18361
- if (typeof slots.features === "string") {
18362
- const rows = pricingFeatureList(card);
18363
- if (rows.length > 0) slots.features = rows.slice(0, PRICING_FEATURES_MAX);
18364
- }
18365
- if (slots.featured === true) slots.featured = { enabled: true };
18366
- else if (slots.featured === false) delete slots.featured;
18367
- }
18368
- function addPricingTier(tree) {
18369
- return withPricingCollection(tree, (collection) => {
18370
- const children = collection.children;
18371
- if (children.length >= PRICING_MAX_CARDS) return false;
18372
- const clone = JSON.parse(JSON.stringify(children[children.length - 1]));
18373
- clone.slots = clone.slots ?? {};
18374
- delete clone.slots.featured;
18375
- clone.slots.heading = "New tier";
18376
- children.push(clone);
18377
- collection.slots = { ...collection.slots, itemsPerRow: children.length };
18378
- return true;
18379
- });
18380
- }
18381
- function removePricingTier(tree, index) {
18382
- return withPricingCollection(tree, (collection) => {
18383
- const children = collection.children;
18384
- if (children.length <= PRICING_MIN_CARDS) return false;
18385
- if (index < 0 || index >= children.length) return false;
18386
- children.splice(index, 1);
18387
- collection.slots = { ...collection.slots, itemsPerRow: children.length };
18388
- return true;
18389
- });
18390
- }
18391
- var PRICING_CARD_STYLES = [
18392
- { value: "outline", label: "Outline", treatment: null },
18393
- { value: "lift", label: "Lift", treatment: "lifted" },
18394
- { value: "invert", label: "Invert", treatment: "inverted" },
18395
- { value: "header", label: "Header", treatment: "accent-header" }
18396
- ];
18397
- var DEFAULT_FEATURED_BADGE = "Most popular";
18398
- function featuredOf(target) {
18399
- const raw = target?.slots?.featured;
18400
- if (raw === true) return { enabled: true, treatment: "outlined" };
18401
- return typeof raw === "object" && raw !== null ? raw : {};
18402
- }
18403
- function badgeOf(featured) {
18404
- return typeof featured.badge === "string" ? featured.badge.trim() : "";
18405
- }
18406
- function pricingCardStyle(tree, index) {
18407
- const featured = featuredOf(findPricingCollection(tree)?.children?.[index]);
18408
- if (featured.enabled !== true) return "outline";
18409
- return PRICING_CARD_STYLES.find((s) => s.treatment === featured.treatment)?.value ?? "outline";
18410
- }
18411
- function pricingCardFeaturedTag(tree, index) {
18412
- const featured = featuredOf(findPricingCollection(tree)?.children?.[index]);
18413
- return featured.enabled === true && badgeOf(featured).length > 0;
18414
- }
18415
- function composeFeatured(style, tagOn, badgeText) {
18416
- const treatment = PRICING_CARD_STYLES.find((s) => s.value === style)?.treatment ?? null;
18417
- if (tagOn) return { enabled: true, badge: badgeText || DEFAULT_FEATURED_BADGE, treatment: treatment ?? "outlined" };
18418
- if (treatment) return { enabled: true, treatment };
18419
- return badgeText.length > 0 ? { enabled: false, badge: badgeText } : { enabled: false };
18420
- }
18421
- function withPricingCard(tree, index, mutate) {
18422
- return withPricingCollection(tree, (collection) => {
18423
- const children = collection.children;
18424
- if (index < 0 || index >= children.length) return false;
18425
- const target = children[index];
18426
- target.slots = target.slots ?? {};
18427
- mutate(target);
18428
- return true;
18429
- });
18430
- }
18431
- function setPricingCardStyle(tree, index, style) {
18432
- if (!PRICING_CARD_STYLES.some((s) => s.value === style)) return null;
18433
- return withPricingCard(tree, index, (target) => {
18434
- const prev = featuredOf(target);
18435
- const tagOn = prev.enabled === true && badgeOf(prev).length > 0;
18436
- target.slots.featured = composeFeatured(style, tagOn, badgeOf(prev));
18437
- });
18438
- }
18439
- function setPricingCardFeaturedTag(tree, index, on) {
18440
- return withPricingCollection(tree, (collection) => {
18441
- const children = collection.children;
18442
- if (index < 0 || index >= children.length) return false;
18443
- if (on) {
18444
- children.forEach((card, i) => {
18445
- if (i === index) return;
18446
- const prev2 = featuredOf(card);
18447
- if (prev2.enabled !== true || badgeOf(prev2).length === 0) return;
18448
- card.slots = card.slots ?? {};
18449
- card.slots.featured = composeFeatured(pricingCardStyleOf(prev2), false, badgeOf(prev2));
18450
- });
18451
- }
18452
- const target = children[index];
18453
- target.slots = target.slots ?? {};
18454
- const prev = featuredOf(target);
18455
- target.slots.featured = composeFeatured(pricingCardStyleOf(prev), on, badgeOf(prev));
18456
- return true;
18457
- });
18458
- }
18459
- function pricingCardStyleOf(featured) {
18460
- if (featured.enabled !== true) return "outline";
18461
- return PRICING_CARD_STYLES.find((s) => s.treatment === featured.treatment)?.value ?? "outline";
18462
- }
18463
- function addPricingCardButton(tree, index, label) {
18464
- return withPricingCollection(tree, (collection) => {
18465
- const children = collection.children;
18466
- if (index < 0 || index >= children.length) return false;
18467
- const target = children[index];
18468
- const buttons = (target.children ?? []).filter((c) => c.type === "button");
18469
- if (buttons.length >= 2) return false;
18470
- const hasPrimary = buttons.some((b) => b.slots?.variant === "primary");
18471
- target.children = [
18472
- ...target.children ?? [],
18473
- { type: "button", span: 12, slots: { label, href: "#", variant: hasPrimary ? "secondary" : "primary" } }
18474
- ];
18475
- return true;
18476
- });
18477
- }
18478
- function removePricingCardButton(tree, index) {
18479
- return withPricingCollection(tree, (collection) => {
18480
- const children = collection.children;
18481
- if (index < 0 || index >= children.length) return false;
18482
- const target = children[index];
18483
- const buttons = (target.children ?? []).filter((c) => c.type === "button");
18484
- if (buttons.length === 0) return false;
18485
- const lastButton = buttons[buttons.length - 1];
18486
- target.children = (target.children ?? []).filter((c) => c !== lastButton);
18487
- return true;
18488
- });
18489
- }
18490
- var PRICING_FEATURES_MIN = 1;
18491
- var PRICING_FEATURES_MAX = 10;
18492
- function pricingFeatureList(target) {
18493
- const raw = target.slots?.features;
18494
- if (Array.isArray(raw)) {
18495
- return raw.filter((item) => !!item && typeof item === "object" && typeof item.text === "string").map((item) => ({
18496
- text: item.text,
18497
- ...typeof item.icon === "string" ? { icon: item.icon } : {},
18498
- ...typeof item.iconColor === "string" ? { iconColor: item.iconColor } : {}
18499
- }));
18500
- }
18501
- if (typeof raw === "string") {
18502
- return raw.split(/\n|<br\s*\/?>/i).map((line) => line.replace(/<[^>]+>/g, "").replace(/^[-*•]\s*/, "").trim().slice(0, 120)).filter(Boolean).map((text) => ({ text }));
18503
- }
18504
- return [];
18505
- }
18506
- function pricingCardFeatureCount(tree, index) {
18507
- const target = findPricingCollection(tree)?.children?.[index];
18508
- return target ? pricingFeatureList(target).length : 0;
18509
- }
18510
- function addPricingCardFeature(tree, index, text) {
18511
- return withPricingCollection(tree, (collection) => {
18512
- const children = collection.children;
18513
- if (index < 0 || index >= children.length) return false;
18514
- const target = children[index];
18515
- const features = pricingFeatureList(target);
18516
- if (features.length >= PRICING_FEATURES_MAX) return false;
18517
- target.slots = target.slots ?? {};
18518
- target.slots.features = [...features, { text }];
18519
- return true;
18520
- });
18521
- }
18522
- function removePricingCardFeature(tree, index) {
18523
- return withPricingCollection(tree, (collection) => {
18524
- const children = collection.children;
18525
- if (index < 0 || index >= children.length) return false;
18526
- const target = children[index];
18527
- const features = pricingFeatureList(target);
18528
- if (features.length <= PRICING_FEATURES_MIN) return false;
18529
- target.slots = target.slots ?? {};
18530
- target.slots.features = features.slice(0, -1);
18531
- return true;
18532
- });
18533
- }
18534
-
18535
- // src/ui/ai-section/AiPricingCardOverlay.tsx
18536
18664
  import { Fragment as Fragment7, jsx as jsx20, jsxs as jsxs11 } from "react/jsx-runtime";
18537
18665
  var PRIMARY4 = "var(--ohw-primary, #0885FE)";
18538
18666
  var CHROME_SELECTOR = "[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-link-modal-root], [data-ohw-section-picker], [data-ohw-ai-review], [data-ohw-more-menu]";
@@ -23664,6 +23792,23 @@ function currentIconRef(el) {
23664
23792
  );
23665
23793
  return named ? `lucide:${named.slice("lucide-".length)}` : "";
23666
23794
  }
23795
+ function requestValidateTree5(postToParent2, tree) {
23796
+ return new Promise((resolve) => {
23797
+ const id = `ai-validate-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
23798
+ const timeout = window.setTimeout(() => {
23799
+ window.removeEventListener("message", onMessage);
23800
+ resolve({ ok: false, reasons: ["Validation request timed out."] });
23801
+ }, 8e3);
23802
+ function onMessage(e) {
23803
+ if (e.data?.type !== "ow:ai-validate-tree-result" || e.data.id !== id) return;
23804
+ window.clearTimeout(timeout);
23805
+ window.removeEventListener("message", onMessage);
23806
+ resolve({ ok: e.data.ok === true, reasons: Array.isArray(e.data.reasons) ? e.data.reasons : [] });
23807
+ }
23808
+ window.addEventListener("message", onMessage);
23809
+ postToParent2({ type: "ow:ai-validate-tree-request", id, tree });
23810
+ });
23811
+ }
23667
23812
  function getEditMeasureEl(editable) {
23668
23813
  return editable.closest("[data-ohw-href-key]") ?? editable;
23669
23814
  }
@@ -28015,10 +28160,14 @@ function OhhwellsBridge() {
28015
28160
  e.preventDefault();
28016
28161
  e.stopPropagation();
28017
28162
  aiSectionApiRef.current?.selectFromElement(editable);
28163
+ const aiIcon = editable.hasAttribute("data-ai-icon");
28164
+ const featureIcon = aiIcon && /\.features\.\d+\.icon$/.test(editable.dataset.ohwKey ?? "");
28018
28165
  postToParentRef.current({
28019
28166
  type: "ow:icon-pick",
28020
28167
  key: editable.dataset.ohwKey ?? "",
28021
- current: currentIconRef(editable)
28168
+ current: currentIconRef(editable),
28169
+ ...aiIcon ? { iconMode: "name" } : {},
28170
+ ...featureIcon ? { options: [...PRICING_FEATURE_ICONS] } : {}
28022
28171
  });
28023
28172
  return;
28024
28173
  }
@@ -29265,15 +29414,43 @@ function OhhwellsBridge() {
29265
29414
  if (e.data?.type !== "ow:icon-markup") return;
29266
29415
  const { key, markup } = e.data;
29267
29416
  if (!key || typeof markup !== "string") return;
29268
- const targets = document.querySelectorAll(
29269
- `[data-ohw-key="${key}"][data-ohw-editable="icon"]`
29270
- );
29417
+ const targets = Array.from(
29418
+ document.querySelectorAll(`[data-ohw-key="${key}"][data-ohw-editable="icon"]`)
29419
+ ).filter((el) => !el.hasAttribute("data-ai-icon"));
29271
29420
  if (!targets.length) return;
29272
29421
  targets.forEach((el) => {
29273
29422
  applyIconMarkup(el, markup);
29274
29423
  });
29275
29424
  postToParentRef.current({ type: "ow:change", nodes: [{ key, text: markup }], flush: true });
29276
29425
  };
29426
+ const handleIconName = (e) => {
29427
+ if (e.data?.type !== "ow:icon-name") return;
29428
+ const { key, icon, url } = e.data;
29429
+ const isName = typeof icon === "string" && /^[a-z0-9-]+$/.test(icon);
29430
+ const isUpload = !isName && typeof url === "string" && /^https?:\/\/\S+$/i.test(url);
29431
+ if (!key || !isName && !isUpload) return;
29432
+ const state = parseAiSectionsState(aiSectionsRef.current);
29433
+ const entry = state.sections.find((s) => key.startsWith(aiSlotKeyPrefixFor(s.id)));
29434
+ if (!entry) return;
29435
+ const path = key.slice(aiSlotKeyPrefixFor(entry.id).length);
29436
+ const uploadRef = isUpload ? `icon-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}` : null;
29437
+ const nextTree = uploadRef ? applyIconUploadToTree(entry.tree, path, uploadRef) : applyIconNameToTree(entry.tree, path, icon);
29438
+ if (!nextTree) return;
29439
+ void requestValidateTree5(postToParentRef.current, nextTree).then(({ ok }) => {
29440
+ if (!ok) return;
29441
+ applyTreeLocalRef.current?.({
29442
+ id: entry.id,
29443
+ mode: "replace",
29444
+ targetSectionId: entry.id,
29445
+ tree: nextTree,
29446
+ label: entry.label,
29447
+ media: uploadRef ? { ...entry.media, [uploadRef]: url } : entry.media,
29448
+ ...entry.brand ? { brand: entry.brand } : {},
29449
+ // The user is looking at the icon they just changed — no need to recenter on it.
29450
+ skipScrollIntoView: true
29451
+ });
29452
+ });
29453
+ };
29277
29454
  const handleImageUrl = (e) => {
29278
29455
  if (e.data?.type !== "ow:image-url") return;
29279
29456
  const { key, url } = e.data;
@@ -30722,6 +30899,7 @@ function OhhwellsBridge() {
30722
30899
  window.addEventListener("message", handleSocialCancel);
30723
30900
  window.addEventListener("message", handleSocialUpdate);
30724
30901
  window.addEventListener("message", handleIconMarkup);
30902
+ window.addEventListener("message", handleIconName);
30725
30903
  window.addEventListener("message", handleImageUrl);
30726
30904
  window.addEventListener("message", handleImageUploading);
30727
30905
  window.addEventListener("message", handleCarouselChange);
@@ -30806,6 +30984,7 @@ function OhhwellsBridge() {
30806
30984
  window.removeEventListener("message", handleSocialCancel);
30807
30985
  window.removeEventListener("message", handleSocialUpdate);
30808
30986
  window.removeEventListener("message", handleIconMarkup);
30987
+ window.removeEventListener("message", handleIconName);
30809
30988
  window.removeEventListener("message", handleImageUrl);
30810
30989
  window.removeEventListener("message", handleImageUploading);
30811
30990
  window.removeEventListener("message", handleCarouselChange);
@@ -31131,7 +31310,7 @@ function OhhwellsBridge() {
31131
31310
  postToParent2({
31132
31311
  type: "ow:ready",
31133
31312
  version: "1",
31134
- bridgeVersion: "0.1.141",
31313
+ bridgeVersion: "0.1.143",
31135
31314
  path: pathname,
31136
31315
  nodes: collectEditableNodes(editContentRef.current),
31137
31316
  sections