@ohhwells/bridge 0.1.148 → 0.1.149-next.402

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
@@ -1726,7 +1726,8 @@ function labelElement2(el) {
1726
1726
  return el.querySelector('[data-ohw-editable="text"], [data-ohw-editable="plain"]') ?? el.querySelector("[data-ohw-key]");
1727
1727
  }
1728
1728
  function buttonLabel(el) {
1729
- return (labelElement2(el)?.textContent ?? el.textContent ?? "").trim();
1729
+ const label = labelElement2(el);
1730
+ return (label ? label.innerHTML : el.textContent ?? "").trim();
1730
1731
  }
1731
1732
  function buttonHref(el) {
1732
1733
  return el.getAttribute("href") ?? "";
@@ -1865,7 +1866,7 @@ function duplicateButton(source, content) {
1865
1866
  stampVariantClass(copy, copyAssignment.variant);
1866
1867
  applyButtonStyle(copy, copyAssignment, paintedTokensOf(source) ?? tokens);
1867
1868
  const copyLabelEl = labelElement2(copy);
1868
- if (copyLabelEl) copyLabelEl.textContent = label;
1869
+ if (copyLabelEl) copyLabelEl.innerHTML = label;
1869
1870
  let collection = buttonCollectionOf(source);
1870
1871
  const collections = readCollections(content);
1871
1872
  let collectionId = collection?.getAttribute(COLLECTION_ATTR) ?? "";
@@ -2191,7 +2192,7 @@ function applySnapshotContent(el, snapshot, content) {
2191
2192
  const label = labelElement2(el);
2192
2193
  if (label) {
2193
2194
  if (label.getAttribute("data-ohw-key") !== labelKey) label.setAttribute("data-ohw-key", labelKey);
2194
- if (label.textContent !== text) label.textContent = text;
2195
+ if (label.innerHTML !== text) label.innerHTML = text;
2195
2196
  }
2196
2197
  }
2197
2198
  function reconcileButtonCollections(doc, content) {
@@ -4276,7 +4277,7 @@ function buildNavLink(index, href, label, template) {
4276
4277
  const span = document.createElement("span");
4277
4278
  span.setAttribute("data-ohw-editable", "text");
4278
4279
  span.setAttribute("data-ohw-key", `nav-${index}-label`);
4279
- span.textContent = label;
4280
+ span.innerHTML = label;
4280
4281
  anchor.appendChild(span);
4281
4282
  return anchor;
4282
4283
  }
@@ -6636,6 +6637,21 @@ function pricingCardFeatureCount(tree, index) {
6636
6637
  const target = findPricingCollection(tree)?.children?.[index];
6637
6638
  return target ? pricingFeatureList(target).length : 0;
6638
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
+ }
6639
6655
  function addPricingCardFeature(tree, index, text) {
6640
6656
  return withPricingCollection(tree, (collection) => {
6641
6657
  const children = collection.children;
@@ -6644,7 +6660,7 @@ function addPricingCardFeature(tree, index, text) {
6644
6660
  const features = pricingFeatureList(target);
6645
6661
  if (features.length >= PRICING_FEATURES_MAX) return false;
6646
6662
  target.slots = target.slots ?? {};
6647
- target.slots.features = [...features, { text }];
6663
+ target.slots.features = [...features, { text, ...prevailingFeatureIcon(features) }];
6648
6664
  return true;
6649
6665
  });
6650
6666
  }
@@ -7827,7 +7843,7 @@ var AI_MOBILE_CSS = [
7827
7843
  // page's first section. Top only (bottom needs no nav clearance); ~96px approximates the
7828
7844
  // template nav's own height (measured ~94px) — there's no shared `--ohw-header-h` var to read
7829
7845
  // an exact figure from (no live measurement exists), so this is a safe approximation, not a
7830
- // pixel-exact fit. Horizontal spacing already comes from `data-ai-container`.
7846
+ // pixel-exact fit. Horizontal spacing already comes from `data-ai-section-inner`.
7831
7847
  '[data-ai-overlay-clearance] [data-ai-overlap-layer="overlay"]{padding-top:96px}',
7832
7848
  // A full-bleed carousel's height is pure arithmetic: it spans the whole window, so 16/9 turns
7833
7849
  // window width straight into height. Measured at 16/9 it came out ~90% of the viewport at EVERY
@@ -7843,8 +7859,10 @@ var AI_MOBILE_CSS = [
7843
7859
  "[data-ai-section]{overflow-x:hidden}",
7844
7860
  // `:not([data-ai-full-bleed])`: this `!important` gutter beat the inline `padding: 0` that a
7845
7861
  // full-bleed section's container sets, so a carousel/media hero that is edge-to-edge on desktop
7846
- // still got a 20px band of section background down both sides on a phone.
7847
- "[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}",
7848
7866
  "[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
7849
7867
  "[data-ai-cell]{width:100%;min-width:0}",
7850
7868
  // The rule above turns every row into a COLUMN flex container on phones, which silently breaks
@@ -8049,6 +8067,31 @@ function pricingCardEmphasis(featured, ctx, hasBg) {
8049
8067
  function textAttrs(ctx, path) {
8050
8068
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
8051
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");
8052
8095
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
8053
8096
  function MediaBox({
8054
8097
  refValue,
@@ -8103,7 +8146,7 @@ function MediaBox({
8103
8146
  position: fill ? "absolute" : void 0,
8104
8147
  inset: fill ? 0 : void 0,
8105
8148
  width: fill ? "100%" : fixedSize ? "auto" : "100%",
8106
- height: fill ? "100%" : void 0,
8149
+ height: fill ? "100%" : fixedSize ? height : void 0,
8107
8150
  aspectRatio: fill ? void 0 : aspectRatio,
8108
8151
  minHeight: fill || fixedSize ? void 0 : height ?? (aspectRatio ? void 0 : 180),
8109
8152
  borderRadius: radius,
@@ -9599,7 +9642,7 @@ function CollectionBlock({ node, ctx, path }) {
9599
9642
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
9600
9643
  "div",
9601
9644
  {
9602
- "data-ai-grid": "",
9645
+ "data-ai-grid": String(itemsPerRow),
9603
9646
  "data-ohw-pricing-collection": isPricing ? "" : void 0,
9604
9647
  style: {
9605
9648
  display: "grid",
@@ -9901,7 +9944,7 @@ function renderNode(node, ctx, path, ambientAlign) {
9901
9944
  },
9902
9945
  children: [
9903
9946
  i > 0 && ctx.heroOverlapFullBleed ? (
9904
- // 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
9905
9948
  // layer (this group's i===0 sibling) can span edge-to-edge — reintroduced here,
9906
9949
  // scoped to just the overlay text, so a heading/copy/CTA stack doesn't stretch
9907
9950
  // the full section width instead of reading at the usual content width.
@@ -10320,6 +10363,7 @@ function AiTreeRenderer({
10320
10363
  ...fillHeight ? { "data-ai-fill-height": "" } : {},
10321
10364
  ...needsOverlayClearance ? { "data-ai-overlay-clearance": "" } : {},
10322
10365
  ...bgAttrs,
10366
+ "data-ai-responsive": "",
10323
10367
  style: {
10324
10368
  position: "relative",
10325
10369
  padding: `${padCss} 0`,
@@ -10333,12 +10377,13 @@ function AiTreeRenderer({
10333
10377
  ...fillHeight ? { display: "grid" } : {}
10334
10378
  },
10335
10379
  children: [
10336
- 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 }),
10337
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})` } }),
10338
10383
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
10339
10384
  "div",
10340
10385
  {
10341
- "data-ai-container": "",
10386
+ "data-ai-section-inner": "",
10342
10387
  ...fullBleedMedia ? { "data-ai-full-bleed": "" } : {},
10343
10388
  style: {
10344
10389
  position: "relative",
@@ -10355,7 +10400,7 @@ function AiTreeRenderer({
10355
10400
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
10356
10401
  "div",
10357
10402
  {
10358
- "data-ai-row": "",
10403
+ "data-ai-columns": "",
10359
10404
  style: {
10360
10405
  display: "grid",
10361
10406
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -10380,7 +10425,12 @@ function AiTreeRenderer({
10380
10425
  // the full row height instead of clumping at the top.
10381
10426
  ...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
10382
10427
  },
10383
- 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
+ )
10384
10434
  },
10385
10435
  b
10386
10436
  ))
@@ -11012,9 +11062,10 @@ function applyAiSectionsToDom(state, options) {
11012
11062
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
11013
11063
  continue;
11014
11064
  }
11015
- const isNewMount = !existing || !existing.container.isConnected;
11016
11065
  let container = existing?.container;
11066
+ let isNewMount = false;
11017
11067
  if (!container || !container.isConnected) {
11068
+ isNewMount = true;
11018
11069
  existing?.root.unmount();
11019
11070
  container?.remove();
11020
11071
  container = document.createElement("div");
@@ -17456,6 +17507,7 @@ function MediaOverlay({
17456
17507
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
17457
17508
  );
17458
17509
  }, [isVideo]);
17510
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
17459
17511
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
17460
17512
  const box = {
17461
17513
  position: "fixed",
@@ -17554,11 +17606,10 @@ function MediaOverlay({
17554
17606
  className: "flex items-center justify-center cursor-pointer",
17555
17607
  style: {
17556
17608
  ...box,
17557
- // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
17558
- // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
17559
- // in-document, pointer-events does it natively. The button below opts back in, so
17560
- // Replace still works.
17561
- 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",
17562
17613
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
17563
17614
  // than hovered. Hover keeps the existing tinted preview.
17564
17615
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -17585,17 +17636,17 @@ function MediaOverlay({
17585
17636
  },
17586
17637
  children: [
17587
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 }),
17588
- isVideo ? "Replace video" : "Replace image"
17639
+ replaceLabel
17589
17640
  ]
17590
17641
  }
17591
17642
  ),
17592
- replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
17643
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
17593
17644
  Button,
17594
17645
  {
17595
17646
  "data-ohw-media-overlay": "",
17596
17647
  variant: "outline",
17597
17648
  size: "sm",
17598
- "aria-label": isVideo ? "Replace video" : "Replace image",
17649
+ "aria-label": replaceLabel,
17599
17650
  className: "gap-1.5 cursor-pointer hover:bg-background",
17600
17651
  style: {
17601
17652
  ...OVERLAY_BUTTON_STYLE,
@@ -17618,7 +17669,7 @@ function MediaOverlay({
17618
17669
  },
17619
17670
  children: [
17620
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 }),
17621
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
17672
+ replaceMode === "full" ? replaceLabel : null
17622
17673
  ]
17623
17674
  }
17624
17675
  )
@@ -18128,7 +18179,7 @@ function AiSectionOverlay({
18128
18179
 
18129
18180
  // src/ui/ai-section/AiMediaStripOverlay.tsx
18130
18181
  var import_react10 = require("react");
18131
- var import_lucide_react8 = require("lucide-react");
18182
+ var import_lucide_react9 = require("lucide-react");
18132
18183
 
18133
18184
  // src/lib/ai-media-strip-tree.ts
18134
18185
  var MEDIA_STRIP_ITEM_MIN = 1;
@@ -18242,14 +18293,6 @@ function setMediaStripItemAsset(tree, index, assetId) {
18242
18293
  return true;
18243
18294
  });
18244
18295
  }
18245
- function setMediaStripItemAlt(tree, index, alt) {
18246
- return withMediaStrip(tree, (block) => {
18247
- const items = block.items;
18248
- if (index < 0 || index >= items.length) return false;
18249
- items[index] = { ...items[index], alt };
18250
- return true;
18251
- });
18252
- }
18253
18296
  function setMediaStripItemLink(tree, index, link) {
18254
18297
  return withMediaStrip(tree, (block) => {
18255
18298
  const items = block.items;
@@ -18267,11 +18310,117 @@ function setMediaStripItemLink(tree, index, link) {
18267
18310
  });
18268
18311
  }
18269
18312
 
18270
- // 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");
18271
18317
  var import_jsx_runtime19 = require("react/jsx-runtime");
18272
- var PRIMARY3 = "#0885FE";
18273
- var OHW_BLUE = `var(--ohw-primary, ${PRIMARY3})`;
18274
- 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]";
18275
18424
  function findMediaStripItemElement(sectionId, index) {
18276
18425
  const sectionEl = findByInstanceId(sectionId);
18277
18426
  if (!sectionEl) return null;
@@ -18281,14 +18430,35 @@ function findMediaStripItemElement(sectionId, index) {
18281
18430
  }
18282
18431
  return null;
18283
18432
  }
18284
- function readRect2(ref) {
18285
- 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) {
18286
18449
  if (!el) return null;
18287
18450
  const r2 = el.getBoundingClientRect();
18288
18451
  if (r2.width <= 0 || r2.height <= 0) return null;
18289
18452
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
18290
18453
  }
18291
- 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) {
18292
18462
  const [rect, setRect] = (0, import_react10.useState)(null);
18293
18463
  (0, import_react10.useEffect)(() => {
18294
18464
  if (!ref) {
@@ -18296,7 +18466,7 @@ function useLiveItemRect(ref) {
18296
18466
  return;
18297
18467
  }
18298
18468
  const update = () => {
18299
- const next = readRect2(ref);
18469
+ const next = strip ? readStripRect(ref) : readRect2(ref);
18300
18470
  setRect(
18301
18471
  (prev) => prev && next && prev.top === next.top && prev.left === next.left && prev.width === next.width && prev.height === next.height ? prev : next
18302
18472
  );
@@ -18311,9 +18481,49 @@ function useLiveItemRect(ref) {
18311
18481
  window.removeEventListener("resize", update);
18312
18482
  clearInterval(interval);
18313
18483
  };
18314
- }, [ref?.sectionId, ref?.index]);
18484
+ }, [ref?.sectionId, ref?.index, strip]);
18315
18485
  return rect;
18316
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
+ }
18317
18527
  function requestValidateTree(postToParent2, tree) {
18318
18528
  return new Promise((resolve) => {
18319
18529
  const id = `ai-validate-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -18350,221 +18560,177 @@ function requestAssetPick(postToParent2) {
18350
18560
  postToParent2({ type: "ow:asset-pick", id });
18351
18561
  });
18352
18562
  }
18353
- function Pill({
18563
+ function ToolbarAction({
18354
18564
  label,
18355
18565
  onClick,
18356
- disabled,
18357
- active
18566
+ disabled = false,
18567
+ active = false,
18568
+ children
18358
18569
  }) {
18359
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
18360
- "button",
18361
- {
18362
- type: "button",
18363
- onClick,
18364
- disabled,
18365
- style: {
18366
- height: 28,
18367
- padding: "0 10px",
18368
- display: "inline-flex",
18369
- alignItems: "center",
18370
- gap: 4,
18371
- borderRadius: 999,
18372
- border: "none",
18373
- cursor: disabled ? "default" : "pointer",
18374
- background: active ? OHW_BLUE_DARK : "#ffffff",
18375
- color: active ? "#ffffff" : OHW_BLUE_DARK,
18376
- opacity: disabled ? 0.4 : 1,
18377
- fontSize: 12,
18378
- lineHeight: "16px",
18379
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18380
- whiteSpace: "nowrap",
18381
- flex: "0 0 auto"
18382
- },
18383
- children: label
18384
- }
18385
- );
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
+ ] });
18386
18575
  }
18387
- function AltInput({ value, onCommit }) {
18388
- const [draft, setDraft] = (0, import_react10.useState)(value);
18389
- (0, import_react10.useEffect)(() => setDraft(value), [value]);
18390
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
18391
- "div",
18392
- {
18393
- style: {
18394
- display: "flex",
18395
- alignItems: "center",
18396
- gap: 6,
18397
- height: 28,
18398
- padding: "0 12px",
18399
- borderRadius: 999,
18400
- background: "#ffffff",
18401
- width: "100%",
18402
- maxWidth: 260,
18403
- boxSizing: "border-box"
18404
- },
18405
- children: [
18406
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
18407
- "span",
18408
- {
18409
- style: {
18410
- flex: "0 0 auto",
18411
- color: OHW_BLUE_DARK,
18412
- fontSize: 10,
18413
- fontWeight: 700,
18414
- textTransform: "uppercase",
18415
- letterSpacing: 0.4
18416
- },
18417
- children: "Alt"
18418
- }
18419
- ),
18420
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
18421
- "input",
18422
- {
18423
- type: "text",
18424
- value: draft,
18425
- onChange: (e) => setDraft(e.target.value),
18426
- onBlur: () => {
18427
- if (draft !== value) onCommit(draft);
18428
- },
18429
- onKeyDown: (e) => {
18430
- if (e.key === "Enter") e.currentTarget.blur();
18431
- if (e.key === "Escape") {
18432
- setDraft(value);
18433
- e.currentTarget.blur();
18434
- }
18435
- },
18436
- placeholder: "Describe this image",
18437
- "aria-label": "Image alt text",
18438
- style: {
18439
- flex: 1,
18440
- minWidth: 0,
18441
- border: "none",
18442
- outline: "none",
18443
- background: "transparent",
18444
- fontSize: 12,
18445
- lineHeight: "16px",
18446
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18447
- color: "#1f2937"
18448
- }
18449
- }
18450
- )
18451
- ]
18452
- }
18453
- );
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" });
18454
18578
  }
18455
- var VARIANTS = [
18456
- { value: "logos", label: "Logos" },
18457
- { value: "reel", label: "Reel" }
18458
- ];
18459
- var ALIGNS = [
18460
- { value: "left", label: "Left" },
18461
- { value: "center", label: "Center" },
18462
- { value: "right", label: "Right" }
18463
- ];
18464
- var HOVERS = [
18465
- { value: "none", label: "None" },
18466
- { value: "lift", label: "Lift" },
18467
- { value: "zoom", label: "Zoom" },
18468
- { value: "fade", label: "Fade" }
18469
- ];
18470
- var TREATMENTS = [
18471
- { value: "color", label: "Color" },
18472
- { value: "grayscale", label: "Grayscale" }
18473
- ];
18474
- var HTTP_URL_RE = /^https?:\/\/\S+$/i;
18475
- function LinkInput({ value, onCommit }) {
18476
- const [draft, setDraft] = (0, import_react10.useState)(value);
18477
- const [invalid, setInvalid] = (0, import_react10.useState)(false);
18478
- (0, import_react10.useEffect)(() => {
18479
- setDraft(value);
18480
- setInvalid(false);
18481
- }, [value]);
18482
- const commit = () => {
18483
- const next = draft.trim();
18484
- if (next === value) return setInvalid(false);
18485
- if (next && !HTTP_URL_RE.test(next)) return setInvalid(true);
18486
- setInvalid(false);
18487
- onCommit(next);
18488
- };
18489
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
18490
- "div",
18491
- {
18492
- style: {
18493
- display: "flex",
18494
- alignItems: "center",
18495
- gap: 6,
18496
- height: 28,
18497
- padding: "0 12px",
18498
- borderRadius: 999,
18499
- background: "#ffffff",
18500
- width: "100%",
18501
- maxWidth: 260,
18502
- boxSizing: "border-box",
18503
- boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
18504
- },
18505
- children: [
18506
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
18507
- "span",
18508
- {
18509
- style: {
18510
- flex: "0 0 auto",
18511
- color: OHW_BLUE_DARK,
18512
- fontSize: 10,
18513
- fontWeight: 700,
18514
- textTransform: "uppercase",
18515
- letterSpacing: 0.4
18516
- },
18517
- children: "Link"
18518
- }
18519
- ),
18520
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
18521
- "input",
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,
18522
18605
  {
18523
- type: "text",
18524
- inputMode: "url",
18525
- value: draft,
18526
- onChange: (e) => setDraft(e.target.value),
18527
- onBlur: commit,
18528
- onKeyDown: (e) => {
18529
- if (e.key === "Enter") e.currentTarget.blur();
18530
- if (e.key === "Escape") {
18531
- setDraft(value);
18532
- setInvalid(false);
18533
- e.currentTarget.blur();
18534
- }
18535
- },
18536
- placeholder: "https://example.com",
18537
- "aria-label": "Image link URL",
18538
- "aria-invalid": invalid,
18539
- style: {
18540
- flex: 1,
18541
- minWidth: 0,
18542
- border: "none",
18543
- outline: "none",
18544
- background: "transparent",
18545
- fontSize: 12,
18546
- lineHeight: "16px",
18547
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18548
- color: "#1f2937"
18549
- }
18550
- }
18551
- )
18552
- ]
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
+ ];
18636
+ var HTTP_URL_RE = /^https?:\/\/\S+$/i;
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");
18645
+ const [invalid, setInvalid] = (0, import_react10.useState)(false);
18646
+ const save = () => {
18647
+ const next = draft.trim();
18648
+ if (next && !HTTP_URL_RE.test(next)) {
18649
+ setInvalid(true);
18650
+ return;
18553
18651
  }
18554
- );
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
+ ] }) });
18555
18720
  }
18556
18721
  function MediaStripToolbar({
18557
18722
  entry,
18558
18723
  index,
18559
18724
  pending,
18560
18725
  error,
18726
+ itemEditOn,
18727
+ onToggleItemEdit,
18561
18728
  onSetVariant,
18562
18729
  onSetAlign,
18563
18730
  onSetHover,
18564
18731
  onSetTreatment,
18565
18732
  onResize,
18566
- onSetAlt,
18567
- onSetLink,
18733
+ onOpenLink,
18568
18734
  onAddItem,
18569
18735
  onRemoveItem,
18570
18736
  onChangeImage,
@@ -18573,81 +18739,102 @@ function MediaStripToolbar({
18573
18739
  }) {
18574
18740
  const items = mediaStripItems(entry.tree);
18575
18741
  const itemCount = items.length;
18576
- const currentAlt = items[index]?.alt ?? "";
18577
- const currentHref = items[index]?.href ?? "";
18578
- const currentTarget = items[index]?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
18579
18742
  const slots = findMediaStripSlots(entry.tree);
18580
18743
  const variant = slots?.variant === "reel" ? "reel" : "logos";
18581
18744
  const align = slots?.align === "left" || slots?.align === "right" ? slots.align : "center";
18582
18745
  const hover = slots?.hover === "none" || slots?.hover === "lift" || slots?.hover === "zoom" || slots?.hover === "fade" ? slots.hover : "lift";
18583
18746
  const treatment = slots?.treatment === "color" || slots?.treatment === "grayscale" ? slots.treatment : variant === "logos" ? "grayscale" : "color";
18584
18747
  const sizeValue = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
18585
- const isMobile = window.innerWidth <= 480;
18586
- const rowStyle = isMobile ? {
18587
- display: "flex",
18588
- gap: 6,
18589
- flexWrap: "nowrap",
18590
- justifyContent: "center",
18591
- overflowX: "auto",
18592
- WebkitOverflowScrolling: "touch",
18593
- width: "100%",
18594
- scrollbarWidth: "none"
18595
- } : { display: "flex", gap: 6, flexWrap: "nowrap", justifyContent: "center" };
18596
- 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)(
18597
18751
  "div",
18598
18752
  {
18599
18753
  "data-ohw-bridge": "",
18600
18754
  "data-ohw-toolbar": "",
18601
18755
  onMouseDown: (e) => e.stopPropagation(),
18602
- style: {
18603
- display: "flex",
18604
- flexDirection: "column",
18605
- gap: 6,
18606
- alignItems: "center",
18607
- background: OHW_BLUE,
18608
- padding: 8,
18609
- borderRadius: 12,
18610
- boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
18611
- pointerEvents: pending ? "none" : "auto",
18612
- opacity: pending ? 0.6 : 1,
18613
- maxWidth: isMobile ? "calc(100vw - 24px)" : "none"
18614
- },
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 },
18615
18758
  children: [
18616
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: rowStyle, children: [
18617
- VARIANTS.map((v) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: v.label, active: variant === v.value, onClick: () => onSetVariant(v.value) }, v.value)),
18618
- ALIGNS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: a.label, active: align === a.value, onClick: () => onSetAlign(a.value) }, a.value))
18619
- ] }),
18620
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: rowStyle, children: [
18621
- HOVERS.map((h) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: h.label, active: hover === h.value, onClick: () => onSetHover(h.value) }, h.value)),
18622
- TREATMENTS.map((t) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: t.label, active: treatment === t.value, onClick: () => onSetTreatment(t.value) }, t.value))
18623
- ] }),
18624
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
18625
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: "\u2212", onClick: () => onResize(-8) }),
18626
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { style: { color: "#ffffff", fontSize: 12, minWidth: 34, textAlign: "center" }, children: [
18627
- sizeValue,
18628
- "px"
18629
- ] }),
18630
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: "+", onClick: () => onResize(8) })
18631
- ] }),
18632
- /* @__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 }) }),
18633
- /* @__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) }) }),
18634
- currentHref && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: rowStyle, children: [
18635
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: "New tab", active: currentTarget === "new-tab", onClick: () => onSetLink(currentHref, "new-tab") }),
18636
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: "Same tab", active: currentTarget === "same-tab", onClick: () => onSetLink(currentHref, "same-tab") })
18637
- ] }),
18638
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: rowStyle, children: [
18639
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: "Change Image", onClick: onChangeImage }),
18640
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: "+ Item", onClick: onAddItem }),
18641
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: "\u2212 Item", onClick: onRemoveItem, disabled: itemCount <= 1 })
18642
- ] }),
18643
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: rowStyle, children: [
18644
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: "\u2190 Move", onClick: onMoveLeft, disabled: index <= 0 }),
18645
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pill, { label: "\u2192 Move", onClick: onMoveRight, disabled: index >= itemCount - 1 })
18646
- ] }),
18647
- 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 })
18648
18835
  ]
18649
18836
  }
18650
- );
18837
+ ) });
18651
18838
  }
18652
18839
  function findMediaStripSlots(tree) {
18653
18840
  const stack = tree.rows.flatMap((row) => row.blocks);
@@ -18666,31 +18853,23 @@ function AiMediaStripOverlay({
18666
18853
  }) {
18667
18854
  const [hover, setHover] = (0, import_react10.useState)(null);
18668
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);
18669
18858
  const [pending, setPending] = (0, import_react10.useState)(false);
18670
18859
  const [error, setError] = (0, import_react10.useState)(null);
18671
18860
  const activeRef = (0, import_react10.useRef)(null);
18672
18861
  activeRef.current = active;
18862
+ const linkOpenRef = (0, import_react10.useRef)(false);
18863
+ linkOpenRef.current = linkOpen;
18673
18864
  (0, import_react10.useEffect)(() => {
18674
18865
  const onMove = (e) => {
18866
+ const ref = itemRefFromTarget(e.target);
18675
18867
  const t = e.target;
18676
- if (!(t instanceof HTMLElement)) return;
18677
- 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]")) {
18678
- return;
18679
- }
18680
- const itemEl = t.closest("[data-ohw-media-strip-item]");
18681
- if (!itemEl) {
18682
- setHover(null);
18683
- return;
18684
- }
18685
- const sectionEl = itemEl.closest("[data-ohw-section]");
18686
- const unit = sectionEl ? movableUnit(sectionEl) : null;
18687
- const sectionId = unit ? instanceIdOf(unit) || null : null;
18688
- const index = Number(itemEl.dataset.ohwMediaStripIndex);
18689
- if (!sectionId || Number.isNaN(index)) {
18690
- setHover(null);
18691
- return;
18692
- }
18693
- 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
+ });
18694
18873
  };
18695
18874
  const onLeave = () => setHover(null);
18696
18875
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -18702,23 +18881,25 @@ function AiMediaStripOverlay({
18702
18881
  }, []);
18703
18882
  (0, import_react10.useEffect)(() => {
18704
18883
  const onClick = (e) => {
18705
- const current = activeRef.current;
18706
- if (!current) return;
18707
18884
  const t = e.target;
18708
- if (!(t instanceof HTMLElement)) return;
18709
- if (t.closest("[data-ohw-item-interaction]")) return;
18710
- const itemEl = findMediaStripItemElement(current.sectionId, current.index);
18711
- if (itemEl && itemEl.contains(t)) return;
18712
- 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);
18713
18891
  setError(null);
18892
+ setActive(ref);
18714
18893
  };
18715
18894
  document.addEventListener("click", onClick, true);
18716
18895
  return () => document.removeEventListener("click", onClick, true);
18717
- }, []);
18896
+ }, [clearInteractionChromeRef]);
18718
18897
  (0, import_react10.useEffect)(() => {
18719
18898
  const onBlur = () => {
18720
18899
  if (!activeRef.current) return;
18721
18900
  setActive(null);
18901
+ setItemEditOn(false);
18902
+ setLinkOpen(false);
18722
18903
  setError(null);
18723
18904
  };
18724
18905
  window.addEventListener("blur", onBlur);
@@ -18728,7 +18909,10 @@ function AiMediaStripOverlay({
18728
18909
  const onKeyDown = (e) => {
18729
18910
  if (e.key !== "Escape") return;
18730
18911
  if (!activeRef.current) return;
18912
+ if (linkOpenRef.current) return;
18913
+ if (document.querySelector('[data-ohw-more-menu][data-state="open"]')) return;
18731
18914
  setActive(null);
18915
+ setItemEditOn(false);
18732
18916
  setError(null);
18733
18917
  };
18734
18918
  document.addEventListener("keydown", onKeyDown);
@@ -18762,137 +18946,133 @@ function AiMediaStripOverlay({
18762
18946
  tree: nextTree,
18763
18947
  label: entry.label,
18764
18948
  media: extraMedia ? { ...entry.media, ...extraMedia } : entry.media,
18765
- ...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
18766
18953
  });
18767
18954
  return true;
18768
18955
  },
18769
18956
  [findEntry, postToParent2, applyTreeLocalRef]
18770
18957
  );
18771
18958
  const activeEntry = active ? findEntry(active.sectionId) : null;
18772
- const hoverHandleRect = useLiveItemRect(!active && hover ? hover : null);
18773
18959
  const activeItemRect = useLiveItemRect(active);
18774
- const TOOLBAR_APPROX_H = 170;
18775
- const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
18776
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
18777
- hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
18778
- "button",
18779
- {
18780
- type: "button",
18781
- "data-ohw-toolbar": "",
18782
- onClick: () => {
18783
- clearInteractionChromeRef.current();
18784
- setActive(hover);
18785
- },
18786
- "aria-label": "Configure media item",
18787
- style: {
18788
- position: "fixed",
18789
- top: hoverHandleRect.top + 8,
18790
- left: hoverHandleRect.left + hoverHandleRect.width - 36,
18791
- width: 28,
18792
- height: 28,
18793
- borderRadius: 8,
18794
- border: `1.5px dashed ${PRIMARY3}`,
18795
- background: "#ffffff",
18796
- color: PRIMARY3,
18797
- display: "flex",
18798
- alignItems: "center",
18799
- justifyContent: "center",
18800
- cursor: "pointer",
18801
- zIndex: 2147483643,
18802
- fontSize: 14,
18803
- fontWeight: 700
18804
- },
18805
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react8.Star, { size: 14, style: { pointerEvents: "none" } })
18806
- }
18807
- ),
18808
- activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
18809
- ItemInteractionLayer,
18810
- {
18811
- rect: activeItemRect,
18812
- state: toolbarState,
18813
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
18814
- MediaStripToolbar,
18815
- {
18816
- entry: activeEntry,
18817
- index: active.index,
18818
- pending,
18819
- error,
18820
- onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
18821
- onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
18822
- onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
18823
- onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
18824
- onResize: (delta) => {
18825
- const slots = findMediaStripSlots(activeEntry.tree);
18826
- const variant = slots?.variant === "reel" ? "reel" : "logos";
18827
- const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
18828
- void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
18829
- },
18830
- onSetAlt: (alt) => {
18831
- void runMutation(active.sectionId, (tree) => setMediaStripItemAlt(tree, active.index, alt));
18832
- },
18833
- 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" };
18834
18994
  void runMutation(
18835
18995
  active.sectionId,
18836
- (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
18837
- );
18838
- },
18839
- onAddItem: () => {
18840
- void requestAssetPick(postToParent2).then((picked) => {
18841
- if (!picked) return;
18842
- const item = { assetId: picked.assetId, alt: "Logo" };
18843
- void runMutation(
18844
- active.sectionId,
18845
- (tree) => addMediaStripItem(tree, item, active.index),
18846
- { [picked.assetId]: picked.url }
18847
- ).then((applied) => {
18848
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
18849
- });
18850
- });
18851
- },
18852
- onChangeImage: () => {
18853
- void requestAssetPick(postToParent2).then((picked) => {
18854
- if (!picked) return;
18855
- void runMutation(
18856
- active.sectionId,
18857
- (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
18858
- { [picked.assetId]: picked.url }
18859
- );
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 });
18860
19000
  });
18861
- },
18862
- onRemoveItem: () => {
18863
- void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
18864
- (applied) => {
18865
- if (applied) setActive(null);
18866
- }
18867
- );
18868
- },
18869
- onMoveLeft: () => {
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
- }
18874
- );
18875
- },
18876
- onMoveRight: () => {
18877
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
18878
- (applied) => {
18879
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
18880
- }
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 }
18881
19010
  );
18882
- }
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
+ );
18883
19033
  }
18884
- )
18885
- }
18886
- )
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
+ ] })
18887
19067
  ] });
18888
19068
  }
18889
19069
 
18890
19070
  // src/ui/ai-section/AiPricingCardOverlay.tsx
18891
19071
  var import_react11 = require("react");
18892
- var import_lucide_react9 = require("lucide-react");
18893
- 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");
18894
19074
  var PRIMARY4 = "var(--ohw-primary, #0885FE)";
18895
- 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]";
18896
19076
  function findPricingCardElement(sectionId, index) {
18897
19077
  const sectionEl = findByInstanceId(sectionId);
18898
19078
  if (!sectionEl) return null;
@@ -18904,7 +19084,7 @@ function findPricingCardElement(sectionId, index) {
18904
19084
  }
18905
19085
  function cardRefFromTarget(target) {
18906
19086
  if (!(target instanceof Element)) return null;
18907
- if (target.closest(CHROME_SELECTOR)) return null;
19087
+ if (target.closest(CHROME_SELECTOR2)) return null;
18908
19088
  const cardEl = target.closest("[data-ohw-pricing-card]");
18909
19089
  if (!cardEl) return null;
18910
19090
  const sectionEl = cardEl.closest("[data-ohw-section]");
@@ -18914,21 +19094,21 @@ function cardRefFromTarget(target) {
18914
19094
  if (!sectionId || Number.isNaN(index)) return null;
18915
19095
  return { sectionId, index };
18916
19096
  }
18917
- function sameRef(a, b) {
19097
+ function sameRef2(a, b) {
18918
19098
  return a?.sectionId === b?.sectionId && a?.index === b?.index;
18919
19099
  }
18920
- function rectOf(el) {
19100
+ function rectOf2(el) {
18921
19101
  if (!el) return null;
18922
19102
  const r2 = el.getBoundingClientRect();
18923
19103
  if (r2.width <= 0 || r2.height <= 0) return null;
18924
19104
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
18925
19105
  }
18926
19106
  function readRect3(ref) {
18927
- return rectOf(ref && findPricingCardElement(ref.sectionId, ref.index));
19107
+ return rectOf2(ref && findPricingCardElement(ref.sectionId, ref.index));
18928
19108
  }
18929
19109
  function readGridRect(ref) {
18930
19110
  const card = ref && findPricingCardElement(ref.sectionId, ref.index);
18931
- return rectOf(card?.closest("[data-ohw-pricing-collection]") ?? card);
19111
+ return rectOf2(card?.closest("[data-ohw-pricing-collection]") ?? card);
18932
19112
  }
18933
19113
  function useLiveCardRect(ref, grid = false) {
18934
19114
  const [rect, setRect] = (0, import_react11.useState)(null);
@@ -18958,7 +19138,7 @@ function useLiveCardRect(ref, grid = false) {
18958
19138
  }
18959
19139
  var TOOLBAR_GRID_GAP = 52;
18960
19140
  var TOOLBAR_GRID_GAP_MOBILE = 42;
18961
- var TOOLBAR_EDGE_MARGIN2 = 4;
19141
+ var TOOLBAR_EDGE_MARGIN3 = 4;
18962
19142
  function GridToolbarSlot({ gridRect, children }) {
18963
19143
  const slotRef = (0, import_react11.useRef)(null);
18964
19144
  const [shift, setShift] = (0, import_react11.useState)(0);
@@ -18970,7 +19150,7 @@ function GridToolbarSlot({ gridRect, children }) {
18970
19150
  const w = slot.offsetWidth;
18971
19151
  if (w <= 0) return;
18972
19152
  const half = w / 2;
18973
- 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));
18974
19154
  setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
18975
19155
  };
18976
19156
  clamp();
@@ -18979,7 +19159,7 @@ function GridToolbarSlot({ gridRect, children }) {
18979
19159
  return () => ro.disconnect();
18980
19160
  }, [centerX, children]);
18981
19161
  const mobile = window.innerWidth <= 480;
18982
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19162
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
18983
19163
  "div",
18984
19164
  {
18985
19165
  ref: slotRef,
@@ -19014,7 +19194,7 @@ function requestValidateTree2(postToParent2, tree) {
19014
19194
  });
19015
19195
  }
19016
19196
  function StyleSwatch({ style }) {
19017
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19197
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19018
19198
  "span",
19019
19199
  {
19020
19200
  "aria-hidden": true,
@@ -19028,35 +19208,35 @@ function StyleSwatch({ style }) {
19028
19208
  );
19029
19209
  }
19030
19210
  function ButtonGlyph() {
19031
- 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: [
19032
- /* @__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" }),
19033
- /* @__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" })
19034
19214
  ] });
19035
19215
  }
19036
19216
  function FeatureGlyph({ mode }) {
19037
- 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: [
19038
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
19039
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
19040
- /* @__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" }),
19041
- /* @__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" }),
19042
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
19043
- 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" })
19044
19224
  ] });
19045
19225
  }
19046
- function ToolbarAction({
19226
+ function ToolbarAction2({
19047
19227
  label,
19048
19228
  onClick,
19049
19229
  disabled = false,
19050
19230
  children
19051
19231
  }) {
19052
- const button = /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
19053
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(Tooltip, { children: [
19054
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "inline-flex", children: button }) : button }),
19055
- /* @__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 })
19056
19236
  ] });
19057
19237
  }
19058
- function ToolbarSeparator() {
19059
- 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" });
19060
19240
  }
19061
19241
  function CardStyleMenu({
19062
19242
  value,
@@ -19064,7 +19244,7 @@ function CardStyleMenu({
19064
19244
  disabled = false
19065
19245
  }) {
19066
19246
  const current = PRICING_CARD_STYLES.find((s) => s.value === value) ?? PRICING_CARD_STYLES[0];
19067
- const trigger = /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
19247
+ const trigger = /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19068
19248
  "button",
19069
19249
  {
19070
19250
  type: "button",
@@ -19075,28 +19255,28 @@ function CardStyleMenu({
19075
19255
  disabled && "cursor-not-allowed opacity-40 hover:bg-transparent"
19076
19256
  ),
19077
19257
  children: [
19078
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(StyleSwatch, { style: current.value }),
19079
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "flex-1 text-left", children: current.label }),
19080
- /* @__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" } })
19081
19261
  ]
19082
19262
  }
19083
19263
  );
19084
19264
  if (disabled) {
19085
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19086
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
19087
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(Tooltip, { children: [
19088
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "inline-flex", children: trigger }) }),
19089
- /* @__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" })
19090
19270
  ] })
19091
19271
  ] });
19092
19272
  }
19093
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19094
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
19095
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(DropdownMenu, { modal: false, children: [
19096
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(DropdownMenuTrigger, { asChild: true, children: trigger }),
19097
- /* @__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) => {
19098
19278
  const selected = style.value === value;
19099
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
19279
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19100
19280
  DropdownMenuItem,
19101
19281
  {
19102
19282
  role: "menuitemradio",
@@ -19104,9 +19284,9 @@ function CardStyleMenu({
19104
19284
  onSelect: () => onChange(style.value),
19105
19285
  className: cn("grid grid-cols-[18px_1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
19106
19286
  children: [
19107
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(StyleSwatch, { style: style.value }),
19108
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { children: style.label }),
19109
- 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" })
19110
19290
  ]
19111
19291
  },
19112
19292
  style.value
@@ -19136,7 +19316,7 @@ function PricingCardToolbar({
19136
19316
  const style = pricingCardStyle(entry.tree, index);
19137
19317
  const featuredTag = pricingCardFeaturedTag(entry.tree, index);
19138
19318
  const mobile = window.innerWidth <= 480;
19139
- 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)(
19140
19320
  "div",
19141
19321
  {
19142
19322
  "data-ohw-bridge": "",
@@ -19148,7 +19328,7 @@ function PricingCardToolbar({
19148
19328
  ),
19149
19329
  style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
19150
19330
  children: [
19151
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
19331
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19152
19332
  "div",
19153
19333
  {
19154
19334
  className: cn(
@@ -19157,8 +19337,8 @@ function PricingCardToolbar({
19157
19337
  mobile ? "flex-wrap justify-center" : "flex-nowrap"
19158
19338
  ),
19159
19339
  children: [
19160
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(Tooltip, { children: [
19161
- /* @__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)(
19162
19342
  CustomToolbarButton,
19163
19343
  {
19164
19344
  type: "button",
@@ -19166,8 +19346,8 @@ function PricingCardToolbar({
19166
19346
  "aria-pressed": featuredTag,
19167
19347
  onClick: () => onSetFeaturedTag(!featuredTag),
19168
19348
  className: "group size-8",
19169
- children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19170
- import_lucide_react9.Star,
19349
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19350
+ import_lucide_react10.Star,
19171
19351
  {
19172
19352
  size: 16,
19173
19353
  fill: "currentColor",
@@ -19181,25 +19361,25 @@ function PricingCardToolbar({
19181
19361
  )
19182
19362
  }
19183
19363
  ) }),
19184
- /* @__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" })
19185
19365
  ] }),
19186
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarSeparator, {}),
19187
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
19188
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarSeparator, {}),
19189
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarAction, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ButtonGlyph, {}) }),
19190
- /* @__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: [
19191
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ButtonGlyph, {}),
19192
- /* @__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" })
19193
19373
  ] }) }),
19194
- /* @__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" }) }),
19195
- /* @__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" }) }),
19196
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ToolbarSeparator, {}),
19197
- /* @__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" } }) }),
19198
- /* @__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" } }) })
19199
19379
  ]
19200
19380
  }
19201
19381
  ),
19202
- 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 })
19203
19383
  ]
19204
19384
  }
19205
19385
  ) });
@@ -19233,7 +19413,7 @@ function AiPricingCardOverlay({
19233
19413
  const overEditable = Boolean(t?.closest?.("[data-ohw-editable], [data-ohw-role], a, button"));
19234
19414
  setHover((prev) => {
19235
19415
  const next = ref && !overEditable ? ref : null;
19236
- return sameRef(prev, next) ? prev : next;
19416
+ return sameRef2(prev, next) ? prev : next;
19237
19417
  });
19238
19418
  };
19239
19419
  const onLeave = () => setHover(null);
@@ -19248,10 +19428,10 @@ function AiPricingCardOverlay({
19248
19428
  const onClick = (e) => {
19249
19429
  const t = e.target;
19250
19430
  if (!(t instanceof Element)) return;
19251
- if (t.closest(CHROME_SELECTOR)) return;
19431
+ if (t.closest(CHROME_SELECTOR2)) return;
19252
19432
  const ref = cardRefFromTarget(t);
19253
- setActive((prev) => sameRef(prev, ref) ? prev : ref);
19254
- 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);
19255
19435
  };
19256
19436
  document.addEventListener("click", onClick, true);
19257
19437
  return () => document.removeEventListener("click", onClick, true);
@@ -19316,12 +19496,12 @@ function AiPricingCardOverlay({
19316
19496
  const activeEntry = active ? findEntry(active.sectionId) : null;
19317
19497
  const activeCardRect = useLiveCardRect(active);
19318
19498
  const activeGridRect = useLiveCardRect(active, true);
19319
- const hoverRect = useLiveCardRect(hover && !sameRef(hover, active) ? hover : null);
19320
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
19321
- hoverRect && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19322
- activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
19323
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
19324
- /* @__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)(
19325
19505
  PricingCardToolbar,
19326
19506
  {
19327
19507
  entry: activeEntry,
@@ -19349,7 +19529,7 @@ function AiPricingCardOverlay({
19349
19529
  onRemoveFeature: () => runMutation(active.sectionId, (tree) => removePricingCardFeature(tree, active.index))
19350
19530
  }
19351
19531
  ) }),
19352
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19532
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19353
19533
  "div",
19354
19534
  {
19355
19535
  "data-ohw-bridge": "",
@@ -19371,7 +19551,7 @@ function AiPricingCardOverlay({
19371
19551
 
19372
19552
  // src/ui/ai-section/AiVideoOverlay.tsx
19373
19553
  var import_react12 = require("react");
19374
- var import_lucide_react10 = require("lucide-react");
19554
+ var import_lucide_react11 = require("lucide-react");
19375
19555
 
19376
19556
  // src/lib/ai-video-tree.ts
19377
19557
  function resolveVideoBlock(tree, path) {
@@ -19442,10 +19622,10 @@ function extractYouTubeId(input) {
19442
19622
  }
19443
19623
 
19444
19624
  // src/ui/ai-section/AiVideoOverlay.tsx
19445
- var import_jsx_runtime21 = require("react/jsx-runtime");
19625
+ var import_jsx_runtime22 = require("react/jsx-runtime");
19446
19626
  var PRIMARY5 = "#0885FE";
19447
- var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY5})`;
19448
- 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)`;
19449
19629
  function findVideoBlockElement(sectionId, path) {
19450
19630
  const sectionEl = findByInstanceId(sectionId);
19451
19631
  if (!sectionEl) return null;
@@ -19505,12 +19685,12 @@ function requestValidateTree3(postToParent2, tree) {
19505
19685
  postToParent2({ type: "ow:ai-validate-tree-request", id, tree });
19506
19686
  });
19507
19687
  }
19508
- function Pill2({
19688
+ function Pill({
19509
19689
  label,
19510
19690
  onClick,
19511
19691
  disabled
19512
19692
  }) {
19513
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19693
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19514
19694
  "button",
19515
19695
  {
19516
19696
  type: "button",
@@ -19526,7 +19706,7 @@ function Pill2({
19526
19706
  border: "none",
19527
19707
  cursor: disabled ? "default" : "pointer",
19528
19708
  background: "#ffffff",
19529
- color: OHW_BLUE_DARK2,
19709
+ color: OHW_BLUE_DARK,
19530
19710
  opacity: disabled ? 0.4 : 1,
19531
19711
  fontSize: 12,
19532
19712
  lineHeight: "16px",
@@ -19569,8 +19749,8 @@ function VideoUrlInput({ value, onCommit }) {
19569
19749
  onCommit(id);
19570
19750
  }
19571
19751
  };
19572
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
19573
- /* @__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)(
19574
19754
  "div",
19575
19755
  {
19576
19756
  style: {
@@ -19586,12 +19766,12 @@ function VideoUrlInput({ value, onCommit }) {
19586
19766
  boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
19587
19767
  },
19588
19768
  children: [
19589
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19769
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19590
19770
  "span",
19591
19771
  {
19592
19772
  style: {
19593
19773
  flex: "0 0 auto",
19594
- color: OHW_BLUE_DARK2,
19774
+ color: OHW_BLUE_DARK,
19595
19775
  fontSize: 10,
19596
19776
  lineHeight: "16px",
19597
19777
  fontWeight: 700,
@@ -19601,7 +19781,7 @@ function VideoUrlInput({ value, onCommit }) {
19601
19781
  children: "Video"
19602
19782
  }
19603
19783
  ),
19604
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19784
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19605
19785
  "input",
19606
19786
  {
19607
19787
  type: "text",
@@ -19638,7 +19818,7 @@ function VideoUrlInput({ value, onCommit }) {
19638
19818
  ]
19639
19819
  }
19640
19820
  ),
19641
- 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." })
19642
19822
  ] });
19643
19823
  }
19644
19824
  function VideoToolbar({
@@ -19648,7 +19828,7 @@ function VideoToolbar({
19648
19828
  onSetVideoId,
19649
19829
  onRemove
19650
19830
  }) {
19651
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19831
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19652
19832
  "div",
19653
19833
  {
19654
19834
  "data-ohw-bridge": "",
@@ -19660,7 +19840,7 @@ function VideoToolbar({
19660
19840
  gap: 6,
19661
19841
  alignItems: "center",
19662
19842
  width: 280,
19663
- background: OHW_BLUE2,
19843
+ background: OHW_BLUE,
19664
19844
  padding: 8,
19665
19845
  borderRadius: 12,
19666
19846
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -19668,9 +19848,9 @@ function VideoToolbar({
19668
19848
  opacity: pending ? 0.6 : 1
19669
19849
  },
19670
19850
  children: [
19671
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
19672
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Pill2, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
19673
- 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 })
19674
19854
  ]
19675
19855
  }
19676
19856
  );
@@ -19790,8 +19970,8 @@ function AiVideoOverlay({
19790
19970
  const activeBlockRect = useLiveBlockRect(active);
19791
19971
  const TOOLBAR_APPROX_H = 90;
19792
19972
  const toolbarState = activeBlockRect && activeBlockRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
19793
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19794
- 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)(
19795
19975
  "button",
19796
19976
  {
19797
19977
  type: "button",
@@ -19819,15 +19999,15 @@ function AiVideoOverlay({
19819
19999
  fontSize: 14,
19820
20000
  fontWeight: 700
19821
20001
  },
19822
- 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" } })
19823
20003
  }
19824
20004
  ),
19825
- activeBlockRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
20005
+ activeBlockRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19826
20006
  ItemInteractionLayer,
19827
20007
  {
19828
20008
  rect: activeBlockRect,
19829
20009
  state: toolbarState,
19830
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
20010
+ toolbar: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19831
20011
  VideoToolbar,
19832
20012
  {
19833
20013
  videoId: videoIdAt(activeEntry.tree, active.path),
@@ -19851,7 +20031,7 @@ function AiVideoOverlay({
19851
20031
 
19852
20032
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
19853
20033
  var import_react13 = require("react");
19854
- var import_lucide_react11 = require("lucide-react");
20034
+ var import_lucide_react12 = require("lucide-react");
19855
20035
 
19856
20036
  // src/lib/ai-carousel-tree.ts
19857
20037
  function isCarouselBlock(node) {
@@ -19902,10 +20082,10 @@ function setCarouselAutoplay(tree, on) {
19902
20082
  }
19903
20083
 
19904
20084
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
19905
- var import_jsx_runtime22 = require("react/jsx-runtime");
20085
+ var import_jsx_runtime23 = require("react/jsx-runtime");
19906
20086
  var PRIMARY6 = "#0885FE";
19907
- var OHW_BLUE3 = `var(--ohw-primary, ${PRIMARY6})`;
19908
- 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)`;
19909
20089
  function findCarouselItemElement(sectionId, index) {
19910
20090
  const sectionEl = findByInstanceId(sectionId);
19911
20091
  if (!sectionEl) return null;
@@ -19984,8 +20164,8 @@ function requestAssetPick2(postToParent2) {
19984
20164
  postToParent2({ type: "ow:asset-pick", id });
19985
20165
  });
19986
20166
  }
19987
- function Pill3({ label, onClick, disabled }) {
19988
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20167
+ function Pill2({ label, onClick, disabled }) {
20168
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
19989
20169
  "button",
19990
20170
  {
19991
20171
  type: "button",
@@ -20001,7 +20181,7 @@ function Pill3({ label, onClick, disabled }) {
20001
20181
  border: "none",
20002
20182
  cursor: disabled ? "default" : "pointer",
20003
20183
  background: "#ffffff",
20004
- color: OHW_BLUE_DARK3,
20184
+ color: OHW_BLUE_DARK2,
20005
20185
  opacity: disabled ? 0.4 : 1,
20006
20186
  fontSize: 12,
20007
20187
  lineHeight: "16px",
@@ -20019,7 +20199,7 @@ function TogglePill({
20019
20199
  onToggle,
20020
20200
  disabled
20021
20201
  }) {
20022
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
20202
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20023
20203
  "button",
20024
20204
  {
20025
20205
  type: "button",
@@ -20038,7 +20218,7 @@ function TogglePill({
20038
20218
  border: "none",
20039
20219
  cursor: disabled ? "default" : "pointer",
20040
20220
  background: "#ffffff",
20041
- color: OHW_BLUE_DARK3,
20221
+ color: OHW_BLUE_DARK2,
20042
20222
  opacity: disabled ? 0.4 : 1,
20043
20223
  fontSize: 12,
20044
20224
  lineHeight: "16px",
@@ -20048,7 +20228,7 @@ function TogglePill({
20048
20228
  },
20049
20229
  children: [
20050
20230
  label,
20051
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20231
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20052
20232
  "span",
20053
20233
  {
20054
20234
  "aria-hidden": true,
@@ -20061,10 +20241,10 @@ function TogglePill({
20061
20241
  // Off is a grey track, not a washed-out blue: the pill's own white background already
20062
20242
  // carries the brand colour in the text, and a pale blue track next to it read as
20063
20243
  // "disabled control" rather than "switch that is off".
20064
- background: on ? OHW_BLUE_DARK3 : "#CBD5E1",
20244
+ background: on ? OHW_BLUE_DARK2 : "#CBD5E1",
20065
20245
  transition: "background 120ms ease"
20066
20246
  },
20067
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20247
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20068
20248
  "span",
20069
20249
  {
20070
20250
  style: {
@@ -20093,7 +20273,7 @@ function CarouselSlideToolbar({
20093
20273
  onChangeImage,
20094
20274
  onToggleAutoplay
20095
20275
  }) {
20096
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
20276
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20097
20277
  "div",
20098
20278
  {
20099
20279
  "data-ohw-bridge": "",
@@ -20104,7 +20284,7 @@ function CarouselSlideToolbar({
20104
20284
  flexDirection: "column",
20105
20285
  gap: 6,
20106
20286
  alignItems: "center",
20107
- background: OHW_BLUE3,
20287
+ background: OHW_BLUE2,
20108
20288
  padding: 8,
20109
20289
  borderRadius: 12,
20110
20290
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -20116,11 +20296,11 @@ function CarouselSlideToolbar({
20116
20296
  pointerEvents: pending ? "none" : "auto"
20117
20297
  },
20118
20298
  children: [
20119
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20120
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Pill3, { label: "Change Image", onClick: onChangeImage }),
20121
- /* @__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 })
20122
20302
  ] }),
20123
- 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 })
20124
20304
  ]
20125
20305
  }
20126
20306
  );
@@ -20260,8 +20440,8 @@ function AiFadeCarouselOverlay({
20260
20440
  const activeItemRect = useLiveItemRect2(active);
20261
20441
  const TOOLBAR_APPROX_H = 90;
20262
20442
  const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
20263
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
20264
- 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)(
20265
20445
  "button",
20266
20446
  {
20267
20447
  type: "button",
@@ -20290,10 +20470,10 @@ function AiFadeCarouselOverlay({
20290
20470
  fontSize: 14,
20291
20471
  fontWeight: 700
20292
20472
  },
20293
- 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" } })
20294
20474
  }
20295
20475
  ),
20296
- swapping && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20476
+ swapping && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20297
20477
  "div",
20298
20478
  {
20299
20479
  ref: skeletonRef,
@@ -20311,15 +20491,15 @@ function AiFadeCarouselOverlay({
20311
20491
  zIndex: 2147483646,
20312
20492
  pointerEvents: "none"
20313
20493
  },
20314
- 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 })
20315
20495
  }
20316
20496
  ),
20317
- activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20497
+ activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20318
20498
  ItemInteractionLayer,
20319
20499
  {
20320
20500
  rect: activeItemRect,
20321
20501
  state: toolbarState,
20322
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20502
+ toolbar: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20323
20503
  CarouselSlideToolbar,
20324
20504
  {
20325
20505
  pending,
@@ -20672,113 +20852,6 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
20672
20852
  // src/ui/link-modal/LinkPopover.tsx
20673
20853
  var import_react17 = require("react");
20674
20854
 
20675
- // src/ui/dialog.tsx
20676
- var React9 = __toESM(require("react"), 1);
20677
- var import_radix_ui6 = require("radix-ui");
20678
- var import_lucide_react12 = require("lucide-react");
20679
- var import_jsx_runtime23 = require("react/jsx-runtime");
20680
- function Dialog2({
20681
- ...props
20682
- }) {
20683
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_radix_ui6.Dialog.Root, { "data-slot": "dialog", ...props });
20684
- }
20685
- function DialogPortal({
20686
- ...props
20687
- }) {
20688
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_radix_ui6.Dialog.Portal, { ...props });
20689
- }
20690
- function DialogOverlay({
20691
- className,
20692
- ...props
20693
- }) {
20694
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20695
- import_radix_ui6.Dialog.Overlay,
20696
- {
20697
- "data-slot": "dialog-overlay",
20698
- "data-ohw-link-modal-root": "",
20699
- className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
20700
- ...props
20701
- }
20702
- );
20703
- }
20704
- var DialogContent = React9.forwardRef(
20705
- ({ className, children, showCloseButton = true, container, ...props }, ref) => {
20706
- const positionMode = container ? "absolute" : "fixed";
20707
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(DialogPortal, { container: container ?? void 0, children: [
20708
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
20709
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20710
- import_radix_ui6.Dialog.Content,
20711
- {
20712
- ref,
20713
- "data-slot": "dialog-content",
20714
- className: cn(
20715
- positionMode,
20716
- "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",
20717
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
20718
- container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
20719
- className
20720
- ),
20721
- onMouseDown: (e) => e.stopPropagation(),
20722
- ...props,
20723
- children: [
20724
- children,
20725
- showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20726
- import_radix_ui6.Dialog.Close,
20727
- {
20728
- type: "button",
20729
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
20730
- "aria-label": "Close",
20731
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react12.X, { size: 16, "aria-hidden": true })
20732
- }
20733
- ) : null
20734
- ]
20735
- }
20736
- )
20737
- ] });
20738
- }
20739
- );
20740
- DialogContent.displayName = import_radix_ui6.Dialog.Content.displayName;
20741
- function DialogHeader({
20742
- className,
20743
- ...props
20744
- }) {
20745
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
20746
- }
20747
- function DialogFooter({
20748
- className,
20749
- ...props
20750
- }) {
20751
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20752
- "div",
20753
- {
20754
- className: cn("flex items-center justify-end gap-2", className),
20755
- ...props
20756
- }
20757
- );
20758
- }
20759
- var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20760
- import_radix_ui6.Dialog.Title,
20761
- {
20762
- ref,
20763
- className: cn(
20764
- "text-lg font-semibold leading-none tracking-tight text-card-foreground",
20765
- className
20766
- ),
20767
- ...props
20768
- }
20769
- ));
20770
- DialogTitle.displayName = import_radix_ui6.Dialog.Title.displayName;
20771
- var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20772
- import_radix_ui6.Dialog.Description,
20773
- {
20774
- ref,
20775
- className: cn("text-sm text-muted-foreground", className),
20776
- ...props
20777
- }
20778
- ));
20779
- DialogDescription.displayName = import_radix_ui6.Dialog.Description.displayName;
20780
- var DialogClose = import_radix_ui6.Dialog.Close;
20781
-
20782
20855
  // src/ui/link-modal/LinkEditorPanel.tsx
20783
20856
  var import_lucide_react16 = require("lucide-react");
20784
20857
 
@@ -21882,6 +21955,7 @@ function shouldUseDevFixtures() {
21882
21955
  function getLogoElement(el) {
21883
21956
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
21884
21957
  if (marked) return marked;
21958
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
21885
21959
  const root = el.closest("nav, [data-ohw-nav-root], footer");
21886
21960
  if (!root) return null;
21887
21961
  const anchor = el.closest("a");
@@ -24462,7 +24536,7 @@ function getNavigationSelectionParent(el) {
24462
24536
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
24463
24537
  return resolveFooterLinksContainerFrom(el);
24464
24538
  }
24465
- 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)) {
24466
24540
  return getNavigationRoot(el);
24467
24541
  }
24468
24542
  return null;
@@ -24684,7 +24758,6 @@ var ICONS = {
24684
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"/>',
24685
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"/>'
24686
24760
  };
24687
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
24688
24761
  var SELECTION_CHROME_GAP2 = 4;
24689
24762
  var TOOLBAR_STROKE_GAP2 = 4;
24690
24763
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -24782,7 +24855,7 @@ function applyVisibleViewport(el, parentScroll) {
24782
24855
  el.style.top = `${top}px`;
24783
24856
  el.style.height = `${height}px`;
24784
24857
  }
24785
- var TOOLBAR_EDGE_MARGIN3 = 4;
24858
+ var TOOLBAR_EDGE_MARGIN4 = 4;
24786
24859
  function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
24787
24860
  const isAbove = transform.includes("translateY(-100%)");
24788
24861
  let visualTop = isAbove ? top - approxH : top;
@@ -24809,8 +24882,8 @@ function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
24809
24882
  function clampToolbarLeft(centerX, toolbarWidth) {
24810
24883
  const half = toolbarWidth / 2;
24811
24884
  return Math.max(
24812
- TOOLBAR_EDGE_MARGIN3 + half,
24813
- Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half)
24885
+ TOOLBAR_EDGE_MARGIN4 + half,
24886
+ Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN4 - half)
24814
24887
  );
24815
24888
  }
24816
24889
  function calcToolbarPos(rect, parentScroll, toolbarW = 306) {
@@ -25700,21 +25773,6 @@ function OhhwellsBridge() {
25700
25773
  const [isItemDragging, setIsItemDragging] = (0, import_react21.useState)(false);
25701
25774
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react21.useState)(false);
25702
25775
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
25703
- const [floatingPanel, setFloatingPanel] = (0, import_react21.useState)(null);
25704
- const floatingPanelOpenRef = (0, import_react21.useRef)(false);
25705
- floatingPanelOpenRef.current = floatingPanel !== null;
25706
- const floatingPanelRef = (0, import_react21.useRef)(null);
25707
- floatingPanelRef.current = floatingPanel;
25708
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react21.useState)(null);
25709
- const buttonCollectionDragRef = (0, import_react21.useRef)(null);
25710
- const buttonDropInsertIndexRef = (0, import_react21.useRef)(null);
25711
- const [buttonDropSlots, setButtonDropSlots] = (0, import_react21.useState)([]);
25712
- const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react21.useState)(null);
25713
- const buttonPointerDragRef = (0, import_react21.useRef)(null);
25714
- const [buttonStyleTick, setButtonStyleTick] = (0, import_react21.useState)(0);
25715
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react21.useState)(null);
25716
- const [editorViewport, setEditorViewport] = (0, import_react21.useState)("desktop");
25717
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react21.useState)(null);
25718
25776
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react21.useState)(null);
25719
25777
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react21.useState)(null);
25720
25778
  const footerDragRef = (0, import_react21.useRef)(null);
@@ -25740,6 +25798,21 @@ function OhhwellsBridge() {
25740
25798
  const stylesRef = (0, import_react21.useRef)("");
25741
25799
  const appliedDrawerRef = (0, import_react21.useRef)(null);
25742
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);
25743
25816
  const [sitePages, setSitePages] = (0, import_react21.useState)([]);
25744
25817
  const [sectionsByPath, setSectionsByPath] = (0, import_react21.useState)({});
25745
25818
  const sectionsPrefetchGenRef = (0, import_react21.useRef)(0);
@@ -25748,7 +25821,18 @@ function OhhwellsBridge() {
25748
25821
  const linkPopoverOpenRef = (0, import_react21.useRef)(false);
25749
25822
  const linkPopoverGraceUntilRef = (0, import_react21.useRef)(0);
25750
25823
  setLinkPopoverRef.current = setLinkPopover;
25824
+ setFloatingPanelRef.current = setFloatingPanel;
25751
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
+ }, []);
25752
25836
  (0, import_react21.useEffect)(() => {
25753
25837
  setSectionOrderPersistHandler((entries) => {
25754
25838
  const currentPath = window.location.pathname;
@@ -27443,6 +27527,7 @@ function OhhwellsBridge() {
27443
27527
  applyAiFooterFromContent(content);
27444
27528
  if (typeof content[STYLE_STORE_KEY] === "string") {
27445
27529
  stylesRef.current = content[STYLE_STORE_KEY];
27530
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
27446
27531
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
27447
27532
  }
27448
27533
  applyBrandChrome(content);
@@ -27453,12 +27538,12 @@ function OhhwellsBridge() {
27453
27538
  if (key === "__ohw_sections") continue;
27454
27539
  if (key === AI_SECTIONS_KEY) continue;
27455
27540
  if (key === AI_FOOTER_KEY) continue;
27541
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
27542
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
27456
27543
  if (key === BRAND_KIT_KEY) continue;
27457
27544
  if (key === STYLE_STORE_KEY) continue;
27458
27545
  if (BRAND_CHROME_KEYS.has(key)) continue;
27459
- if (key === LOGO_PLACEHOLDER_KEY) continue;
27460
27546
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
27461
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
27462
27547
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
27463
27548
  continue;
27464
27549
  }
@@ -27673,17 +27758,18 @@ function OhhwellsBridge() {
27673
27758
  }
27674
27759
  applyAiFooterFromContent(content);
27675
27760
  if (typeof content[STYLE_STORE_KEY] === "string") {
27761
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
27676
27762
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
27677
27763
  }
27678
27764
  for (const [key, val] of Object.entries(content)) {
27679
27765
  if (key === "__ohw_sections") continue;
27680
27766
  if (key === AI_SECTIONS_KEY) continue;
27681
27767
  if (key === AI_FOOTER_KEY) continue;
27768
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
27769
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
27682
27770
  if (key === BRAND_KIT_KEY) continue;
27683
27771
  if (key === STYLE_STORE_KEY) continue;
27684
27772
  if (BRAND_CHROME_KEYS.has(key)) continue;
27685
- if (key === LOGO_PLACEHOLDER_KEY) continue;
27686
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
27687
27773
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
27688
27774
  continue;
27689
27775
  }
@@ -27940,26 +28026,11 @@ function OhhwellsBridge() {
27940
28026
  const t2 = setTimeout(measure, 500);
27941
28027
  const ro = new ResizeObserver(schedule);
27942
28028
  ro.observe(document.body);
27943
- let lastWidth = window.innerWidth;
27944
- let resizeTimers = [];
27945
- const clearResizeTimers = () => {
27946
- resizeTimers.forEach(clearTimeout);
27947
- resizeTimers = [];
27948
- };
27949
- const handleResize = () => {
27950
- if (window.innerWidth === lastWidth) return;
27951
- lastWidth = window.innerWidth;
27952
- clearResizeTimers();
27953
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
27954
- };
27955
- window.addEventListener("resize", handleResize);
27956
28029
  return () => {
27957
28030
  clearTimeout(t1);
27958
28031
  clearTimeout(t2);
27959
28032
  if (raf != null) cancelAnimationFrame(raf);
27960
28033
  ro.disconnect();
27961
- clearResizeTimers();
27962
- window.removeEventListener("resize", handleResize);
27963
28034
  };
27964
28035
  }, [pathname, isEditMode, postToParent2]);
27965
28036
  (0, import_react21.useEffect)(() => {
@@ -28258,6 +28329,13 @@ function OhhwellsBridge() {
28258
28329
  if (target.closest("[data-ohw-state-toggle]")) return;
28259
28330
  if (target.closest("[data-ohw-max-badge]")) return;
28260
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
+ }
28261
28339
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
28262
28340
  clearMediaSelectionRef.current();
28263
28341
  }
@@ -28281,13 +28359,6 @@ function OhhwellsBridge() {
28281
28359
  return;
28282
28360
  }
28283
28361
  }
28284
- if (isInsideFloatingPanel(target)) return;
28285
- if (target.closest("[data-ohw-form-toolbar]")) return;
28286
- if (target.closest(
28287
- '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
28288
- )) {
28289
- return;
28290
- }
28291
28362
  {
28292
28363
  const formEl = getFormElement(target);
28293
28364
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -28441,9 +28512,13 @@ function OhhwellsBridge() {
28441
28512
  if (isIconEditable(editable) && !getSocialItem(editable)) {
28442
28513
  e.preventDefault();
28443
28514
  e.stopPropagation();
28444
- selectIconElementRef.current(editable);
28445
28515
  const aiIcon = editable.hasAttribute("data-ai-icon");
28446
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
+ }
28447
28522
  postToParentRef.current({
28448
28523
  type: "ow:icon-pick",
28449
28524
  key: editable.dataset.ohwKey ?? "",
@@ -28671,6 +28746,9 @@ function OhhwellsBridge() {
28671
28746
  setHoveredItemRect(null);
28672
28747
  hoveredNavContainerRef.current = null;
28673
28748
  setHoveredNavContainerRect(null);
28749
+ siblingHintElRef.current = null;
28750
+ setSiblingHintRect(null);
28751
+ setSiblingHintRects([]);
28674
28752
  return;
28675
28753
  }
28676
28754
  {
@@ -28791,7 +28869,6 @@ function OhhwellsBridge() {
28791
28869
  hoveredNavContainerRef.current = null;
28792
28870
  setHoveredNavContainerRect(null);
28793
28871
  hoveredItemElRef.current = editable;
28794
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
28795
28872
  }
28796
28873
  }
28797
28874
  }
@@ -29501,7 +29578,11 @@ function OhhwellsBridge() {
29501
29578
  };
29502
29579
  const handleMouseMove = (e) => {
29503
29580
  const { clientX, clientY } = e;
29504
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
29581
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
29582
+ dismissImageHover();
29583
+ clearImageHover();
29584
+ return;
29585
+ }
29505
29586
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) {
29506
29587
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
29507
29588
  formHoverElRef.current = null;
@@ -29530,7 +29611,12 @@ function OhhwellsBridge() {
29530
29611
  setHoveredItemRect(null);
29531
29612
  hoveredNavContainerRef.current = null;
29532
29613
  setHoveredNavContainerRect(null);
29614
+ siblingHintElRef.current = null;
29615
+ setSiblingHintRect(null);
29616
+ setSiblingHintRects([]);
29533
29617
  dismissImageHover();
29618
+ clearImageHover();
29619
+ setSectionGap(null);
29534
29620
  return;
29535
29621
  }
29536
29622
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -29542,7 +29628,11 @@ function OhhwellsBridge() {
29542
29628
  if (e.data?.type !== "ow:pointer-sync") return;
29543
29629
  const { clientX, clientY } = e.data;
29544
29630
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
29545
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
29631
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
29632
+ dismissImageHover();
29633
+ clearImageHover();
29634
+ return;
29635
+ }
29546
29636
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) return;
29547
29637
  if (probeSocialsRowAt(clientX, clientY)) return;
29548
29638
  probeSectionGapAt(clientX, clientY);
@@ -29922,6 +30012,7 @@ function OhhwellsBridge() {
29922
30012
  applyAiFooterFromContent(content);
29923
30013
  if (typeof content[STYLE_STORE_KEY] === "string") {
29924
30014
  stylesRef.current = content[STYLE_STORE_KEY];
30015
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
29925
30016
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
29926
30017
  }
29927
30018
  applyBrandChrome(content);
@@ -29936,12 +30027,12 @@ function OhhwellsBridge() {
29936
30027
  }
29937
30028
  if (key === AI_SECTIONS_KEY) continue;
29938
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;
29939
30032
  if (key === BRAND_KIT_KEY) continue;
29940
30033
  if (key === STYLE_STORE_KEY) continue;
29941
30034
  if (BRAND_CHROME_KEYS.has(key)) continue;
29942
- if (key === LOGO_PLACEHOLDER_KEY) continue;
29943
30035
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
29944
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
29945
30036
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
29946
30037
  continue;
29947
30038
  }
@@ -30316,6 +30407,44 @@ function OhhwellsBridge() {
30316
30407
  });
30317
30408
  };
30318
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);
30319
30448
  const handleGetBrand = (e) => {
30320
30449
  if (e.data?.type !== "ow:get-brand") return;
30321
30450
  const template = deriveTemplateBrand();
@@ -30539,6 +30668,12 @@ function OhhwellsBridge() {
30539
30668
  closeLinkPopoverRef.current();
30540
30669
  return;
30541
30670
  }
30671
+ if (floatingPanelOpenRef.current) {
30672
+ setFloatingPanelRef.current(null);
30673
+ deselectRef.current();
30674
+ deactivateRef.current();
30675
+ return;
30676
+ }
30542
30677
  deselectRef.current();
30543
30678
  deactivateRef.current();
30544
30679
  clearMediaSelectionRef.current();
@@ -31349,6 +31484,7 @@ function OhhwellsBridge() {
31349
31484
  window.removeEventListener("message", handleAiSetSections);
31350
31485
  window.removeEventListener("message", handleAiSetBrand);
31351
31486
  window.removeEventListener("message", handleAiSetStyles);
31487
+ window.removeEventListener("message", handleAiSetButtonStyle);
31352
31488
  window.removeEventListener("message", handleCollectItems);
31353
31489
  window.removeEventListener("message", handleAiApplyItems);
31354
31490
  window.removeEventListener("message", handleGetBrand);
@@ -31360,10 +31496,10 @@ function OhhwellsBridge() {
31360
31496
  window.removeEventListener("message", handleDeleteSection);
31361
31497
  window.removeEventListener("message", handleDuplicateSection);
31362
31498
  window.removeEventListener("message", handleDeactivate);
31363
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
31364
31499
  window.removeEventListener("message", handleToastAction);
31365
31500
  window.removeEventListener("message", handleFormCount);
31366
31501
  window.removeEventListener("message", handleUiEscape);
31502
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
31367
31503
  autoSaveTimers.current.forEach(clearTimeout);
31368
31504
  autoSaveTimers.current.clear();
31369
31505
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -31657,7 +31793,7 @@ function OhhwellsBridge() {
31657
31793
  postToParent2({
31658
31794
  type: "ow:ready",
31659
31795
  version: "1",
31660
- bridgeVersion: "0.1.147",
31796
+ bridgeVersion: "0.1.149-next.402",
31661
31797
  path: pathname,
31662
31798
  nodes: collectEditableNodes(editContentRef.current),
31663
31799
  sections