@ohhwells/bridge 0.1.147 → 0.1.149-next.400

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
@@ -1571,7 +1571,19 @@ function brandTrackedFontFamily(doc, stack) {
1571
1571
  }
1572
1572
  var paintLockByEl = /* @__PURE__ */ new WeakMap();
1573
1573
  var paintObserverByEl = /* @__PURE__ */ new WeakMap();
1574
- var paintingButton = false;
1574
+ var PAINT_GUARD_ATTRS = {
1575
+ attributes: true,
1576
+ attributeFilter: ["style", "class"]
1577
+ };
1578
+ function paintGuarded(el, assignment, tokens) {
1579
+ const observer = paintObserverByEl.get(el);
1580
+ observer?.disconnect();
1581
+ try {
1582
+ paintButtonChrome(el, assignment, tokens);
1583
+ } finally {
1584
+ observer?.observe(el, PAINT_GUARD_ATTRS);
1585
+ }
1586
+ }
1575
1587
  function paintButtonChrome(el, assignment, tokens) {
1576
1588
  const origin = captureButtonOrigin(el);
1577
1589
  const chrome = tokens.variants[assignment.variant];
@@ -1610,14 +1622,11 @@ function paintButtonChrome(el, assignment, tokens) {
1610
1622
  function guardTemplateHover(el) {
1611
1623
  if (paintObserverByEl.has(el)) return;
1612
1624
  const observer = new MutationObserver(() => {
1613
- if (paintingButton) return;
1614
1625
  const lock = paintLockByEl.get(el);
1615
1626
  if (!lock) return;
1616
- paintingButton = true;
1617
- paintButtonChrome(el, lock.assignment, lock.tokens);
1618
- paintingButton = false;
1627
+ paintGuarded(el, lock.assignment, lock.tokens);
1619
1628
  });
1620
- observer.observe(el, { attributes: true, attributeFilter: ["style", "class"] });
1629
+ observer.observe(el, PAINT_GUARD_ATTRS);
1621
1630
  paintObserverByEl.set(el, observer);
1622
1631
  }
1623
1632
  function paintedTokensOf(el) {
@@ -1634,10 +1643,8 @@ function applyButtonStyle(el, assignment, tokens) {
1634
1643
  }
1635
1644
  };
1636
1645
  paintLockByEl.set(el, { assignment, tokens: tracked });
1637
- paintingButton = true;
1638
- paintButtonChrome(el, assignment, tracked);
1639
- paintingButton = false;
1640
1646
  guardTemplateHover(el);
1647
+ paintGuarded(el, assignment, tracked);
1641
1648
  }
1642
1649
  function applyButtonStylesFromContent(doc, content) {
1643
1650
  const assignments = readAssignments(content);
@@ -1719,7 +1726,8 @@ function labelElement2(el) {
1719
1726
  return el.querySelector('[data-ohw-editable="text"], [data-ohw-editable="plain"]') ?? el.querySelector("[data-ohw-key]");
1720
1727
  }
1721
1728
  function buttonLabel(el) {
1722
- return (labelElement2(el)?.textContent ?? el.textContent ?? "").trim();
1729
+ const label = labelElement2(el);
1730
+ return (label ? label.innerHTML : el.textContent ?? "").trim();
1723
1731
  }
1724
1732
  function buttonHref(el) {
1725
1733
  return el.getAttribute("href") ?? "";
@@ -1858,7 +1866,7 @@ function duplicateButton(source, content) {
1858
1866
  stampVariantClass(copy, copyAssignment.variant);
1859
1867
  applyButtonStyle(copy, copyAssignment, paintedTokensOf(source) ?? tokens);
1860
1868
  const copyLabelEl = labelElement2(copy);
1861
- if (copyLabelEl) copyLabelEl.textContent = label;
1869
+ if (copyLabelEl) copyLabelEl.innerHTML = label;
1862
1870
  let collection = buttonCollectionOf(source);
1863
1871
  const collections = readCollections(content);
1864
1872
  let collectionId = collection?.getAttribute(COLLECTION_ATTR) ?? "";
@@ -2184,7 +2192,7 @@ function applySnapshotContent(el, snapshot, content) {
2184
2192
  const label = labelElement2(el);
2185
2193
  if (label) {
2186
2194
  if (label.getAttribute("data-ohw-key") !== labelKey) label.setAttribute("data-ohw-key", labelKey);
2187
- if (label.textContent !== text) label.textContent = text;
2195
+ if (label.innerHTML !== text) label.innerHTML = text;
2188
2196
  }
2189
2197
  }
2190
2198
  function reconcileButtonCollections(doc, content) {
@@ -4269,7 +4277,7 @@ function buildNavLink(index, href, label, template) {
4269
4277
  const span = document.createElement("span");
4270
4278
  span.setAttribute("data-ohw-editable", "text");
4271
4279
  span.setAttribute("data-ohw-key", `nav-${index}-label`);
4272
- span.textContent = label;
4280
+ span.innerHTML = label;
4273
4281
  anchor.appendChild(span);
4274
4282
  return anchor;
4275
4283
  }
@@ -6629,6 +6637,21 @@ function pricingCardFeatureCount(tree, index) {
6629
6637
  const target = findPricingCollection(tree)?.children?.[index];
6630
6638
  return target ? pricingFeatureList(target).length : 0;
6631
6639
  }
6640
+ function prevailingFeatureIcon(features) {
6641
+ const counts = /* @__PURE__ */ new Map();
6642
+ for (const row of features) {
6643
+ const key = `${row.icon ?? ""}|${row.iconColor ?? ""}`;
6644
+ const entry = counts.get(key) ?? { count: 0, ...row.icon ? { icon: row.icon } : {}, ...row.iconColor ? { iconColor: row.iconColor } : {} };
6645
+ entry.count += 1;
6646
+ counts.set(key, entry);
6647
+ }
6648
+ let best = null;
6649
+ for (const entry of counts.values()) {
6650
+ if (!best || entry.count > best.count) best = entry;
6651
+ }
6652
+ if (!best) return {};
6653
+ return { ...best.icon ? { icon: best.icon } : {}, ...best.iconColor ? { iconColor: best.iconColor } : {} };
6654
+ }
6632
6655
  function addPricingCardFeature(tree, index, text) {
6633
6656
  return withPricingCollection(tree, (collection) => {
6634
6657
  const children = collection.children;
@@ -6637,7 +6660,7 @@ function addPricingCardFeature(tree, index, text) {
6637
6660
  const features = pricingFeatureList(target);
6638
6661
  if (features.length >= PRICING_FEATURES_MAX) return false;
6639
6662
  target.slots = target.slots ?? {};
6640
- target.slots.features = [...features, { text }];
6663
+ target.slots.features = [...features, { text, ...prevailingFeatureIcon(features) }];
6641
6664
  return true;
6642
6665
  });
6643
6666
  }
@@ -7820,7 +7843,7 @@ var AI_MOBILE_CSS = [
7820
7843
  // page's first section. Top only (bottom needs no nav clearance); ~96px approximates the
7821
7844
  // template nav's own height (measured ~94px) — there's no shared `--ohw-header-h` var to read
7822
7845
  // an exact figure from (no live measurement exists), so this is a safe approximation, not a
7823
- // pixel-exact fit. Horizontal spacing already comes from `data-ai-container`.
7846
+ // pixel-exact fit. Horizontal spacing already comes from `data-ai-section-inner`.
7824
7847
  '[data-ai-overlay-clearance] [data-ai-overlap-layer="overlay"]{padding-top:96px}',
7825
7848
  // A full-bleed carousel's height is pure arithmetic: it spans the whole window, so 16/9 turns
7826
7849
  // window width straight into height. Measured at 16/9 it came out ~90% of the viewport at EVERY
@@ -7836,8 +7859,10 @@ var AI_MOBILE_CSS = [
7836
7859
  "[data-ai-section]{overflow-x:hidden}",
7837
7860
  // `:not([data-ai-full-bleed])`: this `!important` gutter beat the inline `padding: 0` that a
7838
7861
  // full-bleed section's container sets, so a carousel/media hero that is edge-to-edge on desktop
7839
- // still got a 20px band of section background down both sides on a phone.
7840
- "[data-ai-container]:not([data-ai-full-bleed]){padding:0 20px !important}",
7862
+ // still got a 20px band of section background down both sides on a phone. The footer's own
7863
+ // container keeps the unguarded rule below — `data-ai-full-bleed` can never appear on it.
7864
+ "[data-ai-section-inner]:not([data-ai-full-bleed]){padding:0 20px !important}",
7865
+ "[data-ai-container]{padding:0 20px !important}",
7841
7866
  "[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
7842
7867
  "[data-ai-cell]{width:100%;min-width:0}",
7843
7868
  // The rule above turns every row into a COLUMN flex container on phones, which silently breaks
@@ -8042,6 +8067,31 @@ function pricingCardEmphasis(featured, ctx, hasBg) {
8042
8067
  function textAttrs(ctx, path) {
8043
8068
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
8044
8069
  }
8070
+ var AI_RESPONSIVE_CSS = [
8071
+ "@media (max-width: 960px) {",
8072
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
8073
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
8074
+ "}",
8075
+ "@media (max-width: 640px) {",
8076
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
8077
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
8078
+ // Group containers flatten to a column on phones; span placements come along for free.
8079
+ // `:not([data-ai-group="overlap"])` for the same reason as the twin pair in AI_MOBILE_CSS — see
8080
+ // the comment there. This sheet's copy is the narrower selector, so without the exemption it
8081
+ // flattens an `overlap` hero below 640px even though the other rule stands down: measured a 390px
8082
+ // viewport with the heading/CTA in flow *under* the image, 219px of section background where the
8083
+ // layered text used to be.
8084
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) { display: flex !important; flex-direction: column !important; }',
8085
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) > * { grid-column: auto !important; }',
8086
+ // `:not([data-ai-full-bleed])` for the same reason as the twin rule in AI_MOBILE_CSS: this one
8087
+ // is narrower (0,2,0 vs 0,1,0) and also `!important`, so without the guard it wins outright and
8088
+ // a full-bleed hero gets its 20px side band back below 640px only — the one width where the
8089
+ // other rule had already been taught to stand down.
8090
+ " [data-ai-responsive] [data-ai-section-inner]:not([data-ai-full-bleed]) { padding-left: 20px !important; padding-right: 20px !important; }",
8091
+ " [data-ai-responsive] { overflow-x: hidden; }",
8092
+ " [data-ai-responsive] img { max-width: 100%; }",
8093
+ "}"
8094
+ ].join("\n");
8045
8095
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
8046
8096
  function MediaBox({
8047
8097
  refValue,
@@ -8096,7 +8146,7 @@ function MediaBox({
8096
8146
  position: fill ? "absolute" : void 0,
8097
8147
  inset: fill ? 0 : void 0,
8098
8148
  width: fill ? "100%" : fixedSize ? "auto" : "100%",
8099
- height: fill ? "100%" : void 0,
8149
+ height: fill ? "100%" : fixedSize ? height : void 0,
8100
8150
  aspectRatio: fill ? void 0 : aspectRatio,
8101
8151
  minHeight: fill || fixedSize ? void 0 : height ?? (aspectRatio ? void 0 : 180),
8102
8152
  borderRadius: radius,
@@ -9592,7 +9642,7 @@ function CollectionBlock({ node, ctx, path }) {
9592
9642
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
9593
9643
  "div",
9594
9644
  {
9595
- "data-ai-grid": "",
9645
+ "data-ai-grid": String(itemsPerRow),
9596
9646
  "data-ohw-pricing-collection": isPricing ? "" : void 0,
9597
9647
  style: {
9598
9648
  display: "grid",
@@ -9894,7 +9944,7 @@ function renderNode(node, ctx, path, ambientAlign) {
9894
9944
  },
9895
9945
  children: [
9896
9946
  i > 0 && ctx.heroOverlapFullBleed ? (
9897
- // The section's `data-ai-container` gave up its max-width/gutter so the base
9947
+ // The section's `data-ai-section-inner` gave up its max-width/gutter so the base
9898
9948
  // layer (this group's i===0 sibling) can span edge-to-edge — reintroduced here,
9899
9949
  // scoped to just the overlay text, so a heading/copy/CTA stack doesn't stretch
9900
9950
  // the full section width instead of reading at the usual content width.
@@ -10313,6 +10363,7 @@ function AiTreeRenderer({
10313
10363
  ...fillHeight ? { "data-ai-fill-height": "" } : {},
10314
10364
  ...needsOverlayClearance ? { "data-ai-overlay-clearance": "" } : {},
10315
10365
  ...bgAttrs,
10366
+ "data-ai-responsive": "",
10316
10367
  style: {
10317
10368
  position: "relative",
10318
10369
  padding: `${padCss} 0`,
@@ -10326,12 +10377,13 @@ function AiTreeRenderer({
10326
10377
  ...fillHeight ? { display: "grid" } : {}
10327
10378
  },
10328
10379
  children: [
10329
- isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10380
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
10330
10381
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
10382
+ isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10331
10383
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
10332
10384
  "div",
10333
10385
  {
10334
- "data-ai-container": "",
10386
+ "data-ai-section-inner": "",
10335
10387
  ...fullBleedMedia ? { "data-ai-full-bleed": "" } : {},
10336
10388
  style: {
10337
10389
  position: "relative",
@@ -10348,7 +10400,7 @@ function AiTreeRenderer({
10348
10400
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
10349
10401
  "div",
10350
10402
  {
10351
- "data-ai-row": "",
10403
+ "data-ai-columns": "",
10352
10404
  style: {
10353
10405
  display: "grid",
10354
10406
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -10373,7 +10425,12 @@ function AiTreeRenderer({
10373
10425
  // the full row height instead of clumping at the top.
10374
10426
  ...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
10375
10427
  },
10376
- children: renderNode(block, ctx, `r${r2}.b${b}`)
10428
+ children: renderNode(
10429
+ block,
10430
+ ctx,
10431
+ `r${r2}.b${b}`,
10432
+ settings.alignment === "center" ? "center" : "left"
10433
+ )
10377
10434
  },
10378
10435
  b
10379
10436
  ))
@@ -11005,9 +11062,10 @@ function applyAiSectionsToDom(state, options) {
11005
11062
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
11006
11063
  continue;
11007
11064
  }
11008
- const isNewMount = !existing || !existing.container.isConnected;
11009
11065
  let container = existing?.container;
11066
+ let isNewMount = false;
11010
11067
  if (!container || !container.isConnected) {
11068
+ isNewMount = true;
11011
11069
  existing?.root.unmount();
11012
11070
  container?.remove();
11013
11071
  container = document.createElement("div");
@@ -17449,6 +17507,7 @@ function MediaOverlay({
17449
17507
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
17450
17508
  );
17451
17509
  }, [isVideo]);
17510
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
17452
17511
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
17453
17512
  const box = {
17454
17513
  position: "fixed",
@@ -17547,11 +17606,10 @@ function MediaOverlay({
17547
17606
  className: "flex items-center justify-center cursor-pointer",
17548
17607
  style: {
17549
17608
  ...box,
17550
- // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
17551
- // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
17552
- // in-document, pointer-events does it natively. The button below opts back in, so
17553
- // Replace still works.
17554
- pointerEvents: hover.hasTextOverlap || cueOnly ? "none" : "auto",
17609
+ // Visual only. `pointer-events: auto` on this full-bleed rect covered the navbar
17610
+ // (hero bg includes the absolute TopNav) and swallowed CTA press-drag. Text overlay
17611
+ // and cueOnly also need clicks to fall through — Replace / video controls opt back in.
17612
+ pointerEvents: "none",
17555
17613
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
17556
17614
  // than hovered. Hover keeps the existing tinted preview.
17557
17615
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -17578,17 +17636,17 @@ function MediaOverlay({
17578
17636
  },
17579
17637
  children: [
17580
17638
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
17581
- isVideo ? "Replace video" : "Replace image"
17639
+ replaceLabel
17582
17640
  ]
17583
17641
  }
17584
17642
  ),
17585
- replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
17643
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
17586
17644
  Button,
17587
17645
  {
17588
17646
  "data-ohw-media-overlay": "",
17589
17647
  variant: "outline",
17590
17648
  size: "sm",
17591
- "aria-label": isVideo ? "Replace video" : "Replace image",
17649
+ "aria-label": replaceLabel,
17592
17650
  className: "gap-1.5 cursor-pointer hover:bg-background",
17593
17651
  style: {
17594
17652
  ...OVERLAY_BUTTON_STYLE,
@@ -17611,7 +17669,7 @@ function MediaOverlay({
17611
17669
  },
17612
17670
  children: [
17613
17671
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
17614
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
17672
+ replaceMode === "full" ? replaceLabel : null
17615
17673
  ]
17616
17674
  }
17617
17675
  )
@@ -18121,7 +18179,7 @@ function AiSectionOverlay({
18121
18179
 
18122
18180
  // src/ui/ai-section/AiMediaStripOverlay.tsx
18123
18181
  var import_react10 = require("react");
18124
- var import_lucide_react8 = require("lucide-react");
18182
+ var import_lucide_react9 = require("lucide-react");
18125
18183
 
18126
18184
  // src/lib/ai-media-strip-tree.ts
18127
18185
  var MEDIA_STRIP_ITEM_MIN = 1;
@@ -18235,14 +18293,6 @@ function setMediaStripItemAsset(tree, index, assetId) {
18235
18293
  return true;
18236
18294
  });
18237
18295
  }
18238
- function setMediaStripItemAlt(tree, index, alt) {
18239
- return withMediaStrip(tree, (block) => {
18240
- const items = block.items;
18241
- if (index < 0 || index >= items.length) return false;
18242
- items[index] = { ...items[index], alt };
18243
- return true;
18244
- });
18245
- }
18246
18296
  function setMediaStripItemLink(tree, index, link) {
18247
18297
  return withMediaStrip(tree, (block) => {
18248
18298
  const items = block.items;
@@ -18260,11 +18310,117 @@ function setMediaStripItemLink(tree, index, link) {
18260
18310
  });
18261
18311
  }
18262
18312
 
18263
- // src/ui/ai-section/AiMediaStripOverlay.tsx
18313
+ // src/ui/dialog.tsx
18314
+ var React9 = __toESM(require("react"), 1);
18315
+ var import_radix_ui6 = require("radix-ui");
18316
+ var import_lucide_react8 = require("lucide-react");
18264
18317
  var import_jsx_runtime19 = require("react/jsx-runtime");
18265
- var PRIMARY3 = "#0885FE";
18266
- var OHW_BLUE = `var(--ohw-primary, ${PRIMARY3})`;
18267
- var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY3}) 60%, #000000)`;
18318
+ function Dialog2({
18319
+ ...props
18320
+ }) {
18321
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_radix_ui6.Dialog.Root, { "data-slot": "dialog", ...props });
18322
+ }
18323
+ function DialogPortal({
18324
+ ...props
18325
+ }) {
18326
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_radix_ui6.Dialog.Portal, { ...props });
18327
+ }
18328
+ function DialogOverlay({
18329
+ className,
18330
+ ...props
18331
+ }) {
18332
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
18333
+ import_radix_ui6.Dialog.Overlay,
18334
+ {
18335
+ "data-slot": "dialog-overlay",
18336
+ "data-ohw-link-modal-root": "",
18337
+ className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
18338
+ ...props
18339
+ }
18340
+ );
18341
+ }
18342
+ var DialogContent = React9.forwardRef(
18343
+ ({ className, children, showCloseButton = true, container, ...props }, ref) => {
18344
+ const positionMode = container ? "absolute" : "fixed";
18345
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(DialogPortal, { container: container ?? void 0, children: [
18346
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
18347
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
18348
+ import_radix_ui6.Dialog.Content,
18349
+ {
18350
+ ref,
18351
+ "data-slot": "dialog-content",
18352
+ className: cn(
18353
+ positionMode,
18354
+ "left-1/2 top-1/2 z-[2147483647] flex w-full max-w-[448px] -translate-x-1/2 -translate-y-1/2 flex-col overflow-visible",
18355
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
18356
+ container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
18357
+ className
18358
+ ),
18359
+ onMouseDown: (e) => e.stopPropagation(),
18360
+ ...props,
18361
+ children: [
18362
+ children,
18363
+ showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
18364
+ import_radix_ui6.Dialog.Close,
18365
+ {
18366
+ type: "button",
18367
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
18368
+ "aria-label": "Close",
18369
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react8.X, { size: 16, "aria-hidden": true })
18370
+ }
18371
+ ) : null
18372
+ ]
18373
+ }
18374
+ )
18375
+ ] });
18376
+ }
18377
+ );
18378
+ DialogContent.displayName = import_radix_ui6.Dialog.Content.displayName;
18379
+ function DialogHeader({
18380
+ className,
18381
+ ...props
18382
+ }) {
18383
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
18384
+ }
18385
+ function DialogFooter({
18386
+ className,
18387
+ ...props
18388
+ }) {
18389
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
18390
+ "div",
18391
+ {
18392
+ className: cn("flex items-center justify-end gap-2", className),
18393
+ ...props
18394
+ }
18395
+ );
18396
+ }
18397
+ var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
18398
+ import_radix_ui6.Dialog.Title,
18399
+ {
18400
+ ref,
18401
+ className: cn(
18402
+ "text-lg font-semibold leading-none tracking-tight text-card-foreground",
18403
+ className
18404
+ ),
18405
+ ...props
18406
+ }
18407
+ ));
18408
+ DialogTitle.displayName = import_radix_ui6.Dialog.Title.displayName;
18409
+ var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
18410
+ import_radix_ui6.Dialog.Description,
18411
+ {
18412
+ ref,
18413
+ className: cn("text-sm text-muted-foreground", className),
18414
+ ...props
18415
+ }
18416
+ ));
18417
+ DialogDescription.displayName = import_radix_ui6.Dialog.Description.displayName;
18418
+ var DialogClose = import_radix_ui6.Dialog.Close;
18419
+
18420
+ // src/ui/ai-section/AiMediaStripOverlay.tsx
18421
+ var import_jsx_runtime20 = require("react/jsx-runtime");
18422
+ var PRIMARY3 = "var(--ohw-primary, #0885FE)";
18423
+ 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]";
18268
18424
  function findMediaStripItemElement(sectionId, index) {
18269
18425
  const sectionEl = findByInstanceId(sectionId);
18270
18426
  if (!sectionEl) return null;
@@ -18274,14 +18430,35 @@ function findMediaStripItemElement(sectionId, index) {
18274
18430
  }
18275
18431
  return null;
18276
18432
  }
18277
- function readRect2(ref) {
18278
- const el = ref && findMediaStripItemElement(ref.sectionId, ref.index);
18433
+ function itemRefFromTarget(target) {
18434
+ if (!(target instanceof Element)) return null;
18435
+ if (target.closest(CHROME_SELECTOR)) return null;
18436
+ const itemEl = target.closest("[data-ohw-media-strip-item]");
18437
+ if (!itemEl) return null;
18438
+ const sectionEl = itemEl.closest("[data-ohw-section]");
18439
+ const unit = sectionEl ? movableUnit(sectionEl) : null;
18440
+ const sectionId = unit ? instanceIdOf(unit) || null : null;
18441
+ const index = Number(itemEl.dataset.ohwMediaStripIndex);
18442
+ if (!sectionId || Number.isNaN(index)) return null;
18443
+ return { sectionId, index };
18444
+ }
18445
+ function sameRef(a, b) {
18446
+ return a?.sectionId === b?.sectionId && a?.index === b?.index;
18447
+ }
18448
+ function rectOf(el) {
18279
18449
  if (!el) return null;
18280
18450
  const r2 = el.getBoundingClientRect();
18281
18451
  if (r2.width <= 0 || r2.height <= 0) return null;
18282
18452
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
18283
18453
  }
18284
- function useLiveItemRect(ref) {
18454
+ function readRect2(ref) {
18455
+ return rectOf(ref && findMediaStripItemElement(ref.sectionId, ref.index));
18456
+ }
18457
+ function readStripRect(ref) {
18458
+ const item = ref && findMediaStripItemElement(ref.sectionId, ref.index);
18459
+ return rectOf(item?.closest("[data-ohw-media-strip]") ?? item);
18460
+ }
18461
+ function useLiveItemRect(ref, strip = false) {
18285
18462
  const [rect, setRect] = (0, import_react10.useState)(null);
18286
18463
  (0, import_react10.useEffect)(() => {
18287
18464
  if (!ref) {
@@ -18289,7 +18466,7 @@ function useLiveItemRect(ref) {
18289
18466
  return;
18290
18467
  }
18291
18468
  const update = () => {
18292
- const next = readRect2(ref);
18469
+ const next = strip ? readStripRect(ref) : readRect2(ref);
18293
18470
  setRect(
18294
18471
  (prev) => prev && next && prev.top === next.top && prev.left === next.left && prev.width === next.width && prev.height === next.height ? prev : next
18295
18472
  );
@@ -18304,9 +18481,49 @@ function useLiveItemRect(ref) {
18304
18481
  window.removeEventListener("resize", update);
18305
18482
  clearInterval(interval);
18306
18483
  };
18307
- }, [ref?.sectionId, ref?.index]);
18484
+ }, [ref?.sectionId, ref?.index, strip]);
18308
18485
  return rect;
18309
18486
  }
18487
+ var TOOLBAR_STRIP_GAP = 52;
18488
+ var TOOLBAR_STRIP_GAP_MOBILE = 42;
18489
+ var TOOLBAR_EDGE_MARGIN2 = 4;
18490
+ function StripToolbarSlot({ stripRect, children }) {
18491
+ const slotRef = (0, import_react10.useRef)(null);
18492
+ const [shift, setShift] = (0, import_react10.useState)(0);
18493
+ const centerX = stripRect.left + stripRect.width / 2;
18494
+ (0, import_react10.useLayoutEffect)(() => {
18495
+ const slot = slotRef.current;
18496
+ if (!slot) return;
18497
+ const clamp = () => {
18498
+ const w = slot.offsetWidth;
18499
+ if (w <= 0) return;
18500
+ const half = w / 2;
18501
+ const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
18502
+ setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
18503
+ };
18504
+ clamp();
18505
+ const ro = new ResizeObserver(clamp);
18506
+ ro.observe(slot);
18507
+ return () => ro.disconnect();
18508
+ }, [centerX, children]);
18509
+ const mobile = window.innerWidth <= 480;
18510
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18511
+ "div",
18512
+ {
18513
+ ref: slotRef,
18514
+ "data-ohw-bridge": "",
18515
+ "data-ohw-item-interaction": "",
18516
+ className: "pointer-events-auto fixed",
18517
+ style: {
18518
+ top: stripRect.top - (mobile ? TOOLBAR_STRIP_GAP_MOBILE : TOOLBAR_STRIP_GAP),
18519
+ left: centerX + shift,
18520
+ transform: "translate(-50%, -100%)",
18521
+ zIndex: 2147483645
18522
+ },
18523
+ children
18524
+ }
18525
+ );
18526
+ }
18310
18527
  function requestValidateTree(postToParent2, tree) {
18311
18528
  return new Promise((resolve) => {
18312
18529
  const id = `ai-validate-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -18343,221 +18560,177 @@ function requestAssetPick(postToParent2) {
18343
18560
  postToParent2({ type: "ow:asset-pick", id });
18344
18561
  });
18345
18562
  }
18346
- function Pill({
18563
+ function ToolbarAction({
18347
18564
  label,
18348
18565
  onClick,
18349
- disabled,
18350
- active
18566
+ disabled = false,
18567
+ active = false,
18568
+ children
18351
18569
  }) {
18352
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
18353
- "button",
18354
- {
18355
- type: "button",
18356
- onClick,
18357
- disabled,
18358
- style: {
18359
- height: 28,
18360
- padding: "0 10px",
18361
- display: "inline-flex",
18362
- alignItems: "center",
18363
- gap: 4,
18364
- borderRadius: 999,
18365
- border: "none",
18366
- cursor: disabled ? "default" : "pointer",
18367
- background: active ? OHW_BLUE_DARK : "#ffffff",
18368
- color: active ? "#ffffff" : OHW_BLUE_DARK,
18369
- opacity: disabled ? 0.4 : 1,
18370
- fontSize: 12,
18371
- lineHeight: "16px",
18372
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18373
- whiteSpace: "nowrap",
18374
- flex: "0 0 auto"
18375
- },
18376
- children: label
18377
- }
18378
- );
18570
+ const button = /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, active, onClick, className: "size-8", children });
18571
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(Tooltip, { children: [
18572
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "inline-flex", children: button }) : button }),
18573
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
18574
+ ] });
18379
18575
  }
18380
- function AltInput({ value, onCommit }) {
18381
- const [draft, setDraft] = (0, import_react10.useState)(value);
18382
- (0, import_react10.useEffect)(() => setDraft(value), [value]);
18383
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
18384
- "div",
18385
- {
18386
- style: {
18387
- display: "flex",
18388
- alignItems: "center",
18389
- gap: 6,
18390
- height: 28,
18391
- padding: "0 12px",
18392
- borderRadius: 999,
18393
- background: "#ffffff",
18394
- width: "100%",
18395
- maxWidth: 260,
18396
- boxSizing: "border-box"
18397
- },
18398
- children: [
18399
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
18400
- "span",
18401
- {
18402
- style: {
18403
- flex: "0 0 auto",
18404
- color: OHW_BLUE_DARK,
18405
- fontSize: 10,
18406
- fontWeight: 700,
18407
- textTransform: "uppercase",
18408
- letterSpacing: 0.4
18409
- },
18410
- children: "Alt"
18411
- }
18412
- ),
18413
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
18414
- "input",
18415
- {
18416
- type: "text",
18417
- value: draft,
18418
- onChange: (e) => setDraft(e.target.value),
18419
- onBlur: () => {
18420
- if (draft !== value) onCommit(draft);
18421
- },
18422
- onKeyDown: (e) => {
18423
- if (e.key === "Enter") e.currentTarget.blur();
18424
- if (e.key === "Escape") {
18425
- setDraft(value);
18426
- e.currentTarget.blur();
18427
- }
18428
- },
18429
- placeholder: "Describe this image",
18430
- "aria-label": "Image alt text",
18431
- style: {
18432
- flex: 1,
18433
- minWidth: 0,
18434
- border: "none",
18435
- outline: "none",
18436
- background: "transparent",
18437
- fontSize: 12,
18438
- lineHeight: "16px",
18439
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18440
- color: "#1f2937"
18441
- }
18442
- }
18443
- )
18444
- ]
18445
- }
18446
- );
18576
+ function ToolbarSeparator() {
18577
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
18447
18578
  }
18448
- var VARIANTS = [
18449
- { value: "logos", label: "Logos" },
18450
- { value: "reel", label: "Reel" }
18451
- ];
18452
- var ALIGNS = [
18453
- { value: "left", label: "Left" },
18454
- { value: "center", label: "Center" },
18455
- { value: "right", label: "Right" }
18456
- ];
18457
- var HOVERS = [
18458
- { value: "none", label: "None" },
18459
- { value: "lift", label: "Lift" },
18460
- { value: "zoom", label: "Zoom" },
18461
- { value: "fade", label: "Fade" }
18462
- ];
18463
- var TREATMENTS = [
18464
- { value: "color", label: "Color" },
18465
- { value: "grayscale", label: "Grayscale" }
18466
- ];
18579
+ function OptionMenu({
18580
+ label,
18581
+ value,
18582
+ options,
18583
+ onChange
18584
+ }) {
18585
+ const current = options.find((o) => o.value === value) ?? options[0];
18586
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
18587
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "text-xs text-muted-foreground", children: label }),
18588
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(DropdownMenu, { modal: false, children: [
18589
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
18590
+ "button",
18591
+ {
18592
+ type: "button",
18593
+ "aria-label": label,
18594
+ className: "inline-flex h-8 min-w-[76px] items-center gap-[7px] rounded-[6px] bg-transparent px-2 text-[13px] font-medium text-foreground transition-colors hover:bg-muted data-[state=open]:bg-muted",
18595
+ children: [
18596
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "flex-1 text-left", children: current.label }),
18597
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react9.ChevronDown, { size: 16, style: { pointerEvents: "none" } })
18598
+ ]
18599
+ }
18600
+ ) }),
18601
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[132px] min-w-[132px] p-1", children: options.map((option) => {
18602
+ const selected = option.value === value;
18603
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
18604
+ DropdownMenuItem,
18605
+ {
18606
+ role: "menuitemradio",
18607
+ "aria-checked": selected,
18608
+ onSelect: () => onChange(option.value),
18609
+ className: cn("grid grid-cols-[1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
18610
+ children: [
18611
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { children: option.label }),
18612
+ selected && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react9.Check, { size: 16, className: "text-bridge-primary" })
18613
+ ]
18614
+ },
18615
+ option.value
18616
+ );
18617
+ }) })
18618
+ ] })
18619
+ ] });
18620
+ }
18621
+ var VARIANTS = [
18622
+ { value: "logos", label: "Logos" },
18623
+ { value: "reel", label: "Reel" }
18624
+ ];
18625
+ var ALIGNS = [
18626
+ { value: "left", label: "Align left", Icon: import_lucide_react9.AlignLeft },
18627
+ { value: "center", label: "Align center", Icon: import_lucide_react9.AlignCenter },
18628
+ { value: "right", label: "Align right", Icon: import_lucide_react9.AlignRight }
18629
+ ];
18630
+ var HOVERS = [
18631
+ { value: "none", label: "None" },
18632
+ { value: "lift", label: "Lift" },
18633
+ { value: "zoom", label: "Zoom" },
18634
+ { value: "fade", label: "Fade" }
18635
+ ];
18467
18636
  var HTTP_URL_RE = /^https?:\/\/\S+$/i;
18468
- function LinkInput({ value, onCommit }) {
18469
- const [draft, setDraft] = (0, import_react10.useState)(value);
18637
+ function LinkModal({
18638
+ href,
18639
+ linkTarget,
18640
+ onClose,
18641
+ onSave
18642
+ }) {
18643
+ const [draft, setDraft] = (0, import_react10.useState)(href);
18644
+ const [newTab, setNewTab] = (0, import_react10.useState)(linkTarget !== "same-tab");
18470
18645
  const [invalid, setInvalid] = (0, import_react10.useState)(false);
18471
- (0, import_react10.useEffect)(() => {
18472
- setDraft(value);
18473
- setInvalid(false);
18474
- }, [value]);
18475
- const commit = () => {
18646
+ const save = () => {
18476
18647
  const next = draft.trim();
18477
- if (next === value) return setInvalid(false);
18478
- if (next && !HTTP_URL_RE.test(next)) return setInvalid(true);
18479
- setInvalid(false);
18480
- onCommit(next);
18481
- };
18482
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
18483
- "div",
18484
- {
18485
- style: {
18486
- display: "flex",
18487
- alignItems: "center",
18488
- gap: 6,
18489
- height: 28,
18490
- padding: "0 12px",
18491
- borderRadius: 999,
18492
- background: "#ffffff",
18493
- width: "100%",
18494
- maxWidth: 260,
18495
- boxSizing: "border-box",
18496
- boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
18497
- },
18498
- children: [
18499
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
18500
- "span",
18501
- {
18502
- style: {
18503
- flex: "0 0 auto",
18504
- color: OHW_BLUE_DARK,
18505
- fontSize: 10,
18506
- fontWeight: 700,
18507
- textTransform: "uppercase",
18508
- letterSpacing: 0.4
18509
- },
18510
- children: "Link"
18511
- }
18512
- ),
18513
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
18514
- "input",
18515
- {
18516
- type: "text",
18517
- inputMode: "url",
18518
- value: draft,
18519
- onChange: (e) => setDraft(e.target.value),
18520
- onBlur: commit,
18521
- onKeyDown: (e) => {
18522
- if (e.key === "Enter") e.currentTarget.blur();
18523
- if (e.key === "Escape") {
18524
- setDraft(value);
18525
- setInvalid(false);
18526
- e.currentTarget.blur();
18527
- }
18528
- },
18529
- placeholder: "https://example.com",
18530
- "aria-label": "Image link URL",
18531
- "aria-invalid": invalid,
18532
- style: {
18533
- flex: 1,
18534
- minWidth: 0,
18535
- border: "none",
18536
- outline: "none",
18537
- background: "transparent",
18538
- fontSize: 12,
18539
- lineHeight: "16px",
18540
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18541
- color: "#1f2937"
18542
- }
18543
- }
18544
- )
18545
- ]
18648
+ if (next && !HTTP_URL_RE.test(next)) {
18649
+ setInvalid(true);
18650
+ return;
18546
18651
  }
18547
- );
18652
+ onSave(next, newTab ? "new-tab" : "same-tab");
18653
+ onClose();
18654
+ };
18655
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Dialog2, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(DialogContent, { "data-ohw-link-modal-root": "", className: "max-w-[400px] gap-4 p-5", children: [
18656
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(DialogHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(DialogTitle, { className: "text-base", children: "Image link" }) }),
18657
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex flex-col gap-3", children: [
18658
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18659
+ "input",
18660
+ {
18661
+ autoFocus: true,
18662
+ type: "text",
18663
+ inputMode: "url",
18664
+ value: draft,
18665
+ onChange: (e) => {
18666
+ setDraft(e.target.value);
18667
+ setInvalid(false);
18668
+ },
18669
+ onKeyDown: (e) => {
18670
+ if (e.key === "Enter") save();
18671
+ },
18672
+ placeholder: "https://example.com",
18673
+ "aria-label": "Image link URL",
18674
+ "aria-invalid": invalid,
18675
+ className: cn(
18676
+ "h-9 w-full rounded-[6px] border border-border bg-background px-3 text-[13px] text-foreground outline-none placeholder:text-muted-foreground/70 focus:border-bridge-primary",
18677
+ invalid && "border-destructive focus:border-destructive"
18678
+ )
18679
+ }
18680
+ ),
18681
+ invalid && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "text-xs text-destructive", children: "Enter a full URL starting with http:// or https://" }),
18682
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("label", { className: "flex items-center gap-2 text-[13px] text-foreground", children: [
18683
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("input", { type: "checkbox", checked: newTab, onChange: (e) => setNewTab(e.target.checked) }),
18684
+ "Open in new tab"
18685
+ ] })
18686
+ ] }),
18687
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(DialogFooter, { children: [
18688
+ href && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18689
+ "button",
18690
+ {
18691
+ type: "button",
18692
+ onClick: () => {
18693
+ onSave("", "new-tab");
18694
+ onClose();
18695
+ },
18696
+ className: "mr-auto rounded-[6px] px-3 py-2 text-[13px] font-medium text-destructive transition-colors hover:bg-muted",
18697
+ children: "Remove link"
18698
+ }
18699
+ ),
18700
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18701
+ "button",
18702
+ {
18703
+ type: "button",
18704
+ onClick: onClose,
18705
+ className: "rounded-[6px] px-3 py-2 text-[13px] font-medium text-foreground transition-colors hover:bg-muted",
18706
+ children: "Cancel"
18707
+ }
18708
+ ),
18709
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18710
+ "button",
18711
+ {
18712
+ type: "button",
18713
+ onClick: save,
18714
+ className: "rounded-[6px] bg-bridge-primary px-3 py-2 text-[13px] font-medium text-primary-foreground transition-opacity hover:opacity-90",
18715
+ children: "Save"
18716
+ }
18717
+ )
18718
+ ] })
18719
+ ] }) });
18548
18720
  }
18549
18721
  function MediaStripToolbar({
18550
18722
  entry,
18551
18723
  index,
18552
18724
  pending,
18553
18725
  error,
18726
+ itemEditOn,
18727
+ onToggleItemEdit,
18554
18728
  onSetVariant,
18555
18729
  onSetAlign,
18556
18730
  onSetHover,
18557
18731
  onSetTreatment,
18558
18732
  onResize,
18559
- onSetAlt,
18560
- onSetLink,
18733
+ onOpenLink,
18561
18734
  onAddItem,
18562
18735
  onRemoveItem,
18563
18736
  onChangeImage,
@@ -18566,81 +18739,102 @@ function MediaStripToolbar({
18566
18739
  }) {
18567
18740
  const items = mediaStripItems(entry.tree);
18568
18741
  const itemCount = items.length;
18569
- const currentAlt = items[index]?.alt ?? "";
18570
- const currentHref = items[index]?.href ?? "";
18571
- const currentTarget = items[index]?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
18572
18742
  const slots = findMediaStripSlots(entry.tree);
18573
18743
  const variant = slots?.variant === "reel" ? "reel" : "logos";
18574
18744
  const align = slots?.align === "left" || slots?.align === "right" ? slots.align : "center";
18575
18745
  const hover = slots?.hover === "none" || slots?.hover === "lift" || slots?.hover === "zoom" || slots?.hover === "fade" ? slots.hover : "lift";
18576
18746
  const treatment = slots?.treatment === "color" || slots?.treatment === "grayscale" ? slots.treatment : variant === "logos" ? "grayscale" : "color";
18577
18747
  const sizeValue = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
18578
- const isMobile = window.innerWidth <= 480;
18579
- const rowStyle = isMobile ? {
18580
- display: "flex",
18581
- gap: 6,
18582
- flexWrap: "nowrap",
18583
- justifyContent: "center",
18584
- overflowX: "auto",
18585
- WebkitOverflowScrolling: "touch",
18586
- width: "100%",
18587
- scrollbarWidth: "none"
18588
- } : { display: "flex", gap: 6, flexWrap: "nowrap", justifyContent: "center" };
18589
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
18748
+ const mobile = window.innerWidth <= 480;
18749
+ const gatedLabel = (label) => itemEditOn ? label : "Turn on the star to edit this image";
18750
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
18590
18751
  "div",
18591
18752
  {
18592
18753
  "data-ohw-bridge": "",
18593
18754
  "data-ohw-toolbar": "",
18594
18755
  onMouseDown: (e) => e.stopPropagation(),
18595
- style: {
18596
- display: "flex",
18597
- flexDirection: "column",
18598
- gap: 6,
18599
- alignItems: "center",
18600
- background: OHW_BLUE,
18601
- padding: 8,
18602
- borderRadius: 12,
18603
- boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
18604
- pointerEvents: pending ? "none" : "auto",
18605
- opacity: pending ? 0.6 : 1,
18606
- maxWidth: isMobile ? "calc(100vw - 24px)" : "none"
18607
- },
18756
+ className: cn("flex flex-col items-center gap-1.5 font-sans", pending && "pointer-events-none opacity-60"),
18757
+ style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
18608
18758
  children: [
18609
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: rowStyle, children: [
18610
- VARIANTS.map((v) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: v.label, active: variant === v.value, onClick: () => onSetVariant(v.value) }, v.value)),
18611
- ALIGNS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: a.label, active: align === a.value, onClick: () => onSetAlign(a.value) }, a.value))
18612
- ] }),
18613
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: rowStyle, children: [
18614
- HOVERS.map((h) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: h.label, active: hover === h.value, onClick: () => onSetHover(h.value) }, h.value)),
18615
- TREATMENTS.map((t) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: t.label, active: treatment === t.value, onClick: () => onSetTreatment(t.value) }, t.value))
18616
- ] }),
18617
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
18618
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: "\u2212", onClick: () => onResize(-8) }),
18619
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { style: { color: "#ffffff", fontSize: 12, minWidth: 34, textAlign: "center" }, children: [
18620
- sizeValue,
18621
- "px"
18622
- ] }),
18623
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: "+", onClick: () => onResize(8) })
18624
- ] }),
18625
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(AltInput, { value: currentAlt, onCommit: onSetAlt }) }),
18626
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(LinkInput, { value: currentHref, onCommit: (href) => onSetLink(href, currentTarget) }) }),
18627
- currentHref && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: rowStyle, children: [
18628
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: "New tab", active: currentTarget === "new-tab", onClick: () => onSetLink(currentHref, "new-tab") }),
18629
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: "Same tab", active: currentTarget === "same-tab", onClick: () => onSetLink(currentHref, "same-tab") })
18630
- ] }),
18631
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: rowStyle, children: [
18632
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: "Change Image", onClick: onChangeImage }),
18633
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: "+ Item", onClick: onAddItem }),
18634
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: "\u2212 Item", onClick: onRemoveItem, disabled: itemCount <= 1 })
18635
- ] }),
18636
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: rowStyle, children: [
18637
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: "\u2190 Move", onClick: onMoveLeft, disabled: index <= 0 }),
18638
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: "\u2192 Move", onClick: onMoveRight, disabled: index >= itemCount - 1 })
18639
- ] }),
18640
- error && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
18759
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
18760
+ "div",
18761
+ {
18762
+ className: cn(
18763
+ "flex items-center gap-1 rounded-[10px] border border-border bg-background p-1 whitespace-nowrap",
18764
+ "shadow-[0px_10px_15px_-3px_rgba(0,0,0,0.1),0px_4px_6px_-4px_rgba(0,0,0,0.1)]",
18765
+ mobile ? "flex-wrap justify-center" : "flex-nowrap"
18766
+ ),
18767
+ children: [
18768
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(Tooltip, { children: [
18769
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18770
+ CustomToolbarButton,
18771
+ {
18772
+ type: "button",
18773
+ "aria-label": itemEditOn ? "Done editing this image" : "Edit this image",
18774
+ "aria-pressed": itemEditOn,
18775
+ onClick: onToggleItemEdit,
18776
+ className: "group size-8",
18777
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18778
+ import_lucide_react9.Star,
18779
+ {
18780
+ size: 16,
18781
+ fill: "currentColor",
18782
+ strokeWidth: 0,
18783
+ className: cn(
18784
+ "transition-colors",
18785
+ itemEditOn ? "text-[#f59e0b] group-hover:text-[#d97706]" : "text-[#d6d3d1] group-hover:text-[#fbbf24]"
18786
+ ),
18787
+ style: { pointerEvents: "none" }
18788
+ }
18789
+ )
18790
+ }
18791
+ ) }),
18792
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: itemEditOn ? "Done editing this image" : "Edit this image" })
18793
+ ] }),
18794
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarSeparator, {}),
18795
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarAction, { label: gatedLabel("Change image"), onClick: onChangeImage, disabled: !itemEditOn, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react9.Image, { size: 16, style: { pointerEvents: "none" } }) }),
18796
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarAction, { label: gatedLabel("Link"), onClick: onOpenLink, disabled: !itemEditOn, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react9.Link, { size: 16, style: { pointerEvents: "none" } }) }),
18797
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarAction, { label: gatedLabel("Move left"), onClick: onMoveLeft, disabled: !itemEditOn || index <= 0, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react9.ArrowLeft, { size: 16, style: { pointerEvents: "none" } }) }),
18798
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18799
+ ToolbarAction,
18800
+ {
18801
+ label: gatedLabel("Move right"),
18802
+ onClick: onMoveRight,
18803
+ disabled: !itemEditOn || index >= itemCount - 1,
18804
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react9.ArrowRight, { size: 16, style: { pointerEvents: "none" } })
18805
+ }
18806
+ ),
18807
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarAction, { label: gatedLabel("Remove image"), onClick: onRemoveItem, disabled: !itemEditOn || itemCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react9.ImageMinus, { size: 16, style: { pointerEvents: "none" } }) }),
18808
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarSeparator, {}),
18809
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(OptionMenu, { label: "Layout", value: variant, options: VARIANTS, onChange: onSetVariant }),
18810
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(OptionMenu, { label: "Hover", value: hover, options: HOVERS, onChange: onSetHover }),
18811
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18812
+ ToolbarAction,
18813
+ {
18814
+ label: treatment === "grayscale" ? "Show in color" : "Show grayscale",
18815
+ active: treatment === "grayscale",
18816
+ onClick: () => onSetTreatment(treatment === "grayscale" ? "color" : "grayscale"),
18817
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react9.Palette, { size: 16, style: { pointerEvents: "none" } })
18818
+ }
18819
+ ),
18820
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarSeparator, {}),
18821
+ ALIGNS.map(({ value, label, Icon }) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarAction, { label, active: align === value, onClick: () => onSetAlign(value), children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Icon, { size: 16, style: { pointerEvents: "none" } }) }, value)),
18822
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarSeparator, {}),
18823
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarAction, { label: "Smaller", onClick: () => onResize(-8), children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react9.Minus, { size: 16, style: { pointerEvents: "none" } }) }),
18824
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "min-w-[40px] text-center text-xs tabular-nums text-muted-foreground", children: [
18825
+ sizeValue,
18826
+ "px"
18827
+ ] }),
18828
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarAction, { label: "Larger", onClick: () => onResize(8), children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react9.Plus, { size: 16, style: { pointerEvents: "none" } }) }),
18829
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarSeparator, {}),
18830
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarAction, { label: "Add image", onClick: onAddItem, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react9.ImagePlus, { size: 16, style: { pointerEvents: "none" } }) })
18831
+ ]
18832
+ }
18833
+ ),
18834
+ error && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
18641
18835
  ]
18642
18836
  }
18643
- );
18837
+ ) });
18644
18838
  }
18645
18839
  function findMediaStripSlots(tree) {
18646
18840
  const stack = tree.rows.flatMap((row) => row.blocks);
@@ -18659,31 +18853,23 @@ function AiMediaStripOverlay({
18659
18853
  }) {
18660
18854
  const [hover, setHover] = (0, import_react10.useState)(null);
18661
18855
  const [active, setActive] = (0, import_react10.useState)(null);
18856
+ const [itemEditOn, setItemEditOn] = (0, import_react10.useState)(false);
18857
+ const [linkOpen, setLinkOpen] = (0, import_react10.useState)(false);
18662
18858
  const [pending, setPending] = (0, import_react10.useState)(false);
18663
18859
  const [error, setError] = (0, import_react10.useState)(null);
18664
18860
  const activeRef = (0, import_react10.useRef)(null);
18665
18861
  activeRef.current = active;
18862
+ const linkOpenRef = (0, import_react10.useRef)(false);
18863
+ linkOpenRef.current = linkOpen;
18666
18864
  (0, import_react10.useEffect)(() => {
18667
18865
  const onMove = (e) => {
18866
+ const ref = itemRefFromTarget(e.target);
18668
18867
  const t = e.target;
18669
- if (!(t instanceof HTMLElement)) return;
18670
- if (t.closest("[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-link-modal-root], [data-ohw-section-picker], [data-ohw-ai-review]")) {
18671
- return;
18672
- }
18673
- const itemEl = t.closest("[data-ohw-media-strip-item]");
18674
- if (!itemEl) {
18675
- setHover(null);
18676
- return;
18677
- }
18678
- const sectionEl = itemEl.closest("[data-ohw-section]");
18679
- const unit = sectionEl ? movableUnit(sectionEl) : null;
18680
- const sectionId = unit ? instanceIdOf(unit) || null : null;
18681
- const index = Number(itemEl.dataset.ohwMediaStripIndex);
18682
- if (!sectionId || Number.isNaN(index)) {
18683
- setHover(null);
18684
- return;
18685
- }
18686
- setHover({ sectionId, index });
18868
+ const overEditable = Boolean(t?.closest?.("[data-ohw-editable]"));
18869
+ setHover((prev) => {
18870
+ const next = ref && !overEditable ? ref : null;
18871
+ return sameRef(prev, next) ? prev : next;
18872
+ });
18687
18873
  };
18688
18874
  const onLeave = () => setHover(null);
18689
18875
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -18695,23 +18881,25 @@ function AiMediaStripOverlay({
18695
18881
  }, []);
18696
18882
  (0, import_react10.useEffect)(() => {
18697
18883
  const onClick = (e) => {
18698
- const current = activeRef.current;
18699
- if (!current) return;
18700
18884
  const t = e.target;
18701
- if (!(t instanceof HTMLElement)) return;
18702
- if (t.closest("[data-ohw-item-interaction]")) return;
18703
- const itemEl = findMediaStripItemElement(current.sectionId, current.index);
18704
- if (itemEl && itemEl.contains(t)) return;
18705
- setActive(null);
18885
+ if (!(t instanceof Element)) return;
18886
+ if (t.closest(CHROME_SELECTOR)) return;
18887
+ const ref = itemRefFromTarget(t);
18888
+ if (sameRef(activeRef.current, ref)) return;
18889
+ if (ref) clearInteractionChromeRef.current();
18890
+ setItemEditOn(false);
18706
18891
  setError(null);
18892
+ setActive(ref);
18707
18893
  };
18708
18894
  document.addEventListener("click", onClick, true);
18709
18895
  return () => document.removeEventListener("click", onClick, true);
18710
- }, []);
18896
+ }, [clearInteractionChromeRef]);
18711
18897
  (0, import_react10.useEffect)(() => {
18712
18898
  const onBlur = () => {
18713
18899
  if (!activeRef.current) return;
18714
18900
  setActive(null);
18901
+ setItemEditOn(false);
18902
+ setLinkOpen(false);
18715
18903
  setError(null);
18716
18904
  };
18717
18905
  window.addEventListener("blur", onBlur);
@@ -18721,7 +18909,10 @@ function AiMediaStripOverlay({
18721
18909
  const onKeyDown = (e) => {
18722
18910
  if (e.key !== "Escape") return;
18723
18911
  if (!activeRef.current) return;
18912
+ if (linkOpenRef.current) return;
18913
+ if (document.querySelector('[data-ohw-more-menu][data-state="open"]')) return;
18724
18914
  setActive(null);
18915
+ setItemEditOn(false);
18725
18916
  setError(null);
18726
18917
  };
18727
18918
  document.addEventListener("keydown", onKeyDown);
@@ -18755,137 +18946,133 @@ function AiMediaStripOverlay({
18755
18946
  tree: nextTree,
18756
18947
  label: entry.label,
18757
18948
  media: extraMedia ? { ...entry.media, ...extraMedia } : entry.media,
18758
- ...entry.brand ? { brand: entry.brand } : {}
18949
+ ...entry.brand ? { brand: entry.brand } : {},
18950
+ // The user is already looking at the strip they just edited — recentering the viewport
18951
+ // on every edit reads as the section jumping around.
18952
+ skipScrollIntoView: true
18759
18953
  });
18760
18954
  return true;
18761
18955
  },
18762
18956
  [findEntry, postToParent2, applyTreeLocalRef]
18763
18957
  );
18764
18958
  const activeEntry = active ? findEntry(active.sectionId) : null;
18765
- const hoverHandleRect = useLiveItemRect(!active && hover ? hover : null);
18766
18959
  const activeItemRect = useLiveItemRect(active);
18767
- const TOOLBAR_APPROX_H = 170;
18768
- const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
18769
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
18770
- hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
18771
- "button",
18772
- {
18773
- type: "button",
18774
- "data-ohw-toolbar": "",
18775
- onClick: () => {
18776
- clearInteractionChromeRef.current();
18777
- setActive(hover);
18778
- },
18779
- "aria-label": "Configure media item",
18780
- style: {
18781
- position: "fixed",
18782
- top: hoverHandleRect.top + 8,
18783
- left: hoverHandleRect.left + hoverHandleRect.width - 36,
18784
- width: 28,
18785
- height: 28,
18786
- borderRadius: 8,
18787
- border: `1.5px dashed ${PRIMARY3}`,
18788
- background: "#ffffff",
18789
- color: PRIMARY3,
18790
- display: "flex",
18791
- alignItems: "center",
18792
- justifyContent: "center",
18793
- cursor: "pointer",
18794
- zIndex: 2147483643,
18795
- fontSize: 14,
18796
- fontWeight: 700
18797
- },
18798
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react8.Star, { size: 14, style: { pointerEvents: "none" } })
18799
- }
18800
- ),
18801
- activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
18802
- ItemInteractionLayer,
18803
- {
18804
- rect: activeItemRect,
18805
- state: toolbarState,
18806
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
18807
- MediaStripToolbar,
18808
- {
18809
- entry: activeEntry,
18810
- index: active.index,
18811
- pending,
18812
- error,
18813
- onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
18814
- onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
18815
- onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
18816
- onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
18817
- onResize: (delta) => {
18818
- const slots = findMediaStripSlots(activeEntry.tree);
18819
- const variant = slots?.variant === "reel" ? "reel" : "logos";
18820
- const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
18821
- void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
18822
- },
18823
- onSetAlt: (alt) => {
18824
- void runMutation(active.sectionId, (tree) => setMediaStripItemAlt(tree, active.index, alt));
18825
- },
18826
- onSetLink: (href, linkTarget) => {
18960
+ const activeStripRect = useLiveItemRect(active, true);
18961
+ const hoverRect = useLiveItemRect(hover && !sameRef(hover, active) ? hover : null);
18962
+ const activeItems = activeEntry ? mediaStripItems(activeEntry.tree) : [];
18963
+ const activeItem = active ? activeItems[active.index] : void 0;
18964
+ const activeHref = activeItem?.href ?? "";
18965
+ const activeTarget = activeItem?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
18966
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
18967
+ hoverRect && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
18968
+ activeItemRect && activeStripRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
18969
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ItemInteractionLayer, { rect: activeItemRect, state: "active-top" }),
18970
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(StripToolbarSlot, { stripRect: activeStripRect, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18971
+ MediaStripToolbar,
18972
+ {
18973
+ entry: activeEntry,
18974
+ index: active.index,
18975
+ pending,
18976
+ error,
18977
+ itemEditOn,
18978
+ onToggleItemEdit: () => setItemEditOn((on) => !on),
18979
+ onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
18980
+ onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
18981
+ onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
18982
+ onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
18983
+ onResize: (delta) => {
18984
+ const slots = findMediaStripSlots(activeEntry.tree);
18985
+ const variant = slots?.variant === "reel" ? "reel" : "logos";
18986
+ const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
18987
+ void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
18988
+ },
18989
+ onOpenLink: () => setLinkOpen(true),
18990
+ onAddItem: () => {
18991
+ void requestAssetPick(postToParent2).then((picked) => {
18992
+ if (!picked) return;
18993
+ const item = { assetId: picked.assetId, alt: "Logo" };
18827
18994
  void runMutation(
18828
18995
  active.sectionId,
18829
- (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
18830
- );
18831
- },
18832
- onAddItem: () => {
18833
- void requestAssetPick(postToParent2).then((picked) => {
18834
- if (!picked) return;
18835
- const item = { assetId: picked.assetId, alt: "Logo" };
18836
- void runMutation(
18837
- active.sectionId,
18838
- (tree) => addMediaStripItem(tree, item, active.index),
18839
- { [picked.assetId]: picked.url }
18840
- ).then((applied) => {
18841
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
18842
- });
18843
- });
18844
- },
18845
- onChangeImage: () => {
18846
- void requestAssetPick(postToParent2).then((picked) => {
18847
- if (!picked) return;
18848
- void runMutation(
18849
- active.sectionId,
18850
- (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
18851
- { [picked.assetId]: picked.url }
18852
- );
18996
+ (tree) => addMediaStripItem(tree, item, active.index),
18997
+ { [picked.assetId]: picked.url }
18998
+ ).then((applied) => {
18999
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
18853
19000
  });
18854
- },
18855
- onRemoveItem: () => {
18856
- void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
18857
- (applied) => {
18858
- if (applied) setActive(null);
18859
- }
18860
- );
18861
- },
18862
- onMoveLeft: () => {
18863
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
18864
- (applied) => {
18865
- if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
18866
- }
18867
- );
18868
- },
18869
- onMoveRight: () => {
18870
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
18871
- (applied) => {
18872
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
18873
- }
19001
+ });
19002
+ },
19003
+ onChangeImage: () => {
19004
+ void requestAssetPick(postToParent2).then((picked) => {
19005
+ if (!picked) return;
19006
+ void runMutation(
19007
+ active.sectionId,
19008
+ (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
19009
+ { [picked.assetId]: picked.url }
18874
19010
  );
18875
- }
19011
+ });
19012
+ },
19013
+ onRemoveItem: () => {
19014
+ void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
19015
+ (applied) => {
19016
+ if (applied) setActive(null);
19017
+ }
19018
+ );
19019
+ },
19020
+ onMoveLeft: () => {
19021
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
19022
+ (applied) => {
19023
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
19024
+ }
19025
+ );
19026
+ },
19027
+ onMoveRight: () => {
19028
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
19029
+ (applied) => {
19030
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19031
+ }
19032
+ );
18876
19033
  }
18877
- )
18878
- }
18879
- )
19034
+ }
19035
+ ) }),
19036
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19037
+ "div",
19038
+ {
19039
+ "data-ohw-bridge": "",
19040
+ "data-ohw-item-interaction": "",
19041
+ "aria-hidden": true,
19042
+ className: "pointer-events-none fixed rounded-[4px] px-[7px] py-[3px] font-sans text-[11px] font-medium leading-none text-primary-foreground whitespace-nowrap",
19043
+ style: {
19044
+ top: activeItemRect.top + activeItemRect.height + 10,
19045
+ left: activeItemRect.left - 5,
19046
+ background: PRIMARY3,
19047
+ zIndex: 2147483644
19048
+ },
19049
+ children: "Media item"
19050
+ }
19051
+ ),
19052
+ linkOpen && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19053
+ LinkModal,
19054
+ {
19055
+ href: activeHref,
19056
+ linkTarget: activeTarget,
19057
+ onClose: () => setLinkOpen(false),
19058
+ onSave: (href, linkTarget) => {
19059
+ void runMutation(
19060
+ active.sectionId,
19061
+ (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
19062
+ );
19063
+ }
19064
+ }
19065
+ )
19066
+ ] })
18880
19067
  ] });
18881
19068
  }
18882
19069
 
18883
19070
  // src/ui/ai-section/AiPricingCardOverlay.tsx
18884
19071
  var import_react11 = require("react");
18885
- var import_lucide_react9 = require("lucide-react");
18886
- var import_jsx_runtime20 = require("react/jsx-runtime");
19072
+ var import_lucide_react10 = require("lucide-react");
19073
+ var import_jsx_runtime21 = require("react/jsx-runtime");
18887
19074
  var PRIMARY4 = "var(--ohw-primary, #0885FE)";
18888
- 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]";
19075
+ var CHROME_SELECTOR2 = "[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]";
18889
19076
  function findPricingCardElement(sectionId, index) {
18890
19077
  const sectionEl = findByInstanceId(sectionId);
18891
19078
  if (!sectionEl) return null;
@@ -18897,7 +19084,7 @@ function findPricingCardElement(sectionId, index) {
18897
19084
  }
18898
19085
  function cardRefFromTarget(target) {
18899
19086
  if (!(target instanceof Element)) return null;
18900
- if (target.closest(CHROME_SELECTOR)) return null;
19087
+ if (target.closest(CHROME_SELECTOR2)) return null;
18901
19088
  const cardEl = target.closest("[data-ohw-pricing-card]");
18902
19089
  if (!cardEl) return null;
18903
19090
  const sectionEl = cardEl.closest("[data-ohw-section]");
@@ -18907,21 +19094,21 @@ function cardRefFromTarget(target) {
18907
19094
  if (!sectionId || Number.isNaN(index)) return null;
18908
19095
  return { sectionId, index };
18909
19096
  }
18910
- function sameRef(a, b) {
19097
+ function sameRef2(a, b) {
18911
19098
  return a?.sectionId === b?.sectionId && a?.index === b?.index;
18912
19099
  }
18913
- function rectOf(el) {
19100
+ function rectOf2(el) {
18914
19101
  if (!el) return null;
18915
19102
  const r2 = el.getBoundingClientRect();
18916
19103
  if (r2.width <= 0 || r2.height <= 0) return null;
18917
19104
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
18918
19105
  }
18919
19106
  function readRect3(ref) {
18920
- return rectOf(ref && findPricingCardElement(ref.sectionId, ref.index));
19107
+ return rectOf2(ref && findPricingCardElement(ref.sectionId, ref.index));
18921
19108
  }
18922
19109
  function readGridRect(ref) {
18923
19110
  const card = ref && findPricingCardElement(ref.sectionId, ref.index);
18924
- return rectOf(card?.closest("[data-ohw-pricing-collection]") ?? card);
19111
+ return rectOf2(card?.closest("[data-ohw-pricing-collection]") ?? card);
18925
19112
  }
18926
19113
  function useLiveCardRect(ref, grid = false) {
18927
19114
  const [rect, setRect] = (0, import_react11.useState)(null);
@@ -18951,7 +19138,7 @@ function useLiveCardRect(ref, grid = false) {
18951
19138
  }
18952
19139
  var TOOLBAR_GRID_GAP = 52;
18953
19140
  var TOOLBAR_GRID_GAP_MOBILE = 42;
18954
- var TOOLBAR_EDGE_MARGIN2 = 4;
19141
+ var TOOLBAR_EDGE_MARGIN3 = 4;
18955
19142
  function GridToolbarSlot({ gridRect, children }) {
18956
19143
  const slotRef = (0, import_react11.useRef)(null);
18957
19144
  const [shift, setShift] = (0, import_react11.useState)(0);
@@ -18963,7 +19150,7 @@ function GridToolbarSlot({ gridRect, children }) {
18963
19150
  const w = slot.offsetWidth;
18964
19151
  if (w <= 0) return;
18965
19152
  const half = w / 2;
18966
- const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
19153
+ const clamped = Math.max(TOOLBAR_EDGE_MARGIN3 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half));
18967
19154
  setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
18968
19155
  };
18969
19156
  clamp();
@@ -18972,7 +19159,7 @@ function GridToolbarSlot({ gridRect, children }) {
18972
19159
  return () => ro.disconnect();
18973
19160
  }, [centerX, children]);
18974
19161
  const mobile = window.innerWidth <= 480;
18975
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19162
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
18976
19163
  "div",
18977
19164
  {
18978
19165
  ref: slotRef,
@@ -19007,7 +19194,7 @@ function requestValidateTree2(postToParent2, tree) {
19007
19194
  });
19008
19195
  }
19009
19196
  function StyleSwatch({ style }) {
19010
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19197
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19011
19198
  "span",
19012
19199
  {
19013
19200
  "aria-hidden": true,
@@ -19021,35 +19208,35 @@ function StyleSwatch({ style }) {
19021
19208
  );
19022
19209
  }
19023
19210
  function ButtonGlyph() {
19024
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("svg", { width: "18", height: "12", viewBox: "0 0 18 12", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19025
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("rect", { x: "0.75", y: "0.75", width: "16.5", height: "10.5", rx: "3", stroke: "currentColor", strokeWidth: "1.5" }),
19026
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
19211
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("svg", { width: "18", height: "12", viewBox: "0 0 18 12", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19212
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("rect", { x: "0.75", y: "0.75", width: "16.5", height: "10.5", rx: "3", stroke: "currentColor", strokeWidth: "1.5" }),
19213
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
19027
19214
  ] });
19028
19215
  }
19029
19216
  function FeatureGlyph({ mode }) {
19030
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19031
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
19032
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
19033
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("circle", { cx: "13.5", cy: "13.5", r: "4.25", fill: "var(--ohw-background, #ffffff)", stroke: "var(--ohw-background, #ffffff)", strokeWidth: "1" }),
19034
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("circle", { cx: "13.5", cy: "13.5", r: "3.75", fill: "var(--ohw-background, #ffffff)", stroke: PRIMARY4, strokeWidth: "1" }),
19035
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
19036
- mode === "add" && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
19217
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19218
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
19219
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
19220
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("circle", { cx: "13.5", cy: "13.5", r: "4.25", fill: "var(--ohw-background, #ffffff)", stroke: "var(--ohw-background, #ffffff)", strokeWidth: "1" }),
19221
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("circle", { cx: "13.5", cy: "13.5", r: "3.75", fill: "var(--ohw-background, #ffffff)", stroke: PRIMARY4, strokeWidth: "1" }),
19222
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
19223
+ mode === "add" && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
19037
19224
  ] });
19038
19225
  }
19039
- function ToolbarAction({
19226
+ function ToolbarAction2({
19040
19227
  label,
19041
19228
  onClick,
19042
19229
  disabled = false,
19043
19230
  children
19044
19231
  }) {
19045
- const button = /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
19046
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(Tooltip, { children: [
19047
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "inline-flex", children: button }) : button }),
19048
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19232
+ const button = /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
19233
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19234
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "inline-flex", children: button }) : button }),
19235
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19049
19236
  ] });
19050
19237
  }
19051
- function ToolbarSeparator() {
19052
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19238
+ function ToolbarSeparator2() {
19239
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19053
19240
  }
19054
19241
  function CardStyleMenu({
19055
19242
  value,
@@ -19057,7 +19244,7 @@ function CardStyleMenu({
19057
19244
  disabled = false
19058
19245
  }) {
19059
19246
  const current = PRICING_CARD_STYLES.find((s) => s.value === value) ?? PRICING_CARD_STYLES[0];
19060
- const trigger = /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
19247
+ const trigger = /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19061
19248
  "button",
19062
19249
  {
19063
19250
  type: "button",
@@ -19068,28 +19255,28 @@ function CardStyleMenu({
19068
19255
  disabled && "cursor-not-allowed opacity-40 hover:bg-transparent"
19069
19256
  ),
19070
19257
  children: [
19071
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(StyleSwatch, { style: current.value }),
19072
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "flex-1 text-left", children: current.label }),
19073
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react9.ChevronDown, { size: 16, style: { pointerEvents: "none" } })
19258
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StyleSwatch, { style: current.value }),
19259
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "flex-1 text-left", children: current.label }),
19260
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react10.ChevronDown, { size: 16, style: { pointerEvents: "none" } })
19074
19261
  ]
19075
19262
  }
19076
19263
  );
19077
19264
  if (disabled) {
19078
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19079
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
19080
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(Tooltip, { children: [
19081
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "inline-flex", children: trigger }) }),
19082
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
19265
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19266
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
19267
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19268
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "inline-flex", children: trigger }) }),
19269
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
19083
19270
  ] })
19084
19271
  ] });
19085
19272
  }
19086
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19087
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
19088
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(DropdownMenu, { modal: false, children: [
19089
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(DropdownMenuTrigger, { asChild: true, children: trigger }),
19090
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[152px] min-w-[152px] p-1", children: PRICING_CARD_STYLES.map((style) => {
19273
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19274
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
19275
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DropdownMenu, { modal: false, children: [
19276
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuTrigger, { asChild: true, children: trigger }),
19277
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[152px] min-w-[152px] p-1", children: PRICING_CARD_STYLES.map((style) => {
19091
19278
  const selected = style.value === value;
19092
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
19279
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19093
19280
  DropdownMenuItem,
19094
19281
  {
19095
19282
  role: "menuitemradio",
@@ -19097,9 +19284,9 @@ function CardStyleMenu({
19097
19284
  onSelect: () => onChange(style.value),
19098
19285
  className: cn("grid grid-cols-[18px_1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
19099
19286
  children: [
19100
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(StyleSwatch, { style: style.value }),
19101
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { children: style.label }),
19102
- selected && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react9.Check, { size: 16, className: "text-bridge-primary" })
19287
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StyleSwatch, { style: style.value }),
19288
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: style.label }),
19289
+ selected && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react10.Check, { size: 16, className: "text-bridge-primary" })
19103
19290
  ]
19104
19291
  },
19105
19292
  style.value
@@ -19129,7 +19316,7 @@ function PricingCardToolbar({
19129
19316
  const style = pricingCardStyle(entry.tree, index);
19130
19317
  const featuredTag = pricingCardFeaturedTag(entry.tree, index);
19131
19318
  const mobile = window.innerWidth <= 480;
19132
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
19319
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19133
19320
  "div",
19134
19321
  {
19135
19322
  "data-ohw-bridge": "",
@@ -19141,7 +19328,7 @@ function PricingCardToolbar({
19141
19328
  ),
19142
19329
  style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
19143
19330
  children: [
19144
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
19331
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19145
19332
  "div",
19146
19333
  {
19147
19334
  className: cn(
@@ -19150,8 +19337,8 @@ function PricingCardToolbar({
19150
19337
  mobile ? "flex-wrap justify-center" : "flex-nowrap"
19151
19338
  ),
19152
19339
  children: [
19153
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(Tooltip, { children: [
19154
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19340
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19341
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19155
19342
  CustomToolbarButton,
19156
19343
  {
19157
19344
  type: "button",
@@ -19159,8 +19346,8 @@ function PricingCardToolbar({
19159
19346
  "aria-pressed": featuredTag,
19160
19347
  onClick: () => onSetFeaturedTag(!featuredTag),
19161
19348
  className: "group size-8",
19162
- children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19163
- import_lucide_react9.Star,
19349
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19350
+ import_lucide_react10.Star,
19164
19351
  {
19165
19352
  size: 16,
19166
19353
  fill: "currentColor",
@@ -19174,25 +19361,25 @@ function PricingCardToolbar({
19174
19361
  )
19175
19362
  }
19176
19363
  ) }),
19177
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
19364
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
19178
19365
  ] }),
19179
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarSeparator, {}),
19180
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
19181
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarSeparator, {}),
19182
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarAction, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ButtonGlyph, {}) }),
19183
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarAction, { label: "Remove button", onClick: onRemoveButton, disabled: buttonCount === 0, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "relative inline-flex", children: [
19184
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ButtonGlyph, {}),
19185
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react9.Minus, { size: 10, strokeWidth: 2.5, className: "absolute -right-1.5 -top-1.5 rounded-full bg-background text-bridge-primary" })
19366
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator2, {}),
19367
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
19368
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator2, {}),
19369
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction2, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ButtonGlyph, {}) }),
19370
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction2, { label: "Remove button", onClick: onRemoveButton, disabled: buttonCount === 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "relative inline-flex", children: [
19371
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ButtonGlyph, {}),
19372
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react10.Minus, { size: 10, strokeWidth: 2.5, className: "absolute -right-1.5 -top-1.5 rounded-full bg-background text-bridge-primary" })
19186
19373
  ] }) }),
19187
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarAction, { label: "Add feature", onClick: onAddFeature, disabled: featureCount >= 10, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(FeatureGlyph, { mode: "add" }) }),
19188
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarAction, { label: "Remove feature", onClick: onRemoveFeature, disabled: featureCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(FeatureGlyph, { mode: "remove" }) }),
19189
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarSeparator, {}),
19190
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarAction, { label: "Add tier", onClick: onAddTier, disabled: collectionCount >= 4, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react9.Plus, { size: 16, style: { pointerEvents: "none" } }) }),
19191
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarAction, { label: "Remove tier", onClick: onRemoveTier, disabled: collectionCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react9.Minus, { size: 16, style: { pointerEvents: "none" } }) })
19374
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction2, { label: "Add feature", onClick: onAddFeature, disabled: featureCount >= 10, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(FeatureGlyph, { mode: "add" }) }),
19375
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction2, { label: "Remove feature", onClick: onRemoveFeature, disabled: featureCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(FeatureGlyph, { mode: "remove" }) }),
19376
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator2, {}),
19377
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction2, { label: "Add tier", onClick: onAddTier, disabled: collectionCount >= 4, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react10.Plus, { size: 16, style: { pointerEvents: "none" } }) }),
19378
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction2, { label: "Remove tier", onClick: onRemoveTier, disabled: collectionCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react10.Minus, { size: 16, style: { pointerEvents: "none" } }) })
19192
19379
  ]
19193
19380
  }
19194
19381
  ),
19195
- error && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19382
+ error && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19196
19383
  ]
19197
19384
  }
19198
19385
  ) });
@@ -19226,7 +19413,7 @@ function AiPricingCardOverlay({
19226
19413
  const overEditable = Boolean(t?.closest?.("[data-ohw-editable], [data-ohw-role], a, button"));
19227
19414
  setHover((prev) => {
19228
19415
  const next = ref && !overEditable ? ref : null;
19229
- return sameRef(prev, next) ? prev : next;
19416
+ return sameRef2(prev, next) ? prev : next;
19230
19417
  });
19231
19418
  };
19232
19419
  const onLeave = () => setHover(null);
@@ -19241,10 +19428,10 @@ function AiPricingCardOverlay({
19241
19428
  const onClick = (e) => {
19242
19429
  const t = e.target;
19243
19430
  if (!(t instanceof Element)) return;
19244
- if (t.closest(CHROME_SELECTOR)) return;
19431
+ if (t.closest(CHROME_SELECTOR2)) return;
19245
19432
  const ref = cardRefFromTarget(t);
19246
- setActive((prev) => sameRef(prev, ref) ? prev : ref);
19247
- if (!ref || !sameRef(activeRef.current, ref)) setError(null);
19433
+ setActive((prev) => sameRef2(prev, ref) ? prev : ref);
19434
+ if (!ref || !sameRef2(activeRef.current, ref)) setError(null);
19248
19435
  };
19249
19436
  document.addEventListener("click", onClick, true);
19250
19437
  return () => document.removeEventListener("click", onClick, true);
@@ -19309,12 +19496,12 @@ function AiPricingCardOverlay({
19309
19496
  const activeEntry = active ? findEntry(active.sectionId) : null;
19310
19497
  const activeCardRect = useLiveCardRect(active);
19311
19498
  const activeGridRect = useLiveCardRect(active, true);
19312
- const hoverRect = useLiveCardRect(hover && !sameRef(hover, active) ? hover : null);
19313
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
19314
- hoverRect && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19315
- activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
19316
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
19317
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19499
+ const hoverRect = useLiveCardRect(hover && !sameRef2(hover, active) ? hover : null);
19500
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19501
+ hoverRect && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19502
+ activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19503
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
19504
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19318
19505
  PricingCardToolbar,
19319
19506
  {
19320
19507
  entry: activeEntry,
@@ -19342,7 +19529,7 @@ function AiPricingCardOverlay({
19342
19529
  onRemoveFeature: () => runMutation(active.sectionId, (tree) => removePricingCardFeature(tree, active.index))
19343
19530
  }
19344
19531
  ) }),
19345
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19532
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19346
19533
  "div",
19347
19534
  {
19348
19535
  "data-ohw-bridge": "",
@@ -19364,7 +19551,7 @@ function AiPricingCardOverlay({
19364
19551
 
19365
19552
  // src/ui/ai-section/AiVideoOverlay.tsx
19366
19553
  var import_react12 = require("react");
19367
- var import_lucide_react10 = require("lucide-react");
19554
+ var import_lucide_react11 = require("lucide-react");
19368
19555
 
19369
19556
  // src/lib/ai-video-tree.ts
19370
19557
  function resolveVideoBlock(tree, path) {
@@ -19435,10 +19622,10 @@ function extractYouTubeId(input) {
19435
19622
  }
19436
19623
 
19437
19624
  // src/ui/ai-section/AiVideoOverlay.tsx
19438
- var import_jsx_runtime21 = require("react/jsx-runtime");
19625
+ var import_jsx_runtime22 = require("react/jsx-runtime");
19439
19626
  var PRIMARY5 = "#0885FE";
19440
- var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY5})`;
19441
- var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
19627
+ var OHW_BLUE = `var(--ohw-primary, ${PRIMARY5})`;
19628
+ var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
19442
19629
  function findVideoBlockElement(sectionId, path) {
19443
19630
  const sectionEl = findByInstanceId(sectionId);
19444
19631
  if (!sectionEl) return null;
@@ -19498,12 +19685,12 @@ function requestValidateTree3(postToParent2, tree) {
19498
19685
  postToParent2({ type: "ow:ai-validate-tree-request", id, tree });
19499
19686
  });
19500
19687
  }
19501
- function Pill2({
19688
+ function Pill({
19502
19689
  label,
19503
19690
  onClick,
19504
19691
  disabled
19505
19692
  }) {
19506
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19693
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19507
19694
  "button",
19508
19695
  {
19509
19696
  type: "button",
@@ -19519,7 +19706,7 @@ function Pill2({
19519
19706
  border: "none",
19520
19707
  cursor: disabled ? "default" : "pointer",
19521
19708
  background: "#ffffff",
19522
- color: OHW_BLUE_DARK2,
19709
+ color: OHW_BLUE_DARK,
19523
19710
  opacity: disabled ? 0.4 : 1,
19524
19711
  fontSize: 12,
19525
19712
  lineHeight: "16px",
@@ -19562,8 +19749,8 @@ function VideoUrlInput({ value, onCommit }) {
19562
19749
  onCommit(id);
19563
19750
  }
19564
19751
  };
19565
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
19566
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19752
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
19753
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19567
19754
  "div",
19568
19755
  {
19569
19756
  style: {
@@ -19579,12 +19766,12 @@ function VideoUrlInput({ value, onCommit }) {
19579
19766
  boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
19580
19767
  },
19581
19768
  children: [
19582
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19769
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19583
19770
  "span",
19584
19771
  {
19585
19772
  style: {
19586
19773
  flex: "0 0 auto",
19587
- color: OHW_BLUE_DARK2,
19774
+ color: OHW_BLUE_DARK,
19588
19775
  fontSize: 10,
19589
19776
  lineHeight: "16px",
19590
19777
  fontWeight: 700,
@@ -19594,7 +19781,7 @@ function VideoUrlInput({ value, onCommit }) {
19594
19781
  children: "Video"
19595
19782
  }
19596
19783
  ),
19597
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19784
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19598
19785
  "input",
19599
19786
  {
19600
19787
  type: "text",
@@ -19631,7 +19818,7 @@ function VideoUrlInput({ value, onCommit }) {
19631
19818
  ]
19632
19819
  }
19633
19820
  ),
19634
- invalid && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, textAlign: "center" }, children: "That doesn't look like a YouTube link \u2014 try pasting the video URL again." })
19821
+ invalid && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, textAlign: "center" }, children: "That doesn't look like a YouTube link \u2014 try pasting the video URL again." })
19635
19822
  ] });
19636
19823
  }
19637
19824
  function VideoToolbar({
@@ -19641,7 +19828,7 @@ function VideoToolbar({
19641
19828
  onSetVideoId,
19642
19829
  onRemove
19643
19830
  }) {
19644
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19831
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19645
19832
  "div",
19646
19833
  {
19647
19834
  "data-ohw-bridge": "",
@@ -19653,7 +19840,7 @@ function VideoToolbar({
19653
19840
  gap: 6,
19654
19841
  alignItems: "center",
19655
19842
  width: 280,
19656
- background: OHW_BLUE2,
19843
+ background: OHW_BLUE,
19657
19844
  padding: 8,
19658
19845
  borderRadius: 12,
19659
19846
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -19661,9 +19848,9 @@ function VideoToolbar({
19661
19848
  opacity: pending ? 0.6 : 1
19662
19849
  },
19663
19850
  children: [
19664
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
19665
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Pill2, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
19666
- error && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
19851
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
19852
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Pill, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
19853
+ error && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
19667
19854
  ]
19668
19855
  }
19669
19856
  );
@@ -19783,8 +19970,8 @@ function AiVideoOverlay({
19783
19970
  const activeBlockRect = useLiveBlockRect(active);
19784
19971
  const TOOLBAR_APPROX_H = 90;
19785
19972
  const toolbarState = activeBlockRect && activeBlockRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
19786
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19787
- hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19973
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
19974
+ hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19788
19975
  "button",
19789
19976
  {
19790
19977
  type: "button",
@@ -19812,15 +19999,15 @@ function AiVideoOverlay({
19812
19999
  fontSize: 14,
19813
20000
  fontWeight: 700
19814
20001
  },
19815
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react10.Star, { size: 14, style: { pointerEvents: "none" } })
20002
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react11.Star, { size: 14, style: { pointerEvents: "none" } })
19816
20003
  }
19817
20004
  ),
19818
- activeBlockRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
20005
+ activeBlockRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19819
20006
  ItemInteractionLayer,
19820
20007
  {
19821
20008
  rect: activeBlockRect,
19822
20009
  state: toolbarState,
19823
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
20010
+ toolbar: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19824
20011
  VideoToolbar,
19825
20012
  {
19826
20013
  videoId: videoIdAt(activeEntry.tree, active.path),
@@ -19844,7 +20031,7 @@ function AiVideoOverlay({
19844
20031
 
19845
20032
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
19846
20033
  var import_react13 = require("react");
19847
- var import_lucide_react11 = require("lucide-react");
20034
+ var import_lucide_react12 = require("lucide-react");
19848
20035
 
19849
20036
  // src/lib/ai-carousel-tree.ts
19850
20037
  function isCarouselBlock(node) {
@@ -19895,10 +20082,10 @@ function setCarouselAutoplay(tree, on) {
19895
20082
  }
19896
20083
 
19897
20084
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
19898
- var import_jsx_runtime22 = require("react/jsx-runtime");
20085
+ var import_jsx_runtime23 = require("react/jsx-runtime");
19899
20086
  var PRIMARY6 = "#0885FE";
19900
- var OHW_BLUE3 = `var(--ohw-primary, ${PRIMARY6})`;
19901
- var OHW_BLUE_DARK3 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
20087
+ var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY6})`;
20088
+ var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
19902
20089
  function findCarouselItemElement(sectionId, index) {
19903
20090
  const sectionEl = findByInstanceId(sectionId);
19904
20091
  if (!sectionEl) return null;
@@ -19977,8 +20164,8 @@ function requestAssetPick2(postToParent2) {
19977
20164
  postToParent2({ type: "ow:asset-pick", id });
19978
20165
  });
19979
20166
  }
19980
- function Pill3({ label, onClick, disabled }) {
19981
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20167
+ function Pill2({ label, onClick, disabled }) {
20168
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
19982
20169
  "button",
19983
20170
  {
19984
20171
  type: "button",
@@ -19994,7 +20181,7 @@ function Pill3({ label, onClick, disabled }) {
19994
20181
  border: "none",
19995
20182
  cursor: disabled ? "default" : "pointer",
19996
20183
  background: "#ffffff",
19997
- color: OHW_BLUE_DARK3,
20184
+ color: OHW_BLUE_DARK2,
19998
20185
  opacity: disabled ? 0.4 : 1,
19999
20186
  fontSize: 12,
20000
20187
  lineHeight: "16px",
@@ -20012,7 +20199,7 @@ function TogglePill({
20012
20199
  onToggle,
20013
20200
  disabled
20014
20201
  }) {
20015
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
20202
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20016
20203
  "button",
20017
20204
  {
20018
20205
  type: "button",
@@ -20031,7 +20218,7 @@ function TogglePill({
20031
20218
  border: "none",
20032
20219
  cursor: disabled ? "default" : "pointer",
20033
20220
  background: "#ffffff",
20034
- color: OHW_BLUE_DARK3,
20221
+ color: OHW_BLUE_DARK2,
20035
20222
  opacity: disabled ? 0.4 : 1,
20036
20223
  fontSize: 12,
20037
20224
  lineHeight: "16px",
@@ -20041,7 +20228,7 @@ function TogglePill({
20041
20228
  },
20042
20229
  children: [
20043
20230
  label,
20044
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20231
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20045
20232
  "span",
20046
20233
  {
20047
20234
  "aria-hidden": true,
@@ -20054,10 +20241,10 @@ function TogglePill({
20054
20241
  // Off is a grey track, not a washed-out blue: the pill's own white background already
20055
20242
  // carries the brand colour in the text, and a pale blue track next to it read as
20056
20243
  // "disabled control" rather than "switch that is off".
20057
- background: on ? OHW_BLUE_DARK3 : "#CBD5E1",
20244
+ background: on ? OHW_BLUE_DARK2 : "#CBD5E1",
20058
20245
  transition: "background 120ms ease"
20059
20246
  },
20060
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20247
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20061
20248
  "span",
20062
20249
  {
20063
20250
  style: {
@@ -20086,7 +20273,7 @@ function CarouselSlideToolbar({
20086
20273
  onChangeImage,
20087
20274
  onToggleAutoplay
20088
20275
  }) {
20089
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
20276
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20090
20277
  "div",
20091
20278
  {
20092
20279
  "data-ohw-bridge": "",
@@ -20097,7 +20284,7 @@ function CarouselSlideToolbar({
20097
20284
  flexDirection: "column",
20098
20285
  gap: 6,
20099
20286
  alignItems: "center",
20100
- background: OHW_BLUE3,
20287
+ background: OHW_BLUE2,
20101
20288
  padding: 8,
20102
20289
  borderRadius: 12,
20103
20290
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -20109,11 +20296,11 @@ function CarouselSlideToolbar({
20109
20296
  pointerEvents: pending ? "none" : "auto"
20110
20297
  },
20111
20298
  children: [
20112
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20113
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Pill3, { label: "Change Image", onClick: onChangeImage }),
20114
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
20299
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20300
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Pill2, { label: "Change Image", onClick: onChangeImage }),
20301
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
20115
20302
  ] }),
20116
- error && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20303
+ error && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20117
20304
  ]
20118
20305
  }
20119
20306
  );
@@ -20253,8 +20440,8 @@ function AiFadeCarouselOverlay({
20253
20440
  const activeItemRect = useLiveItemRect2(active);
20254
20441
  const TOOLBAR_APPROX_H = 90;
20255
20442
  const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
20256
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
20257
- hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20443
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
20444
+ hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20258
20445
  "button",
20259
20446
  {
20260
20447
  type: "button",
@@ -20283,10 +20470,10 @@ function AiFadeCarouselOverlay({
20283
20470
  fontSize: 14,
20284
20471
  fontWeight: 700
20285
20472
  },
20286
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react11.Star, { size: 14, style: { pointerEvents: "none" } })
20473
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react12.Star, { size: 14, style: { pointerEvents: "none" } })
20287
20474
  }
20288
20475
  ),
20289
- swapping && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20476
+ swapping && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20290
20477
  "div",
20291
20478
  {
20292
20479
  ref: skeletonRef,
@@ -20304,15 +20491,15 @@ function AiFadeCarouselOverlay({
20304
20491
  zIndex: 2147483646,
20305
20492
  pointerEvents: "none"
20306
20493
  },
20307
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("style", { children: MEDIA_SKELETON_CSS })
20494
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("style", { children: MEDIA_SKELETON_CSS })
20308
20495
  }
20309
20496
  ),
20310
- activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20497
+ activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20311
20498
  ItemInteractionLayer,
20312
20499
  {
20313
20500
  rect: activeItemRect,
20314
20501
  state: toolbarState,
20315
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20502
+ toolbar: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20316
20503
  CarouselSlideToolbar,
20317
20504
  {
20318
20505
  pending,
@@ -20665,113 +20852,6 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
20665
20852
  // src/ui/link-modal/LinkPopover.tsx
20666
20853
  var import_react17 = require("react");
20667
20854
 
20668
- // src/ui/dialog.tsx
20669
- var React9 = __toESM(require("react"), 1);
20670
- var import_radix_ui6 = require("radix-ui");
20671
- var import_lucide_react12 = require("lucide-react");
20672
- var import_jsx_runtime23 = require("react/jsx-runtime");
20673
- function Dialog2({
20674
- ...props
20675
- }) {
20676
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_radix_ui6.Dialog.Root, { "data-slot": "dialog", ...props });
20677
- }
20678
- function DialogPortal({
20679
- ...props
20680
- }) {
20681
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_radix_ui6.Dialog.Portal, { ...props });
20682
- }
20683
- function DialogOverlay({
20684
- className,
20685
- ...props
20686
- }) {
20687
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20688
- import_radix_ui6.Dialog.Overlay,
20689
- {
20690
- "data-slot": "dialog-overlay",
20691
- "data-ohw-link-modal-root": "",
20692
- className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
20693
- ...props
20694
- }
20695
- );
20696
- }
20697
- var DialogContent = React9.forwardRef(
20698
- ({ className, children, showCloseButton = true, container, ...props }, ref) => {
20699
- const positionMode = container ? "absolute" : "fixed";
20700
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(DialogPortal, { container: container ?? void 0, children: [
20701
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
20702
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20703
- import_radix_ui6.Dialog.Content,
20704
- {
20705
- ref,
20706
- "data-slot": "dialog-content",
20707
- className: cn(
20708
- positionMode,
20709
- "left-1/2 top-1/2 z-[2147483647] flex w-full max-w-[448px] -translate-x-1/2 -translate-y-1/2 flex-col overflow-visible",
20710
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
20711
- container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
20712
- className
20713
- ),
20714
- onMouseDown: (e) => e.stopPropagation(),
20715
- ...props,
20716
- children: [
20717
- children,
20718
- showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20719
- import_radix_ui6.Dialog.Close,
20720
- {
20721
- type: "button",
20722
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
20723
- "aria-label": "Close",
20724
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react12.X, { size: 16, "aria-hidden": true })
20725
- }
20726
- ) : null
20727
- ]
20728
- }
20729
- )
20730
- ] });
20731
- }
20732
- );
20733
- DialogContent.displayName = import_radix_ui6.Dialog.Content.displayName;
20734
- function DialogHeader({
20735
- className,
20736
- ...props
20737
- }) {
20738
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
20739
- }
20740
- function DialogFooter({
20741
- className,
20742
- ...props
20743
- }) {
20744
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20745
- "div",
20746
- {
20747
- className: cn("flex items-center justify-end gap-2", className),
20748
- ...props
20749
- }
20750
- );
20751
- }
20752
- var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20753
- import_radix_ui6.Dialog.Title,
20754
- {
20755
- ref,
20756
- className: cn(
20757
- "text-lg font-semibold leading-none tracking-tight text-card-foreground",
20758
- className
20759
- ),
20760
- ...props
20761
- }
20762
- ));
20763
- DialogTitle.displayName = import_radix_ui6.Dialog.Title.displayName;
20764
- var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20765
- import_radix_ui6.Dialog.Description,
20766
- {
20767
- ref,
20768
- className: cn("text-sm text-muted-foreground", className),
20769
- ...props
20770
- }
20771
- ));
20772
- DialogDescription.displayName = import_radix_ui6.Dialog.Description.displayName;
20773
- var DialogClose = import_radix_ui6.Dialog.Close;
20774
-
20775
20855
  // src/ui/link-modal/LinkEditorPanel.tsx
20776
20856
  var import_lucide_react16 = require("lucide-react");
20777
20857
 
@@ -21875,6 +21955,7 @@ function shouldUseDevFixtures() {
21875
21955
  function getLogoElement(el) {
21876
21956
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
21877
21957
  if (marked) return marked;
21958
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
21878
21959
  const root = el.closest("nav, [data-ohw-nav-root], footer");
21879
21960
  if (!root) return null;
21880
21961
  const anchor = el.closest("a");
@@ -24455,7 +24536,7 @@ function getNavigationSelectionParent(el) {
24455
24536
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
24456
24537
  return resolveFooterLinksContainerFrom(el);
24457
24538
  }
24458
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
24539
+ if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isFooterLinksContainer(el) || isInferredFooterGroup2(el)) {
24459
24540
  return getNavigationRoot(el);
24460
24541
  }
24461
24542
  return null;
@@ -24677,7 +24758,6 @@ var ICONS = {
24677
24758
  insertUnorderedList: '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>',
24678
24759
  insertOrderedList: '<line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/>'
24679
24760
  };
24680
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
24681
24761
  var SELECTION_CHROME_GAP2 = 4;
24682
24762
  var TOOLBAR_STROKE_GAP2 = 4;
24683
24763
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -24775,7 +24855,7 @@ function applyVisibleViewport(el, parentScroll) {
24775
24855
  el.style.top = `${top}px`;
24776
24856
  el.style.height = `${height}px`;
24777
24857
  }
24778
- var TOOLBAR_EDGE_MARGIN3 = 4;
24858
+ var TOOLBAR_EDGE_MARGIN4 = 4;
24779
24859
  function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
24780
24860
  const isAbove = transform.includes("translateY(-100%)");
24781
24861
  let visualTop = isAbove ? top - approxH : top;
@@ -24802,8 +24882,8 @@ function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
24802
24882
  function clampToolbarLeft(centerX, toolbarWidth) {
24803
24883
  const half = toolbarWidth / 2;
24804
24884
  return Math.max(
24805
- TOOLBAR_EDGE_MARGIN3 + half,
24806
- Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half)
24885
+ TOOLBAR_EDGE_MARGIN4 + half,
24886
+ Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN4 - half)
24807
24887
  );
24808
24888
  }
24809
24889
  function calcToolbarPos(rect, parentScroll, toolbarW = 306) {
@@ -25693,21 +25773,6 @@ function OhhwellsBridge() {
25693
25773
  const [isItemDragging, setIsItemDragging] = (0, import_react21.useState)(false);
25694
25774
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react21.useState)(false);
25695
25775
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
25696
- const [floatingPanel, setFloatingPanel] = (0, import_react21.useState)(null);
25697
- const floatingPanelOpenRef = (0, import_react21.useRef)(false);
25698
- floatingPanelOpenRef.current = floatingPanel !== null;
25699
- const floatingPanelRef = (0, import_react21.useRef)(null);
25700
- floatingPanelRef.current = floatingPanel;
25701
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react21.useState)(null);
25702
- const buttonCollectionDragRef = (0, import_react21.useRef)(null);
25703
- const buttonDropInsertIndexRef = (0, import_react21.useRef)(null);
25704
- const [buttonDropSlots, setButtonDropSlots] = (0, import_react21.useState)([]);
25705
- const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react21.useState)(null);
25706
- const buttonPointerDragRef = (0, import_react21.useRef)(null);
25707
- const [buttonStyleTick, setButtonStyleTick] = (0, import_react21.useState)(0);
25708
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react21.useState)(null);
25709
- const [editorViewport, setEditorViewport] = (0, import_react21.useState)("desktop");
25710
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react21.useState)(null);
25711
25776
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react21.useState)(null);
25712
25777
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react21.useState)(null);
25713
25778
  const footerDragRef = (0, import_react21.useRef)(null);
@@ -25733,6 +25798,21 @@ function OhhwellsBridge() {
25733
25798
  const stylesRef = (0, import_react21.useRef)("");
25734
25799
  const appliedDrawerRef = (0, import_react21.useRef)(null);
25735
25800
  const pendingDeleteUndoRef = (0, import_react21.useRef)(null);
25801
+ const [floatingPanel, setFloatingPanel] = (0, import_react21.useState)(null);
25802
+ const floatingPanelOpenRef = (0, import_react21.useRef)(false);
25803
+ const floatingPanelRef = (0, import_react21.useRef)(null);
25804
+ floatingPanelRef.current = floatingPanel;
25805
+ const setFloatingPanelRef = (0, import_react21.useRef)(setFloatingPanel);
25806
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react21.useState)(null);
25807
+ const buttonCollectionDragRef = (0, import_react21.useRef)(null);
25808
+ const buttonDropInsertIndexRef = (0, import_react21.useRef)(null);
25809
+ const [buttonDropSlots, setButtonDropSlots] = (0, import_react21.useState)([]);
25810
+ const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react21.useState)(null);
25811
+ const buttonPointerDragRef = (0, import_react21.useRef)(null);
25812
+ const [buttonStyleTick, setButtonStyleTick] = (0, import_react21.useState)(0);
25813
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react21.useState)(null);
25814
+ const [editorViewport, setEditorViewport] = (0, import_react21.useState)("desktop");
25815
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react21.useState)(null);
25736
25816
  const [sitePages, setSitePages] = (0, import_react21.useState)([]);
25737
25817
  const [sectionsByPath, setSectionsByPath] = (0, import_react21.useState)({});
25738
25818
  const sectionsPrefetchGenRef = (0, import_react21.useRef)(0);
@@ -25741,7 +25821,18 @@ function OhhwellsBridge() {
25741
25821
  const linkPopoverOpenRef = (0, import_react21.useRef)(false);
25742
25822
  const linkPopoverGraceUntilRef = (0, import_react21.useRef)(0);
25743
25823
  setLinkPopoverRef.current = setLinkPopover;
25824
+ setFloatingPanelRef.current = setFloatingPanel;
25744
25825
  linkPopoverSessionRef.current = linkPopover;
25826
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
25827
+ (0, import_react21.useEffect)(() => {
25828
+ const syncViewport = () => {
25829
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
25830
+ setEditorViewport((prev) => prev === next ? prev : next);
25831
+ };
25832
+ syncViewport();
25833
+ window.addEventListener("resize", syncViewport);
25834
+ return () => window.removeEventListener("resize", syncViewport);
25835
+ }, []);
25745
25836
  (0, import_react21.useEffect)(() => {
25746
25837
  setSectionOrderPersistHandler((entries) => {
25747
25838
  const currentPath = window.location.pathname;
@@ -27436,6 +27527,7 @@ function OhhwellsBridge() {
27436
27527
  applyAiFooterFromContent(content);
27437
27528
  if (typeof content[STYLE_STORE_KEY] === "string") {
27438
27529
  stylesRef.current = content[STYLE_STORE_KEY];
27530
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
27439
27531
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
27440
27532
  }
27441
27533
  applyBrandChrome(content);
@@ -27446,12 +27538,12 @@ function OhhwellsBridge() {
27446
27538
  if (key === "__ohw_sections") continue;
27447
27539
  if (key === AI_SECTIONS_KEY) continue;
27448
27540
  if (key === AI_FOOTER_KEY) continue;
27541
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
27542
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
27449
27543
  if (key === BRAND_KIT_KEY) continue;
27450
27544
  if (key === STYLE_STORE_KEY) continue;
27451
27545
  if (BRAND_CHROME_KEYS.has(key)) continue;
27452
- if (key === LOGO_PLACEHOLDER_KEY) continue;
27453
27546
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
27454
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
27455
27547
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
27456
27548
  continue;
27457
27549
  }
@@ -27666,17 +27758,18 @@ function OhhwellsBridge() {
27666
27758
  }
27667
27759
  applyAiFooterFromContent(content);
27668
27760
  if (typeof content[STYLE_STORE_KEY] === "string") {
27761
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
27669
27762
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
27670
27763
  }
27671
27764
  for (const [key, val] of Object.entries(content)) {
27672
27765
  if (key === "__ohw_sections") continue;
27673
27766
  if (key === AI_SECTIONS_KEY) continue;
27674
27767
  if (key === AI_FOOTER_KEY) continue;
27768
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
27769
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
27675
27770
  if (key === BRAND_KIT_KEY) continue;
27676
27771
  if (key === STYLE_STORE_KEY) continue;
27677
27772
  if (BRAND_CHROME_KEYS.has(key)) continue;
27678
- if (key === LOGO_PLACEHOLDER_KEY) continue;
27679
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
27680
27773
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
27681
27774
  continue;
27682
27775
  }
@@ -27933,26 +28026,11 @@ function OhhwellsBridge() {
27933
28026
  const t2 = setTimeout(measure, 500);
27934
28027
  const ro = new ResizeObserver(schedule);
27935
28028
  ro.observe(document.body);
27936
- let lastWidth = window.innerWidth;
27937
- let resizeTimers = [];
27938
- const clearResizeTimers = () => {
27939
- resizeTimers.forEach(clearTimeout);
27940
- resizeTimers = [];
27941
- };
27942
- const handleResize = () => {
27943
- if (window.innerWidth === lastWidth) return;
27944
- lastWidth = window.innerWidth;
27945
- clearResizeTimers();
27946
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
27947
- };
27948
- window.addEventListener("resize", handleResize);
27949
28029
  return () => {
27950
28030
  clearTimeout(t1);
27951
28031
  clearTimeout(t2);
27952
28032
  if (raf != null) cancelAnimationFrame(raf);
27953
28033
  ro.disconnect();
27954
- clearResizeTimers();
27955
- window.removeEventListener("resize", handleResize);
27956
28034
  };
27957
28035
  }, [pathname, isEditMode, postToParent2]);
27958
28036
  (0, import_react21.useEffect)(() => {
@@ -28251,6 +28329,13 @@ function OhhwellsBridge() {
28251
28329
  if (target.closest("[data-ohw-state-toggle]")) return;
28252
28330
  if (target.closest("[data-ohw-max-badge]")) return;
28253
28331
  if (isInsideLinkEditor(target)) return;
28332
+ if (isInsideFloatingPanel(target)) return;
28333
+ if (target.closest("[data-ohw-form-toolbar]")) return;
28334
+ if (target.closest(
28335
+ '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
28336
+ )) {
28337
+ return;
28338
+ }
28254
28339
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
28255
28340
  clearMediaSelectionRef.current();
28256
28341
  }
@@ -28274,13 +28359,6 @@ function OhhwellsBridge() {
28274
28359
  return;
28275
28360
  }
28276
28361
  }
28277
- if (isInsideFloatingPanel(target)) return;
28278
- if (target.closest("[data-ohw-form-toolbar]")) return;
28279
- if (target.closest(
28280
- '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
28281
- )) {
28282
- return;
28283
- }
28284
28362
  {
28285
28363
  const formEl = getFormElement(target);
28286
28364
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -28434,9 +28512,13 @@ function OhhwellsBridge() {
28434
28512
  if (isIconEditable(editable) && !getSocialItem(editable)) {
28435
28513
  e.preventDefault();
28436
28514
  e.stopPropagation();
28437
- selectIconElementRef.current(editable);
28438
28515
  const aiIcon = editable.hasAttribute("data-ai-icon");
28439
28516
  const featureIcon = aiIcon && /\.features\.\d+\.icon$/.test(editable.dataset.ohwKey ?? "");
28517
+ if (aiIcon) {
28518
+ aiSectionApiRef.current?.selectFromElement(editable);
28519
+ } else {
28520
+ selectIconElementRef.current(editable);
28521
+ }
28440
28522
  postToParentRef.current({
28441
28523
  type: "ow:icon-pick",
28442
28524
  key: editable.dataset.ohwKey ?? "",
@@ -28664,6 +28746,9 @@ function OhhwellsBridge() {
28664
28746
  setHoveredItemRect(null);
28665
28747
  hoveredNavContainerRef.current = null;
28666
28748
  setHoveredNavContainerRect(null);
28749
+ siblingHintElRef.current = null;
28750
+ setSiblingHintRect(null);
28751
+ setSiblingHintRects([]);
28667
28752
  return;
28668
28753
  }
28669
28754
  {
@@ -28784,7 +28869,6 @@ function OhhwellsBridge() {
28784
28869
  hoveredNavContainerRef.current = null;
28785
28870
  setHoveredNavContainerRect(null);
28786
28871
  hoveredItemElRef.current = editable;
28787
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
28788
28872
  }
28789
28873
  }
28790
28874
  }
@@ -29494,7 +29578,11 @@ function OhhwellsBridge() {
29494
29578
  };
29495
29579
  const handleMouseMove = (e) => {
29496
29580
  const { clientX, clientY } = e;
29497
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
29581
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
29582
+ dismissImageHover();
29583
+ clearImageHover();
29584
+ return;
29585
+ }
29498
29586
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) {
29499
29587
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
29500
29588
  formHoverElRef.current = null;
@@ -29523,7 +29611,12 @@ function OhhwellsBridge() {
29523
29611
  setHoveredItemRect(null);
29524
29612
  hoveredNavContainerRef.current = null;
29525
29613
  setHoveredNavContainerRect(null);
29614
+ siblingHintElRef.current = null;
29615
+ setSiblingHintRect(null);
29616
+ setSiblingHintRects([]);
29526
29617
  dismissImageHover();
29618
+ clearImageHover();
29619
+ setSectionGap(null);
29527
29620
  return;
29528
29621
  }
29529
29622
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -29535,7 +29628,11 @@ function OhhwellsBridge() {
29535
29628
  if (e.data?.type !== "ow:pointer-sync") return;
29536
29629
  const { clientX, clientY } = e.data;
29537
29630
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
29538
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
29631
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
29632
+ dismissImageHover();
29633
+ clearImageHover();
29634
+ return;
29635
+ }
29539
29636
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) return;
29540
29637
  if (probeSocialsRowAt(clientX, clientY)) return;
29541
29638
  probeSectionGapAt(clientX, clientY);
@@ -29915,6 +30012,7 @@ function OhhwellsBridge() {
29915
30012
  applyAiFooterFromContent(content);
29916
30013
  if (typeof content[STYLE_STORE_KEY] === "string") {
29917
30014
  stylesRef.current = content[STYLE_STORE_KEY];
30015
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
29918
30016
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
29919
30017
  }
29920
30018
  applyBrandChrome(content);
@@ -29929,12 +30027,12 @@ function OhhwellsBridge() {
29929
30027
  }
29930
30028
  if (key === AI_SECTIONS_KEY) continue;
29931
30029
  if (key === AI_FOOTER_KEY) continue;
30030
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
30031
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
29932
30032
  if (key === BRAND_KIT_KEY) continue;
29933
30033
  if (key === STYLE_STORE_KEY) continue;
29934
30034
  if (BRAND_CHROME_KEYS.has(key)) continue;
29935
- if (key === LOGO_PLACEHOLDER_KEY) continue;
29936
30035
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
29937
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
29938
30036
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
29939
30037
  continue;
29940
30038
  }
@@ -30309,6 +30407,44 @@ function OhhwellsBridge() {
30309
30407
  });
30310
30408
  };
30311
30409
  window.addEventListener("message", handleAiSetStyles);
30410
+ const handleAiSetButtonStyle = (e) => {
30411
+ if (e.data?.type !== "ow:ai-set-button-style") return;
30412
+ const nodeKey = typeof e.data.hrefKey === "string" ? e.data.hrefKey : "";
30413
+ const keyed = nodeKey ? document.querySelector(
30414
+ `[data-ohw-href-key="${CSS.escape(nodeKey)}"], [data-ohw-key="${CSS.escape(nodeKey)}"]`
30415
+ ) : null;
30416
+ const hrefKey = buttonId(keyed);
30417
+ const el = hrefKey ? document.querySelector(`[data-ohw-href-key="${CSS.escape(hrefKey)}"]`) : null;
30418
+ const previous = editContentRef.current[BUTTON_ASSIGNMENTS_KEY] ?? "";
30419
+ if (!el || !hrefKey || !isCtaButton(el)) {
30420
+ postToParentRef.current({ type: "ow:ai-button-style-applied", ok: false, previous, value: previous });
30421
+ return;
30422
+ }
30423
+ const tokens = tokensFromButton(el);
30424
+ const current = assignmentFor(el, editContentRef.current, tokens);
30425
+ const patch = e.data.assignment;
30426
+ const next = {
30427
+ variant: patch?.variant ?? current.variant,
30428
+ size: patch?.size ?? current.size
30429
+ };
30430
+ const value = typeof e.data.assignments === "string" ? e.data.assignments : JSON.stringify(withAssignment(editContentRef.current, hrefKey, next));
30431
+ editContentRef.current = { ...editContentRef.current, [BUTTON_ASSIGNMENTS_KEY]: value };
30432
+ applyButtonStyle(el, next, tokens);
30433
+ postToParentRef.current({
30434
+ type: "ow:change",
30435
+ nodes: [{ key: BUTTON_ASSIGNMENTS_KEY, text: value }]
30436
+ });
30437
+ postToParentRef.current({
30438
+ type: "ow:ai-button-style-applied",
30439
+ ok: true,
30440
+ // The resolved id, which may differ from the key the editor sent — undo must target this one.
30441
+ hrefKey,
30442
+ previous,
30443
+ previousAssignment: current,
30444
+ value
30445
+ });
30446
+ };
30447
+ window.addEventListener("message", handleAiSetButtonStyle);
30312
30448
  const handleGetBrand = (e) => {
30313
30449
  if (e.data?.type !== "ow:get-brand") return;
30314
30450
  const template = deriveTemplateBrand();
@@ -30532,6 +30668,12 @@ function OhhwellsBridge() {
30532
30668
  closeLinkPopoverRef.current();
30533
30669
  return;
30534
30670
  }
30671
+ if (floatingPanelOpenRef.current) {
30672
+ setFloatingPanelRef.current(null);
30673
+ deselectRef.current();
30674
+ deactivateRef.current();
30675
+ return;
30676
+ }
30535
30677
  deselectRef.current();
30536
30678
  deactivateRef.current();
30537
30679
  clearMediaSelectionRef.current();
@@ -31342,6 +31484,7 @@ function OhhwellsBridge() {
31342
31484
  window.removeEventListener("message", handleAiSetSections);
31343
31485
  window.removeEventListener("message", handleAiSetBrand);
31344
31486
  window.removeEventListener("message", handleAiSetStyles);
31487
+ window.removeEventListener("message", handleAiSetButtonStyle);
31345
31488
  window.removeEventListener("message", handleCollectItems);
31346
31489
  window.removeEventListener("message", handleAiApplyItems);
31347
31490
  window.removeEventListener("message", handleGetBrand);
@@ -31353,10 +31496,10 @@ function OhhwellsBridge() {
31353
31496
  window.removeEventListener("message", handleDeleteSection);
31354
31497
  window.removeEventListener("message", handleDuplicateSection);
31355
31498
  window.removeEventListener("message", handleDeactivate);
31356
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
31357
31499
  window.removeEventListener("message", handleToastAction);
31358
31500
  window.removeEventListener("message", handleFormCount);
31359
31501
  window.removeEventListener("message", handleUiEscape);
31502
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
31360
31503
  autoSaveTimers.current.forEach(clearTimeout);
31361
31504
  autoSaveTimers.current.clear();
31362
31505
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -31650,7 +31793,7 @@ function OhhwellsBridge() {
31650
31793
  postToParent2({
31651
31794
  type: "ow:ready",
31652
31795
  version: "1",
31653
- bridgeVersion: "0.1.146",
31796
+ bridgeVersion: "0.1.149",
31654
31797
  path: pathname,
31655
31798
  nodes: collectEditableNodes(editContentRef.current),
31656
31799
  sections