@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.cjs CHANGED
@@ -1522,6 +1522,52 @@ function assignmentFor(el, content, tokens) {
1522
1522
  function labelElement(el) {
1523
1523
  return el.querySelector('[data-ohw-editable="text"], [data-ohw-editable="plain"]') ?? (el.matches('[data-ohw-editable="text"], [data-ohw-editable="plain"]') ? el : null);
1524
1524
  }
1525
+ var BRAND_TRACK_PRECEDENCE = {
1526
+ background: ["primary", "accent", "background", "surface", "on-primary", "text", "border", "text-muted"],
1527
+ color: ["on-primary", "text", "primary", "accent", "text-muted", "background", "surface", "border"],
1528
+ borderColor: ["border", "primary", "accent", "text", "on-primary", "background", "surface", "text-muted"]
1529
+ };
1530
+ var FONT_TRACK_VARS = ["--font-body", "--font-heading", "--font-display", "--brand-font-body", "--brand-font-heading"];
1531
+ function readRootVar(doc, name) {
1532
+ const root = doc.documentElement;
1533
+ return root.style.getPropertyValue(name).trim() || getComputedStyle(root).getPropertyValue(name).trim();
1534
+ }
1535
+ function sameColor(a, b) {
1536
+ 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;
1537
+ }
1538
+ function brandTrackedColor(doc, color, roles) {
1539
+ if (!color || color.includes("var(")) return color;
1540
+ const parsed = parseColor(color);
1541
+ if (!parsed || isTransparent(parsed)) return color;
1542
+ for (const role of roles) {
1543
+ const name = `--brand-${role}`;
1544
+ const token = parseColor(readRootVar(doc, name));
1545
+ if (token && !isTransparent(token) && sameColor(parsed, token)) return `var(${name}, ${color})`;
1546
+ }
1547
+ return color;
1548
+ }
1549
+ function brandTrackedChrome(doc, chrome) {
1550
+ return {
1551
+ ...chrome,
1552
+ background: brandTrackedColor(doc, chrome.background, BRAND_TRACK_PRECEDENCE.background),
1553
+ color: brandTrackedColor(doc, chrome.color, BRAND_TRACK_PRECEDENCE.color),
1554
+ borderColor: brandTrackedColor(doc, chrome.borderColor, BRAND_TRACK_PRECEDENCE.borderColor)
1555
+ };
1556
+ }
1557
+ function firstFamily(stack) {
1558
+ const first = stack.split(",")[0]?.trim() ?? "";
1559
+ return first.replace(/^['"]|['"]$/g, "").toLowerCase();
1560
+ }
1561
+ function brandTrackedFontFamily(doc, stack) {
1562
+ if (!stack || stack.includes("var(")) return stack;
1563
+ const family = firstFamily(stack);
1564
+ if (!family) return stack;
1565
+ for (const name of FONT_TRACK_VARS) {
1566
+ const raw = readRootVar(doc, name);
1567
+ if (raw && firstFamily(raw) === family) return `var(${name}, ${stack})`;
1568
+ }
1569
+ return stack;
1570
+ }
1525
1571
  var paintLockByEl = /* @__PURE__ */ new WeakMap();
1526
1572
  var paintObserverByEl = /* @__PURE__ */ new WeakMap();
1527
1573
  var paintingButton = false;
@@ -1548,7 +1594,7 @@ function paintButtonChrome(el, assignment, tokens) {
1548
1594
  el.style.borderRadius = px(origin.size.borderRadius);
1549
1595
  el.style.gap = geometry.gap;
1550
1596
  el.style.height = px(geometry.height);
1551
- el.style.fontFamily = typography.fontFamily;
1597
+ el.style.fontFamily = brandTrackedFontFamily(el.ownerDocument, typography.fontFamily);
1552
1598
  el.style.fontWeight = typography.fontWeight;
1553
1599
  el.style.letterSpacing = typography.letterSpacing;
1554
1600
  el.style.textTransform = typography.textTransform;
@@ -1577,9 +1623,18 @@ function paintedTokensOf(el) {
1577
1623
  return paintLockByEl.get(el)?.tokens ?? null;
1578
1624
  }
1579
1625
  function applyButtonStyle(el, assignment, tokens) {
1580
- paintLockByEl.set(el, { assignment, tokens });
1626
+ const doc = el.ownerDocument;
1627
+ const tracked = {
1628
+ ...tokens,
1629
+ variants: {
1630
+ primary: brandTrackedChrome(doc, tokens.variants.primary),
1631
+ secondary: brandTrackedChrome(doc, tokens.variants.secondary),
1632
+ ghost: brandTrackedChrome(doc, tokens.variants.ghost)
1633
+ }
1634
+ };
1635
+ paintLockByEl.set(el, { assignment, tokens: tracked });
1581
1636
  paintingButton = true;
1582
- paintButtonChrome(el, assignment, tokens);
1637
+ paintButtonChrome(el, assignment, tracked);
1583
1638
  paintingButton = false;
1584
1639
  guardTemplateHover(el);
1585
1640
  }
@@ -5033,10 +5088,9 @@ function getLinkHref3(el) {
5033
5088
  }
5034
5089
  function collectEditableNodes(extraContent, root = document) {
5035
5090
  const isScoped = root !== document;
5036
- const editableEls = Array.from(
5037
- root.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="form"])')
5038
- );
5039
- if (isScoped && root instanceof HTMLElement && root.matches('[data-ohw-editable]:not([data-ohw-editable="form"])')) {
5091
+ const EDITABLE_SELECTOR = '[data-ohw-editable]:not([data-ohw-editable="form"]):not([data-ai-icon])';
5092
+ const editableEls = Array.from(root.querySelectorAll(EDITABLE_SELECTOR));
5093
+ if (isScoped && root instanceof HTMLElement && root.matches(EDITABLE_SELECTOR)) {
5040
5094
  editableEls.unshift(root);
5041
5095
  }
5042
5096
  const nodes = editableEls.map((el) => {
@@ -6303,6 +6357,281 @@ function reapRemovedAiSections(state, store, removedIds, excludeIds = /* @__PURE
6303
6357
  return { state: nextState, store: nextStore, reapedIds, slotPrefixes, changed: true };
6304
6358
  }
6305
6359
 
6360
+ // src/lib/ai-pricing-tree.ts
6361
+ var PRICING_MIN_CARDS = 1;
6362
+ var PRICING_MAX_CARDS = 4;
6363
+ function isPricingCard(node) {
6364
+ return typeof node === "object" && node !== null && node.type === "card" && node.slots?.preset === "pricing";
6365
+ }
6366
+ function isPricingCollection(node) {
6367
+ return node.type === "collection" && Array.isArray(node.children) && node.children.length > 0 && isPricingCard(node.children[0]);
6368
+ }
6369
+ function findCollection(node) {
6370
+ if (isPricingCollection(node)) return node;
6371
+ for (const child of node.children ?? []) {
6372
+ const found = findCollection(child);
6373
+ if (found) return found;
6374
+ }
6375
+ return null;
6376
+ }
6377
+ function findPricingCollection(tree) {
6378
+ for (const row of tree.rows ?? []) {
6379
+ for (const block of row.blocks ?? []) {
6380
+ const found = findCollection(block);
6381
+ if (found) return found;
6382
+ }
6383
+ }
6384
+ return null;
6385
+ }
6386
+ function pricingCardCount(tree) {
6387
+ return findPricingCollection(tree)?.children?.length ?? 0;
6388
+ }
6389
+ function withPricingCollection(tree, mutate) {
6390
+ const next = JSON.parse(JSON.stringify(tree));
6391
+ const collection = findPricingCollection(next);
6392
+ if (!collection || !collection.children) return null;
6393
+ for (const card of collection.children) {
6394
+ if (isPricingCard(card)) upgradeLegacyPricingSlots(card);
6395
+ }
6396
+ return mutate(collection) ? next : null;
6397
+ }
6398
+ function upgradeLegacyPricingSlots(card) {
6399
+ const slots = card.slots = card.slots ?? {};
6400
+ if (typeof slots.features === "string") {
6401
+ const rows = pricingFeatureList(card);
6402
+ if (rows.length > 0) slots.features = rows.slice(0, PRICING_FEATURES_MAX);
6403
+ }
6404
+ if (slots.featured === true) slots.featured = { enabled: true };
6405
+ else if (slots.featured === false) delete slots.featured;
6406
+ }
6407
+ function addPricingTier(tree) {
6408
+ return withPricingCollection(tree, (collection) => {
6409
+ const children = collection.children;
6410
+ if (children.length >= PRICING_MAX_CARDS) return false;
6411
+ const clone = JSON.parse(JSON.stringify(children[children.length - 1]));
6412
+ clone.slots = clone.slots ?? {};
6413
+ delete clone.slots.featured;
6414
+ clone.slots.heading = "New tier";
6415
+ children.push(clone);
6416
+ collection.slots = { ...collection.slots, itemsPerRow: children.length };
6417
+ return true;
6418
+ });
6419
+ }
6420
+ function removePricingTier(tree, index) {
6421
+ return withPricingCollection(tree, (collection) => {
6422
+ const children = collection.children;
6423
+ if (children.length <= PRICING_MIN_CARDS) return false;
6424
+ if (index < 0 || index >= children.length) return false;
6425
+ children.splice(index, 1);
6426
+ collection.slots = { ...collection.slots, itemsPerRow: children.length };
6427
+ return true;
6428
+ });
6429
+ }
6430
+ var PRICING_CARD_STYLES = [
6431
+ { value: "outline", label: "Outline", treatment: null },
6432
+ { value: "lift", label: "Lift", treatment: "lifted" },
6433
+ { value: "invert", label: "Invert", treatment: "inverted" },
6434
+ { value: "header", label: "Header", treatment: "accent-header" }
6435
+ ];
6436
+ var DEFAULT_FEATURED_BADGE = "Most popular";
6437
+ function featuredOf(target) {
6438
+ const raw = target?.slots?.featured;
6439
+ if (raw === true) return { enabled: true, treatment: "outlined" };
6440
+ return typeof raw === "object" && raw !== null ? raw : {};
6441
+ }
6442
+ function badgeOf(featured) {
6443
+ return typeof featured.badge === "string" ? featured.badge.trim() : "";
6444
+ }
6445
+ function pricingCardStyle(tree, index) {
6446
+ const featured = featuredOf(findPricingCollection(tree)?.children?.[index]);
6447
+ if (featured.enabled !== true) return "outline";
6448
+ return PRICING_CARD_STYLES.find((s) => s.treatment === featured.treatment)?.value ?? "outline";
6449
+ }
6450
+ function pricingCardFeaturedTag(tree, index) {
6451
+ const featured = featuredOf(findPricingCollection(tree)?.children?.[index]);
6452
+ return featured.enabled === true && badgeOf(featured).length > 0;
6453
+ }
6454
+ function composeFeatured(style, tagOn, badgeText) {
6455
+ const treatment = PRICING_CARD_STYLES.find((s) => s.value === style)?.treatment ?? null;
6456
+ if (tagOn) return { enabled: true, badge: badgeText || DEFAULT_FEATURED_BADGE, treatment: treatment ?? "outlined" };
6457
+ if (treatment) return { enabled: true, treatment };
6458
+ return badgeText.length > 0 ? { enabled: false, badge: badgeText } : { enabled: false };
6459
+ }
6460
+ function withPricingCard(tree, index, mutate) {
6461
+ return withPricingCollection(tree, (collection) => {
6462
+ const children = collection.children;
6463
+ if (index < 0 || index >= children.length) return false;
6464
+ const target = children[index];
6465
+ target.slots = target.slots ?? {};
6466
+ mutate(target);
6467
+ return true;
6468
+ });
6469
+ }
6470
+ function setPricingCardStyle(tree, index, style) {
6471
+ if (!PRICING_CARD_STYLES.some((s) => s.value === style)) return null;
6472
+ return withPricingCard(tree, index, (target) => {
6473
+ const prev = featuredOf(target);
6474
+ const tagOn = prev.enabled === true && badgeOf(prev).length > 0;
6475
+ target.slots.featured = composeFeatured(style, tagOn, badgeOf(prev));
6476
+ });
6477
+ }
6478
+ function setPricingCardFeaturedTag(tree, index, on) {
6479
+ return withPricingCollection(tree, (collection) => {
6480
+ const children = collection.children;
6481
+ if (index < 0 || index >= children.length) return false;
6482
+ if (on) {
6483
+ children.forEach((card, i) => {
6484
+ if (i === index) return;
6485
+ const prev2 = featuredOf(card);
6486
+ if (prev2.enabled !== true || badgeOf(prev2).length === 0) return;
6487
+ card.slots = card.slots ?? {};
6488
+ card.slots.featured = composeFeatured(pricingCardStyleOf(prev2), false, badgeOf(prev2));
6489
+ });
6490
+ }
6491
+ const target = children[index];
6492
+ target.slots = target.slots ?? {};
6493
+ const prev = featuredOf(target);
6494
+ target.slots.featured = composeFeatured(pricingCardStyleOf(prev), on, badgeOf(prev));
6495
+ return true;
6496
+ });
6497
+ }
6498
+ function pricingCardStyleOf(featured) {
6499
+ if (featured.enabled !== true) return "outline";
6500
+ return PRICING_CARD_STYLES.find((s) => s.treatment === featured.treatment)?.value ?? "outline";
6501
+ }
6502
+ function addPricingCardButton(tree, index, label) {
6503
+ return withPricingCollection(tree, (collection) => {
6504
+ const children = collection.children;
6505
+ if (index < 0 || index >= children.length) return false;
6506
+ const target = children[index];
6507
+ const buttons = (target.children ?? []).filter((c) => c.type === "button");
6508
+ if (buttons.length >= 2) return false;
6509
+ const hasPrimary = buttons.some((b) => b.slots?.variant === "primary");
6510
+ target.children = [
6511
+ ...target.children ?? [],
6512
+ { type: "button", span: 12, slots: { label, href: "#", variant: hasPrimary ? "secondary" : "primary" } }
6513
+ ];
6514
+ return true;
6515
+ });
6516
+ }
6517
+ function removePricingCardButton(tree, index) {
6518
+ return withPricingCollection(tree, (collection) => {
6519
+ const children = collection.children;
6520
+ if (index < 0 || index >= children.length) return false;
6521
+ const target = children[index];
6522
+ const buttons = (target.children ?? []).filter((c) => c.type === "button");
6523
+ if (buttons.length === 0) return false;
6524
+ const lastButton = buttons[buttons.length - 1];
6525
+ target.children = (target.children ?? []).filter((c) => c !== lastButton);
6526
+ return true;
6527
+ });
6528
+ }
6529
+ var PRICING_FEATURES_MIN = 1;
6530
+ var PRICING_FEATURES_MAX = 10;
6531
+ var PRICING_FEATURE_ICONS = [
6532
+ "check",
6533
+ "check-circle",
6534
+ "x",
6535
+ "star",
6536
+ "zap",
6537
+ "shield",
6538
+ "clock",
6539
+ "users",
6540
+ "infinity"
6541
+ ];
6542
+ function pricingFeatureList(target) {
6543
+ const raw = target.slots?.features;
6544
+ if (Array.isArray(raw)) {
6545
+ return raw.filter((item) => !!item && typeof item === "object" && typeof item.text === "string").map((item) => ({
6546
+ text: item.text,
6547
+ ...typeof item.icon === "string" ? { icon: item.icon } : {},
6548
+ ...typeof item.iconColor === "string" ? { iconColor: item.iconColor } : {}
6549
+ }));
6550
+ }
6551
+ if (typeof raw === "string") {
6552
+ return raw.split(/\n|<br\s*\/?>/i).map((line) => line.replace(/<[^>]+>/g, "").replace(/^[-*•]\s*/, "").trim().slice(0, 120)).filter(Boolean).map((text) => ({ text }));
6553
+ }
6554
+ return [];
6555
+ }
6556
+ function pricingCardFeatureCount(tree, index) {
6557
+ const target = findPricingCollection(tree)?.children?.[index];
6558
+ return target ? pricingFeatureList(target).length : 0;
6559
+ }
6560
+ function addPricingCardFeature(tree, index, text) {
6561
+ return withPricingCollection(tree, (collection) => {
6562
+ const children = collection.children;
6563
+ if (index < 0 || index >= children.length) return false;
6564
+ const target = children[index];
6565
+ const features = pricingFeatureList(target);
6566
+ if (features.length >= PRICING_FEATURES_MAX) return false;
6567
+ target.slots = target.slots ?? {};
6568
+ target.slots.features = [...features, { text }];
6569
+ return true;
6570
+ });
6571
+ }
6572
+ function setPricingCardFeatureIcon(tree, index, featureIndex, icon) {
6573
+ if (!PRICING_FEATURE_ICONS.includes(icon)) return null;
6574
+ return withPricingCollection(tree, (collection) => {
6575
+ const children = collection.children;
6576
+ if (index < 0 || index >= children.length) return false;
6577
+ const target = children[index];
6578
+ const features = pricingFeatureList(target);
6579
+ if (featureIndex < 0 || featureIndex >= features.length) return false;
6580
+ target.slots = target.slots ?? {};
6581
+ target.slots.features = features.map((row, i) => i === featureIndex ? { ...row, icon } : row);
6582
+ return true;
6583
+ });
6584
+ }
6585
+ function removePricingCardFeature(tree, index) {
6586
+ return withPricingCollection(tree, (collection) => {
6587
+ const children = collection.children;
6588
+ if (index < 0 || index >= children.length) return false;
6589
+ const target = children[index];
6590
+ const features = pricingFeatureList(target);
6591
+ if (features.length <= PRICING_FEATURES_MIN) return false;
6592
+ target.slots = target.slots ?? {};
6593
+ target.slots.features = features.slice(0, -1);
6594
+ return true;
6595
+ });
6596
+ }
6597
+
6598
+ // src/lib/ai-icon-edit.ts
6599
+ var FEATURE_ICON_PATH = /\.c(\d+)\.features\.(\d+)\.icon$/;
6600
+ var NODE_SEGMENT = /^[rbc]\d+$/;
6601
+ function nodeAtPath(tree, segments) {
6602
+ if (segments.length < 2 || segments.some((s) => !NODE_SEGMENT.test(s))) return null;
6603
+ if (!segments[0].startsWith("r") || !segments[1].startsWith("b")) return null;
6604
+ let node = tree.rows?.[Number(segments[0].slice(1))]?.blocks?.[Number(segments[1].slice(1))];
6605
+ for (const segment of segments.slice(2)) {
6606
+ if (!segment.startsWith("c")) return null;
6607
+ node = node?.children?.[Number(segment.slice(1))];
6608
+ }
6609
+ return node ?? null;
6610
+ }
6611
+ function applyIconNameToTree(tree, path, icon) {
6612
+ const feature = path.match(FEATURE_ICON_PATH);
6613
+ if (feature) {
6614
+ return setPricingCardFeatureIcon(tree, Number(feature[1]), Number(feature[2]), icon);
6615
+ }
6616
+ return rewriteIconSlot(tree, path, `lucide:${icon}`);
6617
+ }
6618
+ function applyIconUploadToTree(tree, path, mediaRef) {
6619
+ if (FEATURE_ICON_PATH.test(path)) return null;
6620
+ return rewriteIconSlot(tree, path, `icon:${mediaRef}`);
6621
+ }
6622
+ function rewriteIconSlot(tree, path, ref) {
6623
+ const segments = path.split(".");
6624
+ const slot = segments.pop();
6625
+ if (!slot || NODE_SEGMENT.test(slot)) return null;
6626
+ const next = JSON.parse(JSON.stringify(tree));
6627
+ const node = nodeAtPath(next, segments);
6628
+ if (!node) return null;
6629
+ const current = node.slots?.[slot];
6630
+ if (typeof current !== "string" || !/^(lucide|simple|icon):/.test(current)) return null;
6631
+ node.slots = { ...node.slots, [slot]: ref };
6632
+ return next;
6633
+ }
6634
+
6306
6635
  // src/lib/ai-footer-tree.ts
6307
6636
  var FOOTER_COLUMN_SPANS = [12, 6, 4, 3];
6308
6637
  var FOOTER_COLUMN_BLOCKS = [
@@ -6786,8 +7115,8 @@ function applyBrandToDom(kit) {
6786
7115
  if (kit.fonts) {
6787
7116
  appliedFonts = applyFonts ? kit.fonts : null;
6788
7117
  }
7118
+ for (const name of BUTTON_VAR_NAMES) root.style.removeProperty(name);
6789
7119
  if (kit.buttons) {
6790
- for (const name of BUTTON_VAR_NAMES) root.style.removeProperty(name);
6791
7120
  for (const [name, value] of Object.entries(buttonStyleVars({ ...kit, buttons: kit.buttons }))) root.style.setProperty(name, value);
6792
7121
  }
6793
7122
  syncButtonRules(kit.buttons ? kit : null);
@@ -7659,7 +7988,7 @@ function MediaBox({
7659
7988
  onError
7660
7989
  }) {
7661
7990
  const url = refValue ? ctx.resolveMedia(refValue) : null;
7662
- const isIcon = /^(lucide|simple):/.test(refValue);
7991
+ const isIcon = /^(lucide|simple|icon):/.test(refValue);
7663
7992
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
7664
7993
  const editAttrs = ctx.keyFor && editPath && !isIcon ? {
7665
7994
  "data-ohw-key": ctx.keyFor(editPath),
@@ -7667,10 +7996,13 @@ function MediaBox({
7667
7996
  } : {};
7668
7997
  if (isIcon) {
7669
7998
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
7999
+ const uploadedIconUrl = refValue.startsWith("icon:") ? ctx.resolveMedia(refValue.slice(5)) : null;
8000
+ const iconEditAttrs = ctx.keyFor && editPath && genericImageEdit ? { "data-ohw-key": ctx.keyFor(editPath), "data-ohw-editable": "icon" } : {};
7670
8001
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
7671
8002
  "span",
7672
8003
  {
7673
8004
  "data-ai-icon": refValue,
8005
+ ...iconEditAttrs,
7674
8006
  style: {
7675
8007
  display: "inline-flex",
7676
8008
  width: 48,
@@ -7679,7 +8011,7 @@ function MediaBox({
7679
8011
  justifyContent: "center",
7680
8012
  color: ctx.brand.palette.dark
7681
8013
  },
7682
- children: Icon ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { size: 48, color: ctx.brand.palette.dark, strokeWidth: 1.5 }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 24 }, children: "\u25C6" })
8014
+ children: uploadedIconUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: uploadedIconUrl, alt: "", style: { width: 48, height: 48, objectFit: "contain" } }) : Icon ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { size: 48, color: ctx.brand.palette.dark, strokeWidth: 1.5 }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 24 }, children: "\u25C6" })
7683
8015
  }
7684
8016
  );
7685
8017
  }
@@ -8167,8 +8499,20 @@ function PricingCard({ node, ctx, path }) {
8167
8499
  /* @__PURE__ */ (0, import_jsx_runtime.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) => {
8168
8500
  const Icon = lucideByName(item.icon || "check");
8169
8501
  const iconColor = pricingIconColor(item.iconColor, ctx.brand, fg);
8502
+ const iconAttrs = ctx.keyFor && !item.html ? {
8503
+ "data-ohw-key": ctx.keyFor(`${path}.features.${i}.icon`),
8504
+ "data-ohw-editable": "icon"
8505
+ } : {};
8170
8506
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "flex-start", gap: AI_TREE_TOKENS.spacing3 }, children: [
8171
- Icon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { size: 20, color: iconColor }),
8507
+ Icon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
8508
+ "span",
8509
+ {
8510
+ "data-ai-icon": `lucide:${item.icon || "check"}`,
8511
+ ...iconAttrs,
8512
+ style: { display: "inline-flex", flexShrink: 0 },
8513
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { size: 20, color: iconColor })
8514
+ }
8515
+ ),
8172
8516
  item.html ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
8173
8517
  "span",
8174
8518
  {
@@ -10173,7 +10517,7 @@ function isChromeSection2(el) {
10173
10517
  if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) return true;
10174
10518
  return el.tagName === "HEADER" || el.tagName === "FOOTER";
10175
10519
  }
10176
- function readRootVar(name) {
10520
+ function readRootVar2(name) {
10177
10521
  if (typeof document === "undefined") return "";
10178
10522
  return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
10179
10523
  }
@@ -10213,14 +10557,14 @@ function humanizeFontFamily(name) {
10213
10557
  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());
10214
10558
  }
10215
10559
  function readFontVar(name) {
10216
- return humanizeFontFamily(primaryFontFamily(readRootVar(name)));
10560
+ return humanizeFontFamily(primaryFontFamily(readRootVar2(name)));
10217
10561
  }
10218
10562
  function deriveBrandOverride() {
10219
- const dark = readRootVar("--ohw-brand-dark");
10220
- const primary = readRootVar("--ohw-brand-primary");
10221
- const light = readRootVar("--ohw-brand-light");
10563
+ const dark = readRootVar2("--ohw-brand-dark");
10564
+ const primary = readRootVar2("--ohw-brand-primary");
10565
+ const light = readRootVar2("--ohw-brand-light");
10222
10566
  if (!dark || !primary || !light) return null;
10223
- const accent = readRootVar("--ohw-brand-accent");
10567
+ const accent = readRootVar2("--ohw-brand-accent");
10224
10568
  const heading = readFontVar("--font-heading") || readFontVar("--font-display");
10225
10569
  const body = readFontVar("--font-body");
10226
10570
  return {
@@ -10232,11 +10576,11 @@ function deriveBrandOverride() {
10232
10576
  };
10233
10577
  }
10234
10578
  function deriveTemplateBrandLive() {
10235
- const primary = readRootVar("--brand-primary") || readRootVar("--color-primary");
10236
- const dark = readRootVar("--brand-text") || readRootVar("--color-dark");
10237
- const light = readRootVar("--brand-background") || readRootVar("--color-light");
10579
+ const primary = readRootVar2("--brand-primary") || readRootVar2("--color-primary");
10580
+ const dark = readRootVar2("--brand-text") || readRootVar2("--color-dark");
10581
+ const light = readRootVar2("--brand-background") || readRootVar2("--color-light");
10238
10582
  if (!dark || !primary || !light) return null;
10239
- const accent = readRootVar("--brand-accent") || readRootVar("--color-accent");
10583
+ const accent = readRootVar2("--brand-accent") || readRootVar2("--color-accent");
10240
10584
  const heading = readFontVar("--brand-font-heading") || readFontVar("--font-heading") || readFontVar("--font-display");
10241
10585
  const body = readFontVar("--brand-font-body") || readFontVar("--font-body");
10242
10586
  return {
@@ -18411,222 +18755,6 @@ function AiMediaStripOverlay({
18411
18755
  // src/ui/ai-section/AiPricingCardOverlay.tsx
18412
18756
  var import_react11 = require("react");
18413
18757
  var import_lucide_react9 = require("lucide-react");
18414
-
18415
- // src/lib/ai-pricing-tree.ts
18416
- var PRICING_MIN_CARDS = 1;
18417
- var PRICING_MAX_CARDS = 4;
18418
- function isPricingCard(node) {
18419
- return typeof node === "object" && node !== null && node.type === "card" && node.slots?.preset === "pricing";
18420
- }
18421
- function isPricingCollection(node) {
18422
- return node.type === "collection" && Array.isArray(node.children) && node.children.length > 0 && isPricingCard(node.children[0]);
18423
- }
18424
- function findCollection(node) {
18425
- if (isPricingCollection(node)) return node;
18426
- for (const child of node.children ?? []) {
18427
- const found = findCollection(child);
18428
- if (found) return found;
18429
- }
18430
- return null;
18431
- }
18432
- function findPricingCollection(tree) {
18433
- for (const row of tree.rows ?? []) {
18434
- for (const block of row.blocks ?? []) {
18435
- const found = findCollection(block);
18436
- if (found) return found;
18437
- }
18438
- }
18439
- return null;
18440
- }
18441
- function pricingCardCount(tree) {
18442
- return findPricingCollection(tree)?.children?.length ?? 0;
18443
- }
18444
- function withPricingCollection(tree, mutate) {
18445
- const next = JSON.parse(JSON.stringify(tree));
18446
- const collection = findPricingCollection(next);
18447
- if (!collection || !collection.children) return null;
18448
- for (const card of collection.children) {
18449
- if (isPricingCard(card)) upgradeLegacyPricingSlots(card);
18450
- }
18451
- return mutate(collection) ? next : null;
18452
- }
18453
- function upgradeLegacyPricingSlots(card) {
18454
- const slots = card.slots = card.slots ?? {};
18455
- if (typeof slots.features === "string") {
18456
- const rows = pricingFeatureList(card);
18457
- if (rows.length > 0) slots.features = rows.slice(0, PRICING_FEATURES_MAX);
18458
- }
18459
- if (slots.featured === true) slots.featured = { enabled: true };
18460
- else if (slots.featured === false) delete slots.featured;
18461
- }
18462
- function addPricingTier(tree) {
18463
- return withPricingCollection(tree, (collection) => {
18464
- const children = collection.children;
18465
- if (children.length >= PRICING_MAX_CARDS) return false;
18466
- const clone = JSON.parse(JSON.stringify(children[children.length - 1]));
18467
- clone.slots = clone.slots ?? {};
18468
- delete clone.slots.featured;
18469
- clone.slots.heading = "New tier";
18470
- children.push(clone);
18471
- collection.slots = { ...collection.slots, itemsPerRow: children.length };
18472
- return true;
18473
- });
18474
- }
18475
- function removePricingTier(tree, index) {
18476
- return withPricingCollection(tree, (collection) => {
18477
- const children = collection.children;
18478
- if (children.length <= PRICING_MIN_CARDS) return false;
18479
- if (index < 0 || index >= children.length) return false;
18480
- children.splice(index, 1);
18481
- collection.slots = { ...collection.slots, itemsPerRow: children.length };
18482
- return true;
18483
- });
18484
- }
18485
- var PRICING_CARD_STYLES = [
18486
- { value: "outline", label: "Outline", treatment: null },
18487
- { value: "lift", label: "Lift", treatment: "lifted" },
18488
- { value: "invert", label: "Invert", treatment: "inverted" },
18489
- { value: "header", label: "Header", treatment: "accent-header" }
18490
- ];
18491
- var DEFAULT_FEATURED_BADGE = "Most popular";
18492
- function featuredOf(target) {
18493
- const raw = target?.slots?.featured;
18494
- if (raw === true) return { enabled: true, treatment: "outlined" };
18495
- return typeof raw === "object" && raw !== null ? raw : {};
18496
- }
18497
- function badgeOf(featured) {
18498
- return typeof featured.badge === "string" ? featured.badge.trim() : "";
18499
- }
18500
- function pricingCardStyle(tree, index) {
18501
- const featured = featuredOf(findPricingCollection(tree)?.children?.[index]);
18502
- if (featured.enabled !== true) return "outline";
18503
- return PRICING_CARD_STYLES.find((s) => s.treatment === featured.treatment)?.value ?? "outline";
18504
- }
18505
- function pricingCardFeaturedTag(tree, index) {
18506
- const featured = featuredOf(findPricingCollection(tree)?.children?.[index]);
18507
- return featured.enabled === true && badgeOf(featured).length > 0;
18508
- }
18509
- function composeFeatured(style, tagOn, badgeText) {
18510
- const treatment = PRICING_CARD_STYLES.find((s) => s.value === style)?.treatment ?? null;
18511
- if (tagOn) return { enabled: true, badge: badgeText || DEFAULT_FEATURED_BADGE, treatment: treatment ?? "outlined" };
18512
- if (treatment) return { enabled: true, treatment };
18513
- return badgeText.length > 0 ? { enabled: false, badge: badgeText } : { enabled: false };
18514
- }
18515
- function withPricingCard(tree, index, mutate) {
18516
- return withPricingCollection(tree, (collection) => {
18517
- const children = collection.children;
18518
- if (index < 0 || index >= children.length) return false;
18519
- const target = children[index];
18520
- target.slots = target.slots ?? {};
18521
- mutate(target);
18522
- return true;
18523
- });
18524
- }
18525
- function setPricingCardStyle(tree, index, style) {
18526
- if (!PRICING_CARD_STYLES.some((s) => s.value === style)) return null;
18527
- return withPricingCard(tree, index, (target) => {
18528
- const prev = featuredOf(target);
18529
- const tagOn = prev.enabled === true && badgeOf(prev).length > 0;
18530
- target.slots.featured = composeFeatured(style, tagOn, badgeOf(prev));
18531
- });
18532
- }
18533
- function setPricingCardFeaturedTag(tree, index, on) {
18534
- return withPricingCollection(tree, (collection) => {
18535
- const children = collection.children;
18536
- if (index < 0 || index >= children.length) return false;
18537
- if (on) {
18538
- children.forEach((card, i) => {
18539
- if (i === index) return;
18540
- const prev2 = featuredOf(card);
18541
- if (prev2.enabled !== true || badgeOf(prev2).length === 0) return;
18542
- card.slots = card.slots ?? {};
18543
- card.slots.featured = composeFeatured(pricingCardStyleOf(prev2), false, badgeOf(prev2));
18544
- });
18545
- }
18546
- const target = children[index];
18547
- target.slots = target.slots ?? {};
18548
- const prev = featuredOf(target);
18549
- target.slots.featured = composeFeatured(pricingCardStyleOf(prev), on, badgeOf(prev));
18550
- return true;
18551
- });
18552
- }
18553
- function pricingCardStyleOf(featured) {
18554
- if (featured.enabled !== true) return "outline";
18555
- return PRICING_CARD_STYLES.find((s) => s.treatment === featured.treatment)?.value ?? "outline";
18556
- }
18557
- function addPricingCardButton(tree, index, label) {
18558
- return withPricingCollection(tree, (collection) => {
18559
- const children = collection.children;
18560
- if (index < 0 || index >= children.length) return false;
18561
- const target = children[index];
18562
- const buttons = (target.children ?? []).filter((c) => c.type === "button");
18563
- if (buttons.length >= 2) return false;
18564
- const hasPrimary = buttons.some((b) => b.slots?.variant === "primary");
18565
- target.children = [
18566
- ...target.children ?? [],
18567
- { type: "button", span: 12, slots: { label, href: "#", variant: hasPrimary ? "secondary" : "primary" } }
18568
- ];
18569
- return true;
18570
- });
18571
- }
18572
- function removePricingCardButton(tree, index) {
18573
- return withPricingCollection(tree, (collection) => {
18574
- const children = collection.children;
18575
- if (index < 0 || index >= children.length) return false;
18576
- const target = children[index];
18577
- const buttons = (target.children ?? []).filter((c) => c.type === "button");
18578
- if (buttons.length === 0) return false;
18579
- const lastButton = buttons[buttons.length - 1];
18580
- target.children = (target.children ?? []).filter((c) => c !== lastButton);
18581
- return true;
18582
- });
18583
- }
18584
- var PRICING_FEATURES_MIN = 1;
18585
- var PRICING_FEATURES_MAX = 10;
18586
- function pricingFeatureList(target) {
18587
- const raw = target.slots?.features;
18588
- if (Array.isArray(raw)) {
18589
- return raw.filter((item) => !!item && typeof item === "object" && typeof item.text === "string").map((item) => ({
18590
- text: item.text,
18591
- ...typeof item.icon === "string" ? { icon: item.icon } : {},
18592
- ...typeof item.iconColor === "string" ? { iconColor: item.iconColor } : {}
18593
- }));
18594
- }
18595
- if (typeof raw === "string") {
18596
- return raw.split(/\n|<br\s*\/?>/i).map((line) => line.replace(/<[^>]+>/g, "").replace(/^[-*•]\s*/, "").trim().slice(0, 120)).filter(Boolean).map((text) => ({ text }));
18597
- }
18598
- return [];
18599
- }
18600
- function pricingCardFeatureCount(tree, index) {
18601
- const target = findPricingCollection(tree)?.children?.[index];
18602
- return target ? pricingFeatureList(target).length : 0;
18603
- }
18604
- function addPricingCardFeature(tree, index, text) {
18605
- return withPricingCollection(tree, (collection) => {
18606
- const children = collection.children;
18607
- if (index < 0 || index >= children.length) return false;
18608
- const target = children[index];
18609
- const features = pricingFeatureList(target);
18610
- if (features.length >= PRICING_FEATURES_MAX) return false;
18611
- target.slots = target.slots ?? {};
18612
- target.slots.features = [...features, { text }];
18613
- return true;
18614
- });
18615
- }
18616
- function removePricingCardFeature(tree, index) {
18617
- return withPricingCollection(tree, (collection) => {
18618
- const children = collection.children;
18619
- if (index < 0 || index >= children.length) return false;
18620
- const target = children[index];
18621
- const features = pricingFeatureList(target);
18622
- if (features.length <= PRICING_FEATURES_MIN) return false;
18623
- target.slots = target.slots ?? {};
18624
- target.slots.features = features.slice(0, -1);
18625
- return true;
18626
- });
18627
- }
18628
-
18629
- // src/ui/ai-section/AiPricingCardOverlay.tsx
18630
18758
  var import_jsx_runtime20 = require("react/jsx-runtime");
18631
18759
  var PRIMARY4 = "var(--ohw-primary, #0885FE)";
18632
18760
  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]";
@@ -23752,6 +23880,23 @@ function currentIconRef(el) {
23752
23880
  );
23753
23881
  return named ? `lucide:${named.slice("lucide-".length)}` : "";
23754
23882
  }
23883
+ function requestValidateTree5(postToParent2, tree) {
23884
+ return new Promise((resolve) => {
23885
+ const id = `ai-validate-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
23886
+ const timeout = window.setTimeout(() => {
23887
+ window.removeEventListener("message", onMessage);
23888
+ resolve({ ok: false, reasons: ["Validation request timed out."] });
23889
+ }, 8e3);
23890
+ function onMessage(e) {
23891
+ if (e.data?.type !== "ow:ai-validate-tree-result" || e.data.id !== id) return;
23892
+ window.clearTimeout(timeout);
23893
+ window.removeEventListener("message", onMessage);
23894
+ resolve({ ok: e.data.ok === true, reasons: Array.isArray(e.data.reasons) ? e.data.reasons : [] });
23895
+ }
23896
+ window.addEventListener("message", onMessage);
23897
+ postToParent2({ type: "ow:ai-validate-tree-request", id, tree });
23898
+ });
23899
+ }
23755
23900
  function getEditMeasureEl(editable) {
23756
23901
  return editable.closest("[data-ohw-href-key]") ?? editable;
23757
23902
  }
@@ -28103,10 +28248,14 @@ function OhhwellsBridge() {
28103
28248
  e.preventDefault();
28104
28249
  e.stopPropagation();
28105
28250
  aiSectionApiRef.current?.selectFromElement(editable);
28251
+ const aiIcon = editable.hasAttribute("data-ai-icon");
28252
+ const featureIcon = aiIcon && /\.features\.\d+\.icon$/.test(editable.dataset.ohwKey ?? "");
28106
28253
  postToParentRef.current({
28107
28254
  type: "ow:icon-pick",
28108
28255
  key: editable.dataset.ohwKey ?? "",
28109
- current: currentIconRef(editable)
28256
+ current: currentIconRef(editable),
28257
+ ...aiIcon ? { iconMode: "name" } : {},
28258
+ ...featureIcon ? { options: [...PRICING_FEATURE_ICONS] } : {}
28110
28259
  });
28111
28260
  return;
28112
28261
  }
@@ -29353,15 +29502,43 @@ function OhhwellsBridge() {
29353
29502
  if (e.data?.type !== "ow:icon-markup") return;
29354
29503
  const { key, markup } = e.data;
29355
29504
  if (!key || typeof markup !== "string") return;
29356
- const targets = document.querySelectorAll(
29357
- `[data-ohw-key="${key}"][data-ohw-editable="icon"]`
29358
- );
29505
+ const targets = Array.from(
29506
+ document.querySelectorAll(`[data-ohw-key="${key}"][data-ohw-editable="icon"]`)
29507
+ ).filter((el) => !el.hasAttribute("data-ai-icon"));
29359
29508
  if (!targets.length) return;
29360
29509
  targets.forEach((el) => {
29361
29510
  applyIconMarkup(el, markup);
29362
29511
  });
29363
29512
  postToParentRef.current({ type: "ow:change", nodes: [{ key, text: markup }], flush: true });
29364
29513
  };
29514
+ const handleIconName = (e) => {
29515
+ if (e.data?.type !== "ow:icon-name") return;
29516
+ const { key, icon, url } = e.data;
29517
+ const isName = typeof icon === "string" && /^[a-z0-9-]+$/.test(icon);
29518
+ const isUpload = !isName && typeof url === "string" && /^https?:\/\/\S+$/i.test(url);
29519
+ if (!key || !isName && !isUpload) return;
29520
+ const state = parseAiSectionsState(aiSectionsRef.current);
29521
+ const entry = state.sections.find((s) => key.startsWith(aiSlotKeyPrefixFor(s.id)));
29522
+ if (!entry) return;
29523
+ const path = key.slice(aiSlotKeyPrefixFor(entry.id).length);
29524
+ const uploadRef = isUpload ? `icon-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}` : null;
29525
+ const nextTree = uploadRef ? applyIconUploadToTree(entry.tree, path, uploadRef) : applyIconNameToTree(entry.tree, path, icon);
29526
+ if (!nextTree) return;
29527
+ void requestValidateTree5(postToParentRef.current, nextTree).then(({ ok }) => {
29528
+ if (!ok) return;
29529
+ applyTreeLocalRef.current?.({
29530
+ id: entry.id,
29531
+ mode: "replace",
29532
+ targetSectionId: entry.id,
29533
+ tree: nextTree,
29534
+ label: entry.label,
29535
+ media: uploadRef ? { ...entry.media, [uploadRef]: url } : entry.media,
29536
+ ...entry.brand ? { brand: entry.brand } : {},
29537
+ // The user is looking at the icon they just changed — no need to recenter on it.
29538
+ skipScrollIntoView: true
29539
+ });
29540
+ });
29541
+ };
29365
29542
  const handleImageUrl = (e) => {
29366
29543
  if (e.data?.type !== "ow:image-url") return;
29367
29544
  const { key, url } = e.data;
@@ -30810,6 +30987,7 @@ function OhhwellsBridge() {
30810
30987
  window.addEventListener("message", handleSocialCancel);
30811
30988
  window.addEventListener("message", handleSocialUpdate);
30812
30989
  window.addEventListener("message", handleIconMarkup);
30990
+ window.addEventListener("message", handleIconName);
30813
30991
  window.addEventListener("message", handleImageUrl);
30814
30992
  window.addEventListener("message", handleImageUploading);
30815
30993
  window.addEventListener("message", handleCarouselChange);
@@ -30894,6 +31072,7 @@ function OhhwellsBridge() {
30894
31072
  window.removeEventListener("message", handleSocialCancel);
30895
31073
  window.removeEventListener("message", handleSocialUpdate);
30896
31074
  window.removeEventListener("message", handleIconMarkup);
31075
+ window.removeEventListener("message", handleIconName);
30897
31076
  window.removeEventListener("message", handleImageUrl);
30898
31077
  window.removeEventListener("message", handleImageUploading);
30899
31078
  window.removeEventListener("message", handleCarouselChange);
@@ -31219,7 +31398,7 @@ function OhhwellsBridge() {
31219
31398
  postToParent2({
31220
31399
  type: "ow:ready",
31221
31400
  version: "1",
31222
- bridgeVersion: "0.1.141",
31401
+ bridgeVersion: "0.1.143",
31223
31402
  path: pathname,
31224
31403
  nodes: collectEditableNodes(editContentRef.current),
31225
31404
  sections