@ohhwells/bridge 0.1.148 → 0.1.149-next.400

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/OhhwellsBridge.tsx
4
- import React12, { useCallback as useCallback12, useEffect as useEffect17, useLayoutEffect as useLayoutEffect6, useRef as useRef14, useState as useState18 } from "react";
4
+ import React12, { useCallback as useCallback12, useEffect as useEffect17, useLayoutEffect as useLayoutEffect7, useRef as useRef14, useState as useState18 } from "react";
5
5
  import { createRoot as createRoot3 } from "react-dom/client";
6
6
  import { flushSync as flushSync3 } from "react-dom";
7
7
 
@@ -1631,7 +1631,8 @@ function labelElement2(el) {
1631
1631
  return el.querySelector('[data-ohw-editable="text"], [data-ohw-editable="plain"]') ?? el.querySelector("[data-ohw-key]");
1632
1632
  }
1633
1633
  function buttonLabel(el) {
1634
- return (labelElement2(el)?.textContent ?? el.textContent ?? "").trim();
1634
+ const label = labelElement2(el);
1635
+ return (label ? label.innerHTML : el.textContent ?? "").trim();
1635
1636
  }
1636
1637
  function buttonHref(el) {
1637
1638
  return el.getAttribute("href") ?? "";
@@ -1770,7 +1771,7 @@ function duplicateButton(source, content) {
1770
1771
  stampVariantClass(copy, copyAssignment.variant);
1771
1772
  applyButtonStyle(copy, copyAssignment, paintedTokensOf(source) ?? tokens);
1772
1773
  const copyLabelEl = labelElement2(copy);
1773
- if (copyLabelEl) copyLabelEl.textContent = label;
1774
+ if (copyLabelEl) copyLabelEl.innerHTML = label;
1774
1775
  let collection = buttonCollectionOf(source);
1775
1776
  const collections = readCollections(content);
1776
1777
  let collectionId = collection?.getAttribute(COLLECTION_ATTR) ?? "";
@@ -2096,7 +2097,7 @@ function applySnapshotContent(el, snapshot, content) {
2096
2097
  const label = labelElement2(el);
2097
2098
  if (label) {
2098
2099
  if (label.getAttribute("data-ohw-key") !== labelKey) label.setAttribute("data-ohw-key", labelKey);
2099
- if (label.textContent !== text) label.textContent = text;
2100
+ if (label.innerHTML !== text) label.innerHTML = text;
2100
2101
  }
2101
2102
  }
2102
2103
  function reconcileButtonCollections(doc, content) {
@@ -4181,7 +4182,7 @@ function buildNavLink(index, href, label, template) {
4181
4182
  const span = document.createElement("span");
4182
4183
  span.setAttribute("data-ohw-editable", "text");
4183
4184
  span.setAttribute("data-ohw-key", `nav-${index}-label`);
4184
- span.textContent = label;
4185
+ span.innerHTML = label;
4185
4186
  anchor.appendChild(span);
4186
4187
  return anchor;
4187
4188
  }
@@ -6541,6 +6542,21 @@ function pricingCardFeatureCount(tree, index) {
6541
6542
  const target = findPricingCollection(tree)?.children?.[index];
6542
6543
  return target ? pricingFeatureList(target).length : 0;
6543
6544
  }
6545
+ function prevailingFeatureIcon(features) {
6546
+ const counts = /* @__PURE__ */ new Map();
6547
+ for (const row of features) {
6548
+ const key = `${row.icon ?? ""}|${row.iconColor ?? ""}`;
6549
+ const entry = counts.get(key) ?? { count: 0, ...row.icon ? { icon: row.icon } : {}, ...row.iconColor ? { iconColor: row.iconColor } : {} };
6550
+ entry.count += 1;
6551
+ counts.set(key, entry);
6552
+ }
6553
+ let best = null;
6554
+ for (const entry of counts.values()) {
6555
+ if (!best || entry.count > best.count) best = entry;
6556
+ }
6557
+ if (!best) return {};
6558
+ return { ...best.icon ? { icon: best.icon } : {}, ...best.iconColor ? { iconColor: best.iconColor } : {} };
6559
+ }
6544
6560
  function addPricingCardFeature(tree, index, text) {
6545
6561
  return withPricingCollection(tree, (collection) => {
6546
6562
  const children = collection.children;
@@ -6549,7 +6565,7 @@ function addPricingCardFeature(tree, index, text) {
6549
6565
  const features = pricingFeatureList(target);
6550
6566
  if (features.length >= PRICING_FEATURES_MAX) return false;
6551
6567
  target.slots = target.slots ?? {};
6552
- target.slots.features = [...features, { text }];
6568
+ target.slots.features = [...features, { text, ...prevailingFeatureIcon(features) }];
6553
6569
  return true;
6554
6570
  });
6555
6571
  }
@@ -7732,7 +7748,7 @@ var AI_MOBILE_CSS = [
7732
7748
  // page's first section. Top only (bottom needs no nav clearance); ~96px approximates the
7733
7749
  // template nav's own height (measured ~94px) — there's no shared `--ohw-header-h` var to read
7734
7750
  // an exact figure from (no live measurement exists), so this is a safe approximation, not a
7735
- // pixel-exact fit. Horizontal spacing already comes from `data-ai-container`.
7751
+ // pixel-exact fit. Horizontal spacing already comes from `data-ai-section-inner`.
7736
7752
  '[data-ai-overlay-clearance] [data-ai-overlap-layer="overlay"]{padding-top:96px}',
7737
7753
  // A full-bleed carousel's height is pure arithmetic: it spans the whole window, so 16/9 turns
7738
7754
  // window width straight into height. Measured at 16/9 it came out ~90% of the viewport at EVERY
@@ -7748,8 +7764,10 @@ var AI_MOBILE_CSS = [
7748
7764
  "[data-ai-section]{overflow-x:hidden}",
7749
7765
  // `:not([data-ai-full-bleed])`: this `!important` gutter beat the inline `padding: 0` that a
7750
7766
  // full-bleed section's container sets, so a carousel/media hero that is edge-to-edge on desktop
7751
- // still got a 20px band of section background down both sides on a phone.
7752
- "[data-ai-container]:not([data-ai-full-bleed]){padding:0 20px !important}",
7767
+ // still got a 20px band of section background down both sides on a phone. The footer's own
7768
+ // container keeps the unguarded rule below — `data-ai-full-bleed` can never appear on it.
7769
+ "[data-ai-section-inner]:not([data-ai-full-bleed]){padding:0 20px !important}",
7770
+ "[data-ai-container]{padding:0 20px !important}",
7753
7771
  "[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
7754
7772
  "[data-ai-cell]{width:100%;min-width:0}",
7755
7773
  // The rule above turns every row into a COLUMN flex container on phones, which silently breaks
@@ -7954,6 +7972,31 @@ function pricingCardEmphasis(featured, ctx, hasBg) {
7954
7972
  function textAttrs(ctx, path) {
7955
7973
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
7956
7974
  }
7975
+ var AI_RESPONSIVE_CSS = [
7976
+ "@media (max-width: 960px) {",
7977
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
7978
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
7979
+ "}",
7980
+ "@media (max-width: 640px) {",
7981
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
7982
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
7983
+ // Group containers flatten to a column on phones; span placements come along for free.
7984
+ // `:not([data-ai-group="overlap"])` for the same reason as the twin pair in AI_MOBILE_CSS — see
7985
+ // the comment there. This sheet's copy is the narrower selector, so without the exemption it
7986
+ // flattens an `overlap` hero below 640px even though the other rule stands down: measured a 390px
7987
+ // viewport with the heading/CTA in flow *under* the image, 219px of section background where the
7988
+ // layered text used to be.
7989
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) { display: flex !important; flex-direction: column !important; }',
7990
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) > * { grid-column: auto !important; }',
7991
+ // `:not([data-ai-full-bleed])` for the same reason as the twin rule in AI_MOBILE_CSS: this one
7992
+ // is narrower (0,2,0 vs 0,1,0) and also `!important`, so without the guard it wins outright and
7993
+ // a full-bleed hero gets its 20px side band back below 640px only — the one width where the
7994
+ // other rule had already been taught to stand down.
7995
+ " [data-ai-responsive] [data-ai-section-inner]:not([data-ai-full-bleed]) { padding-left: 20px !important; padding-right: 20px !important; }",
7996
+ " [data-ai-responsive] { overflow-x: hidden; }",
7997
+ " [data-ai-responsive] img { max-width: 100%; }",
7998
+ "}"
7999
+ ].join("\n");
7957
8000
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
7958
8001
  function MediaBox({
7959
8002
  refValue,
@@ -8008,7 +8051,7 @@ function MediaBox({
8008
8051
  position: fill ? "absolute" : void 0,
8009
8052
  inset: fill ? 0 : void 0,
8010
8053
  width: fill ? "100%" : fixedSize ? "auto" : "100%",
8011
- height: fill ? "100%" : void 0,
8054
+ height: fill ? "100%" : fixedSize ? height : void 0,
8012
8055
  aspectRatio: fill ? void 0 : aspectRatio,
8013
8056
  minHeight: fill || fixedSize ? void 0 : height ?? (aspectRatio ? void 0 : 180),
8014
8057
  borderRadius: radius,
@@ -9504,7 +9547,7 @@ function CollectionBlock({ node, ctx, path }) {
9504
9547
  return /* @__PURE__ */ jsx(
9505
9548
  "div",
9506
9549
  {
9507
- "data-ai-grid": "",
9550
+ "data-ai-grid": String(itemsPerRow),
9508
9551
  "data-ohw-pricing-collection": isPricing ? "" : void 0,
9509
9552
  style: {
9510
9553
  display: "grid",
@@ -9806,7 +9849,7 @@ function renderNode(node, ctx, path, ambientAlign) {
9806
9849
  },
9807
9850
  children: [
9808
9851
  i > 0 && ctx.heroOverlapFullBleed ? (
9809
- // The section's `data-ai-container` gave up its max-width/gutter so the base
9852
+ // The section's `data-ai-section-inner` gave up its max-width/gutter so the base
9810
9853
  // layer (this group's i===0 sibling) can span edge-to-edge — reintroduced here,
9811
9854
  // scoped to just the overlay text, so a heading/copy/CTA stack doesn't stretch
9812
9855
  // the full section width instead of reading at the usual content width.
@@ -10225,6 +10268,7 @@ function AiTreeRenderer({
10225
10268
  ...fillHeight ? { "data-ai-fill-height": "" } : {},
10226
10269
  ...needsOverlayClearance ? { "data-ai-overlay-clearance": "" } : {},
10227
10270
  ...bgAttrs,
10271
+ "data-ai-responsive": "",
10228
10272
  style: {
10229
10273
  position: "relative",
10230
10274
  padding: `${padCss} 0`,
@@ -10238,12 +10282,13 @@ function AiTreeRenderer({
10238
10282
  ...fillHeight ? { display: "grid" } : {}
10239
10283
  },
10240
10284
  children: [
10241
- isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10285
+ /* @__PURE__ */ jsx("style", { children: AI_RESPONSIVE_CSS }),
10242
10286
  /* @__PURE__ */ jsx("style", { children: AI_MOBILE_CSS }),
10287
+ isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10243
10288
  /* @__PURE__ */ jsx(
10244
10289
  "div",
10245
10290
  {
10246
- "data-ai-container": "",
10291
+ "data-ai-section-inner": "",
10247
10292
  ...fullBleedMedia ? { "data-ai-full-bleed": "" } : {},
10248
10293
  style: {
10249
10294
  position: "relative",
@@ -10260,7 +10305,7 @@ function AiTreeRenderer({
10260
10305
  children: tree.rows.map((row, r2) => /* @__PURE__ */ jsx(
10261
10306
  "div",
10262
10307
  {
10263
- "data-ai-row": "",
10308
+ "data-ai-columns": "",
10264
10309
  style: {
10265
10310
  display: "grid",
10266
10311
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -10285,7 +10330,12 @@ function AiTreeRenderer({
10285
10330
  // the full row height instead of clumping at the top.
10286
10331
  ...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
10287
10332
  },
10288
- children: renderNode(block, ctx, `r${r2}.b${b}`)
10333
+ children: renderNode(
10334
+ block,
10335
+ ctx,
10336
+ `r${r2}.b${b}`,
10337
+ settings.alignment === "center" ? "center" : "left"
10338
+ )
10289
10339
  },
10290
10340
  b
10291
10341
  ))
@@ -10917,9 +10967,10 @@ function applyAiSectionsToDom(state, options) {
10917
10967
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
10918
10968
  continue;
10919
10969
  }
10920
- const isNewMount = !existing || !existing.container.isConnected;
10921
10970
  let container = existing?.container;
10971
+ let isNewMount = false;
10922
10972
  if (!container || !container.isConnected) {
10973
+ isNewMount = true;
10923
10974
  existing?.root.unmount();
10924
10975
  container?.remove();
10925
10976
  container = document.createElement("div");
@@ -17361,6 +17412,7 @@ function MediaOverlay({
17361
17412
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
17362
17413
  );
17363
17414
  }, [isVideo]);
17415
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
17364
17416
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
17365
17417
  const box = {
17366
17418
  position: "fixed",
@@ -17459,11 +17511,10 @@ function MediaOverlay({
17459
17511
  className: "flex items-center justify-center cursor-pointer",
17460
17512
  style: {
17461
17513
  ...box,
17462
- // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
17463
- // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
17464
- // in-document, pointer-events does it natively. The button below opts back in, so
17465
- // Replace still works.
17466
- pointerEvents: hover.hasTextOverlap || cueOnly ? "none" : "auto",
17514
+ // Visual only. `pointer-events: auto` on this full-bleed rect covered the navbar
17515
+ // (hero bg includes the absolute TopNav) and swallowed CTA press-drag. Text overlay
17516
+ // and cueOnly also need clicks to fall through — Replace / video controls opt back in.
17517
+ pointerEvents: "none",
17467
17518
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
17468
17519
  // than hovered. Hover keeps the existing tinted preview.
17469
17520
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -17490,17 +17541,17 @@ function MediaOverlay({
17490
17541
  },
17491
17542
  children: [
17492
17543
  isVideo ? /* @__PURE__ */ jsx16(Film, { size: 14 }) : /* @__PURE__ */ jsx16(ImageIcon, { size: 14 }),
17493
- isVideo ? "Replace video" : "Replace image"
17544
+ replaceLabel
17494
17545
  ]
17495
17546
  }
17496
17547
  ),
17497
- replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ jsxs7(
17548
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ jsxs7(
17498
17549
  Button,
17499
17550
  {
17500
17551
  "data-ohw-media-overlay": "",
17501
17552
  variant: "outline",
17502
17553
  size: "sm",
17503
- "aria-label": isVideo ? "Replace video" : "Replace image",
17554
+ "aria-label": replaceLabel,
17504
17555
  className: "gap-1.5 cursor-pointer hover:bg-background",
17505
17556
  style: {
17506
17557
  ...OVERLAY_BUTTON_STYLE,
@@ -17523,7 +17574,7 @@ function MediaOverlay({
17523
17574
  },
17524
17575
  children: [
17525
17576
  isVideo ? /* @__PURE__ */ jsx16(Film, { size: 14 }) : /* @__PURE__ */ jsx16(ImageIcon, { size: 14 }),
17526
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
17577
+ replaceMode === "full" ? replaceLabel : null
17527
17578
  ]
17528
17579
  }
17529
17580
  )
@@ -18032,8 +18083,24 @@ function AiSectionOverlay({
18032
18083
  }
18033
18084
 
18034
18085
  // src/ui/ai-section/AiMediaStripOverlay.tsx
18035
- import { useCallback as useCallback3, useEffect as useEffect6, useRef as useRef5, useState as useState8 } from "react";
18036
- import { Star } from "lucide-react";
18086
+ import { useCallback as useCallback3, useEffect as useEffect6, useLayoutEffect as useLayoutEffect4, useRef as useRef5, useState as useState8 } from "react";
18087
+ import {
18088
+ AlignCenter,
18089
+ AlignLeft,
18090
+ AlignRight,
18091
+ ArrowLeft as ArrowLeft2,
18092
+ ArrowRight as ArrowRight2,
18093
+ Check as Check2,
18094
+ ChevronDown as ChevronDown3,
18095
+ Image as ImageIcon2,
18096
+ ImageMinus,
18097
+ ImagePlus,
18098
+ Link as LinkIcon,
18099
+ Minus,
18100
+ Palette,
18101
+ Plus as Plus2,
18102
+ Star
18103
+ } from "lucide-react";
18037
18104
 
18038
18105
  // src/lib/ai-media-strip-tree.ts
18039
18106
  var MEDIA_STRIP_ITEM_MIN = 1;
@@ -18147,14 +18214,6 @@ function setMediaStripItemAsset(tree, index, assetId) {
18147
18214
  return true;
18148
18215
  });
18149
18216
  }
18150
- function setMediaStripItemAlt(tree, index, alt) {
18151
- return withMediaStrip(tree, (block) => {
18152
- const items = block.items;
18153
- if (index < 0 || index >= items.length) return false;
18154
- items[index] = { ...items[index], alt };
18155
- return true;
18156
- });
18157
- }
18158
18217
  function setMediaStripItemLink(tree, index, link) {
18159
18218
  return withMediaStrip(tree, (block) => {
18160
18219
  const items = block.items;
@@ -18172,11 +18231,117 @@ function setMediaStripItemLink(tree, index, link) {
18172
18231
  });
18173
18232
  }
18174
18233
 
18234
+ // src/ui/dialog.tsx
18235
+ import * as React9 from "react";
18236
+ import { Dialog as DialogPrimitive } from "radix-ui";
18237
+ import { X as X2 } from "lucide-react";
18238
+ import { jsx as jsx19, jsxs as jsxs10 } from "react/jsx-runtime";
18239
+ function Dialog2({
18240
+ ...props
18241
+ }) {
18242
+ return /* @__PURE__ */ jsx19(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
18243
+ }
18244
+ function DialogPortal({
18245
+ ...props
18246
+ }) {
18247
+ return /* @__PURE__ */ jsx19(DialogPrimitive.Portal, { ...props });
18248
+ }
18249
+ function DialogOverlay({
18250
+ className,
18251
+ ...props
18252
+ }) {
18253
+ return /* @__PURE__ */ jsx19(
18254
+ DialogPrimitive.Overlay,
18255
+ {
18256
+ "data-slot": "dialog-overlay",
18257
+ "data-ohw-link-modal-root": "",
18258
+ className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
18259
+ ...props
18260
+ }
18261
+ );
18262
+ }
18263
+ var DialogContent = React9.forwardRef(
18264
+ ({ className, children, showCloseButton = true, container, ...props }, ref) => {
18265
+ const positionMode = container ? "absolute" : "fixed";
18266
+ return /* @__PURE__ */ jsxs10(DialogPortal, { container: container ?? void 0, children: [
18267
+ /* @__PURE__ */ jsx19(DialogOverlay, { className: cn(positionMode, "inset-0") }),
18268
+ /* @__PURE__ */ jsxs10(
18269
+ DialogPrimitive.Content,
18270
+ {
18271
+ ref,
18272
+ "data-slot": "dialog-content",
18273
+ className: cn(
18274
+ positionMode,
18275
+ "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",
18276
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
18277
+ container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
18278
+ className
18279
+ ),
18280
+ onMouseDown: (e) => e.stopPropagation(),
18281
+ ...props,
18282
+ children: [
18283
+ children,
18284
+ showCloseButton ? /* @__PURE__ */ jsx19(
18285
+ DialogPrimitive.Close,
18286
+ {
18287
+ type: "button",
18288
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
18289
+ "aria-label": "Close",
18290
+ children: /* @__PURE__ */ jsx19(X2, { size: 16, "aria-hidden": true })
18291
+ }
18292
+ ) : null
18293
+ ]
18294
+ }
18295
+ )
18296
+ ] });
18297
+ }
18298
+ );
18299
+ DialogContent.displayName = DialogPrimitive.Content.displayName;
18300
+ function DialogHeader({
18301
+ className,
18302
+ ...props
18303
+ }) {
18304
+ return /* @__PURE__ */ jsx19("div", { className: cn("flex flex-col gap-1.5", className), ...props });
18305
+ }
18306
+ function DialogFooter({
18307
+ className,
18308
+ ...props
18309
+ }) {
18310
+ return /* @__PURE__ */ jsx19(
18311
+ "div",
18312
+ {
18313
+ className: cn("flex items-center justify-end gap-2", className),
18314
+ ...props
18315
+ }
18316
+ );
18317
+ }
18318
+ var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx19(
18319
+ DialogPrimitive.Title,
18320
+ {
18321
+ ref,
18322
+ className: cn(
18323
+ "text-lg font-semibold leading-none tracking-tight text-card-foreground",
18324
+ className
18325
+ ),
18326
+ ...props
18327
+ }
18328
+ ));
18329
+ DialogTitle.displayName = DialogPrimitive.Title.displayName;
18330
+ var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx19(
18331
+ DialogPrimitive.Description,
18332
+ {
18333
+ ref,
18334
+ className: cn("text-sm text-muted-foreground", className),
18335
+ ...props
18336
+ }
18337
+ ));
18338
+ DialogDescription.displayName = DialogPrimitive.Description.displayName;
18339
+ var DialogClose = DialogPrimitive.Close;
18340
+
18175
18341
  // src/ui/ai-section/AiMediaStripOverlay.tsx
18176
- import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs10 } from "react/jsx-runtime";
18177
- var PRIMARY3 = "#0885FE";
18178
- var OHW_BLUE = `var(--ohw-primary, ${PRIMARY3})`;
18179
- var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY3}) 60%, #000000)`;
18342
+ import { Fragment as Fragment6, jsx as jsx20, jsxs as jsxs11 } from "react/jsx-runtime";
18343
+ var PRIMARY3 = "var(--ohw-primary, #0885FE)";
18344
+ 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]";
18180
18345
  function findMediaStripItemElement(sectionId, index) {
18181
18346
  const sectionEl = findByInstanceId(sectionId);
18182
18347
  if (!sectionEl) return null;
@@ -18186,14 +18351,35 @@ function findMediaStripItemElement(sectionId, index) {
18186
18351
  }
18187
18352
  return null;
18188
18353
  }
18189
- function readRect2(ref) {
18190
- const el = ref && findMediaStripItemElement(ref.sectionId, ref.index);
18354
+ function itemRefFromTarget(target) {
18355
+ if (!(target instanceof Element)) return null;
18356
+ if (target.closest(CHROME_SELECTOR)) return null;
18357
+ const itemEl = target.closest("[data-ohw-media-strip-item]");
18358
+ if (!itemEl) return null;
18359
+ const sectionEl = itemEl.closest("[data-ohw-section]");
18360
+ const unit = sectionEl ? movableUnit(sectionEl) : null;
18361
+ const sectionId = unit ? instanceIdOf(unit) || null : null;
18362
+ const index = Number(itemEl.dataset.ohwMediaStripIndex);
18363
+ if (!sectionId || Number.isNaN(index)) return null;
18364
+ return { sectionId, index };
18365
+ }
18366
+ function sameRef(a, b) {
18367
+ return a?.sectionId === b?.sectionId && a?.index === b?.index;
18368
+ }
18369
+ function rectOf(el) {
18191
18370
  if (!el) return null;
18192
18371
  const r2 = el.getBoundingClientRect();
18193
18372
  if (r2.width <= 0 || r2.height <= 0) return null;
18194
18373
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
18195
18374
  }
18196
- function useLiveItemRect(ref) {
18375
+ function readRect2(ref) {
18376
+ return rectOf(ref && findMediaStripItemElement(ref.sectionId, ref.index));
18377
+ }
18378
+ function readStripRect(ref) {
18379
+ const item = ref && findMediaStripItemElement(ref.sectionId, ref.index);
18380
+ return rectOf(item?.closest("[data-ohw-media-strip]") ?? item);
18381
+ }
18382
+ function useLiveItemRect(ref, strip = false) {
18197
18383
  const [rect, setRect] = useState8(null);
18198
18384
  useEffect6(() => {
18199
18385
  if (!ref) {
@@ -18201,7 +18387,7 @@ function useLiveItemRect(ref) {
18201
18387
  return;
18202
18388
  }
18203
18389
  const update = () => {
18204
- const next = readRect2(ref);
18390
+ const next = strip ? readStripRect(ref) : readRect2(ref);
18205
18391
  setRect(
18206
18392
  (prev) => prev && next && prev.top === next.top && prev.left === next.left && prev.width === next.width && prev.height === next.height ? prev : next
18207
18393
  );
@@ -18216,9 +18402,49 @@ function useLiveItemRect(ref) {
18216
18402
  window.removeEventListener("resize", update);
18217
18403
  clearInterval(interval);
18218
18404
  };
18219
- }, [ref?.sectionId, ref?.index]);
18405
+ }, [ref?.sectionId, ref?.index, strip]);
18220
18406
  return rect;
18221
18407
  }
18408
+ var TOOLBAR_STRIP_GAP = 52;
18409
+ var TOOLBAR_STRIP_GAP_MOBILE = 42;
18410
+ var TOOLBAR_EDGE_MARGIN2 = 4;
18411
+ function StripToolbarSlot({ stripRect, children }) {
18412
+ const slotRef = useRef5(null);
18413
+ const [shift, setShift] = useState8(0);
18414
+ const centerX = stripRect.left + stripRect.width / 2;
18415
+ useLayoutEffect4(() => {
18416
+ const slot = slotRef.current;
18417
+ if (!slot) return;
18418
+ const clamp = () => {
18419
+ const w = slot.offsetWidth;
18420
+ if (w <= 0) return;
18421
+ const half = w / 2;
18422
+ const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
18423
+ setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
18424
+ };
18425
+ clamp();
18426
+ const ro = new ResizeObserver(clamp);
18427
+ ro.observe(slot);
18428
+ return () => ro.disconnect();
18429
+ }, [centerX, children]);
18430
+ const mobile = window.innerWidth <= 480;
18431
+ return /* @__PURE__ */ jsx20(
18432
+ "div",
18433
+ {
18434
+ ref: slotRef,
18435
+ "data-ohw-bridge": "",
18436
+ "data-ohw-item-interaction": "",
18437
+ className: "pointer-events-auto fixed",
18438
+ style: {
18439
+ top: stripRect.top - (mobile ? TOOLBAR_STRIP_GAP_MOBILE : TOOLBAR_STRIP_GAP),
18440
+ left: centerX + shift,
18441
+ transform: "translate(-50%, -100%)",
18442
+ zIndex: 2147483645
18443
+ },
18444
+ children
18445
+ }
18446
+ );
18447
+ }
18222
18448
  function requestValidateTree(postToParent2, tree) {
18223
18449
  return new Promise((resolve) => {
18224
18450
  const id = `ai-validate-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -18255,221 +18481,177 @@ function requestAssetPick(postToParent2) {
18255
18481
  postToParent2({ type: "ow:asset-pick", id });
18256
18482
  });
18257
18483
  }
18258
- function Pill({
18484
+ function ToolbarAction({
18259
18485
  label,
18260
18486
  onClick,
18261
- disabled,
18262
- active
18487
+ disabled = false,
18488
+ active = false,
18489
+ children
18263
18490
  }) {
18264
- return /* @__PURE__ */ jsx19(
18265
- "button",
18266
- {
18267
- type: "button",
18268
- onClick,
18269
- disabled,
18270
- style: {
18271
- height: 28,
18272
- padding: "0 10px",
18273
- display: "inline-flex",
18274
- alignItems: "center",
18275
- gap: 4,
18276
- borderRadius: 999,
18277
- border: "none",
18278
- cursor: disabled ? "default" : "pointer",
18279
- background: active ? OHW_BLUE_DARK : "#ffffff",
18280
- color: active ? "#ffffff" : OHW_BLUE_DARK,
18281
- opacity: disabled ? 0.4 : 1,
18282
- fontSize: 12,
18283
- lineHeight: "16px",
18284
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18285
- whiteSpace: "nowrap",
18286
- flex: "0 0 auto"
18287
- },
18288
- children: label
18289
- }
18290
- );
18491
+ const button = /* @__PURE__ */ jsx20(CustomToolbarButton, { type: "button", "aria-label": label, disabled, active, onClick, className: "size-8", children });
18492
+ return /* @__PURE__ */ jsxs11(Tooltip, { children: [
18493
+ /* @__PURE__ */ jsx20(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ jsx20("span", { className: "inline-flex", children: button }) : button }),
18494
+ /* @__PURE__ */ jsx20(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
18495
+ ] });
18291
18496
  }
18292
- function AltInput({ value, onCommit }) {
18293
- const [draft, setDraft] = useState8(value);
18294
- useEffect6(() => setDraft(value), [value]);
18295
- return /* @__PURE__ */ jsxs10(
18296
- "div",
18297
- {
18298
- style: {
18299
- display: "flex",
18300
- alignItems: "center",
18301
- gap: 6,
18302
- height: 28,
18303
- padding: "0 12px",
18304
- borderRadius: 999,
18305
- background: "#ffffff",
18306
- width: "100%",
18307
- maxWidth: 260,
18308
- boxSizing: "border-box"
18309
- },
18310
- children: [
18311
- /* @__PURE__ */ jsx19(
18312
- "span",
18313
- {
18314
- style: {
18315
- flex: "0 0 auto",
18316
- color: OHW_BLUE_DARK,
18317
- fontSize: 10,
18318
- fontWeight: 700,
18319
- textTransform: "uppercase",
18320
- letterSpacing: 0.4
18321
- },
18322
- children: "Alt"
18323
- }
18324
- ),
18325
- /* @__PURE__ */ jsx19(
18326
- "input",
18327
- {
18328
- type: "text",
18329
- value: draft,
18330
- onChange: (e) => setDraft(e.target.value),
18331
- onBlur: () => {
18332
- if (draft !== value) onCommit(draft);
18333
- },
18334
- onKeyDown: (e) => {
18335
- if (e.key === "Enter") e.currentTarget.blur();
18336
- if (e.key === "Escape") {
18337
- setDraft(value);
18338
- e.currentTarget.blur();
18339
- }
18340
- },
18341
- placeholder: "Describe this image",
18342
- "aria-label": "Image alt text",
18343
- style: {
18344
- flex: 1,
18345
- minWidth: 0,
18346
- border: "none",
18347
- outline: "none",
18348
- background: "transparent",
18349
- fontSize: 12,
18350
- lineHeight: "16px",
18351
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18352
- color: "#1f2937"
18353
- }
18354
- }
18355
- )
18356
- ]
18357
- }
18358
- );
18497
+ function ToolbarSeparator() {
18498
+ return /* @__PURE__ */ jsx20("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
18359
18499
  }
18360
- var VARIANTS = [
18361
- { value: "logos", label: "Logos" },
18362
- { value: "reel", label: "Reel" }
18363
- ];
18364
- var ALIGNS = [
18365
- { value: "left", label: "Left" },
18366
- { value: "center", label: "Center" },
18367
- { value: "right", label: "Right" }
18368
- ];
18369
- var HOVERS = [
18370
- { value: "none", label: "None" },
18371
- { value: "lift", label: "Lift" },
18372
- { value: "zoom", label: "Zoom" },
18373
- { value: "fade", label: "Fade" }
18374
- ];
18375
- var TREATMENTS = [
18376
- { value: "color", label: "Color" },
18377
- { value: "grayscale", label: "Grayscale" }
18378
- ];
18379
- var HTTP_URL_RE = /^https?:\/\/\S+$/i;
18380
- function LinkInput({ value, onCommit }) {
18381
- const [draft, setDraft] = useState8(value);
18382
- const [invalid, setInvalid] = useState8(false);
18383
- useEffect6(() => {
18384
- setDraft(value);
18385
- setInvalid(false);
18386
- }, [value]);
18387
- const commit = () => {
18388
- const next = draft.trim();
18389
- if (next === value) return setInvalid(false);
18390
- if (next && !HTTP_URL_RE.test(next)) return setInvalid(true);
18391
- setInvalid(false);
18392
- onCommit(next);
18393
- };
18394
- return /* @__PURE__ */ jsxs10(
18395
- "div",
18396
- {
18397
- style: {
18398
- display: "flex",
18399
- alignItems: "center",
18400
- gap: 6,
18401
- height: 28,
18402
- padding: "0 12px",
18403
- borderRadius: 999,
18404
- background: "#ffffff",
18405
- width: "100%",
18406
- maxWidth: 260,
18407
- boxSizing: "border-box",
18408
- boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
18409
- },
18410
- children: [
18411
- /* @__PURE__ */ jsx19(
18412
- "span",
18413
- {
18414
- style: {
18415
- flex: "0 0 auto",
18416
- color: OHW_BLUE_DARK,
18417
- fontSize: 10,
18418
- fontWeight: 700,
18419
- textTransform: "uppercase",
18420
- letterSpacing: 0.4
18421
- },
18422
- children: "Link"
18423
- }
18424
- ),
18425
- /* @__PURE__ */ jsx19(
18426
- "input",
18500
+ function OptionMenu({
18501
+ label,
18502
+ value,
18503
+ options,
18504
+ onChange
18505
+ }) {
18506
+ const current = options.find((o) => o.value === value) ?? options[0];
18507
+ return /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
18508
+ /* @__PURE__ */ jsx20("span", { className: "text-xs text-muted-foreground", children: label }),
18509
+ /* @__PURE__ */ jsxs11(DropdownMenu, { modal: false, children: [
18510
+ /* @__PURE__ */ jsx20(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs11(
18511
+ "button",
18512
+ {
18513
+ type: "button",
18514
+ "aria-label": label,
18515
+ 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",
18516
+ children: [
18517
+ /* @__PURE__ */ jsx20("span", { className: "flex-1 text-left", children: current.label }),
18518
+ /* @__PURE__ */ jsx20(ChevronDown3, { size: 16, style: { pointerEvents: "none" } })
18519
+ ]
18520
+ }
18521
+ ) }),
18522
+ /* @__PURE__ */ jsx20(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[132px] min-w-[132px] p-1", children: options.map((option) => {
18523
+ const selected = option.value === value;
18524
+ return /* @__PURE__ */ jsxs11(
18525
+ DropdownMenuItem,
18427
18526
  {
18428
- type: "text",
18429
- inputMode: "url",
18430
- value: draft,
18431
- onChange: (e) => setDraft(e.target.value),
18432
- onBlur: commit,
18433
- onKeyDown: (e) => {
18434
- if (e.key === "Enter") e.currentTarget.blur();
18435
- if (e.key === "Escape") {
18436
- setDraft(value);
18437
- setInvalid(false);
18438
- e.currentTarget.blur();
18439
- }
18440
- },
18441
- placeholder: "https://example.com",
18442
- "aria-label": "Image link URL",
18443
- "aria-invalid": invalid,
18444
- style: {
18445
- flex: 1,
18446
- minWidth: 0,
18447
- border: "none",
18448
- outline: "none",
18449
- background: "transparent",
18450
- fontSize: 12,
18451
- lineHeight: "16px",
18452
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18453
- color: "#1f2937"
18454
- }
18455
- }
18456
- )
18457
- ]
18527
+ role: "menuitemradio",
18528
+ "aria-checked": selected,
18529
+ onSelect: () => onChange(option.value),
18530
+ className: cn("grid grid-cols-[1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
18531
+ children: [
18532
+ /* @__PURE__ */ jsx20("span", { children: option.label }),
18533
+ selected && /* @__PURE__ */ jsx20(Check2, { size: 16, className: "text-bridge-primary" })
18534
+ ]
18535
+ },
18536
+ option.value
18537
+ );
18538
+ }) })
18539
+ ] })
18540
+ ] });
18541
+ }
18542
+ var VARIANTS = [
18543
+ { value: "logos", label: "Logos" },
18544
+ { value: "reel", label: "Reel" }
18545
+ ];
18546
+ var ALIGNS = [
18547
+ { value: "left", label: "Align left", Icon: AlignLeft },
18548
+ { value: "center", label: "Align center", Icon: AlignCenter },
18549
+ { value: "right", label: "Align right", Icon: AlignRight }
18550
+ ];
18551
+ var HOVERS = [
18552
+ { value: "none", label: "None" },
18553
+ { value: "lift", label: "Lift" },
18554
+ { value: "zoom", label: "Zoom" },
18555
+ { value: "fade", label: "Fade" }
18556
+ ];
18557
+ var HTTP_URL_RE = /^https?:\/\/\S+$/i;
18558
+ function LinkModal({
18559
+ href,
18560
+ linkTarget,
18561
+ onClose,
18562
+ onSave
18563
+ }) {
18564
+ const [draft, setDraft] = useState8(href);
18565
+ const [newTab, setNewTab] = useState8(linkTarget !== "same-tab");
18566
+ const [invalid, setInvalid] = useState8(false);
18567
+ const save = () => {
18568
+ const next = draft.trim();
18569
+ if (next && !HTTP_URL_RE.test(next)) {
18570
+ setInvalid(true);
18571
+ return;
18458
18572
  }
18459
- );
18573
+ onSave(next, newTab ? "new-tab" : "same-tab");
18574
+ onClose();
18575
+ };
18576
+ return /* @__PURE__ */ jsx20(Dialog2, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs11(DialogContent, { "data-ohw-link-modal-root": "", className: "max-w-[400px] gap-4 p-5", children: [
18577
+ /* @__PURE__ */ jsx20(DialogHeader, { children: /* @__PURE__ */ jsx20(DialogTitle, { className: "text-base", children: "Image link" }) }),
18578
+ /* @__PURE__ */ jsxs11("div", { className: "flex flex-col gap-3", children: [
18579
+ /* @__PURE__ */ jsx20(
18580
+ "input",
18581
+ {
18582
+ autoFocus: true,
18583
+ type: "text",
18584
+ inputMode: "url",
18585
+ value: draft,
18586
+ onChange: (e) => {
18587
+ setDraft(e.target.value);
18588
+ setInvalid(false);
18589
+ },
18590
+ onKeyDown: (e) => {
18591
+ if (e.key === "Enter") save();
18592
+ },
18593
+ placeholder: "https://example.com",
18594
+ "aria-label": "Image link URL",
18595
+ "aria-invalid": invalid,
18596
+ className: cn(
18597
+ "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",
18598
+ invalid && "border-destructive focus:border-destructive"
18599
+ )
18600
+ }
18601
+ ),
18602
+ invalid && /* @__PURE__ */ jsx20("p", { className: "text-xs text-destructive", children: "Enter a full URL starting with http:// or https://" }),
18603
+ /* @__PURE__ */ jsxs11("label", { className: "flex items-center gap-2 text-[13px] text-foreground", children: [
18604
+ /* @__PURE__ */ jsx20("input", { type: "checkbox", checked: newTab, onChange: (e) => setNewTab(e.target.checked) }),
18605
+ "Open in new tab"
18606
+ ] })
18607
+ ] }),
18608
+ /* @__PURE__ */ jsxs11(DialogFooter, { children: [
18609
+ href && /* @__PURE__ */ jsx20(
18610
+ "button",
18611
+ {
18612
+ type: "button",
18613
+ onClick: () => {
18614
+ onSave("", "new-tab");
18615
+ onClose();
18616
+ },
18617
+ className: "mr-auto rounded-[6px] px-3 py-2 text-[13px] font-medium text-destructive transition-colors hover:bg-muted",
18618
+ children: "Remove link"
18619
+ }
18620
+ ),
18621
+ /* @__PURE__ */ jsx20(
18622
+ "button",
18623
+ {
18624
+ type: "button",
18625
+ onClick: onClose,
18626
+ className: "rounded-[6px] px-3 py-2 text-[13px] font-medium text-foreground transition-colors hover:bg-muted",
18627
+ children: "Cancel"
18628
+ }
18629
+ ),
18630
+ /* @__PURE__ */ jsx20(
18631
+ "button",
18632
+ {
18633
+ type: "button",
18634
+ onClick: save,
18635
+ className: "rounded-[6px] bg-bridge-primary px-3 py-2 text-[13px] font-medium text-primary-foreground transition-opacity hover:opacity-90",
18636
+ children: "Save"
18637
+ }
18638
+ )
18639
+ ] })
18640
+ ] }) });
18460
18641
  }
18461
18642
  function MediaStripToolbar({
18462
18643
  entry,
18463
18644
  index,
18464
18645
  pending,
18465
18646
  error,
18647
+ itemEditOn,
18648
+ onToggleItemEdit,
18466
18649
  onSetVariant,
18467
18650
  onSetAlign,
18468
18651
  onSetHover,
18469
18652
  onSetTreatment,
18470
18653
  onResize,
18471
- onSetAlt,
18472
- onSetLink,
18654
+ onOpenLink,
18473
18655
  onAddItem,
18474
18656
  onRemoveItem,
18475
18657
  onChangeImage,
@@ -18478,81 +18660,102 @@ function MediaStripToolbar({
18478
18660
  }) {
18479
18661
  const items = mediaStripItems(entry.tree);
18480
18662
  const itemCount = items.length;
18481
- const currentAlt = items[index]?.alt ?? "";
18482
- const currentHref = items[index]?.href ?? "";
18483
- const currentTarget = items[index]?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
18484
18663
  const slots = findMediaStripSlots(entry.tree);
18485
18664
  const variant = slots?.variant === "reel" ? "reel" : "logos";
18486
18665
  const align = slots?.align === "left" || slots?.align === "right" ? slots.align : "center";
18487
18666
  const hover = slots?.hover === "none" || slots?.hover === "lift" || slots?.hover === "zoom" || slots?.hover === "fade" ? slots.hover : "lift";
18488
18667
  const treatment = slots?.treatment === "color" || slots?.treatment === "grayscale" ? slots.treatment : variant === "logos" ? "grayscale" : "color";
18489
18668
  const sizeValue = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
18490
- const isMobile = window.innerWidth <= 480;
18491
- const rowStyle = isMobile ? {
18492
- display: "flex",
18493
- gap: 6,
18494
- flexWrap: "nowrap",
18495
- justifyContent: "center",
18496
- overflowX: "auto",
18497
- WebkitOverflowScrolling: "touch",
18498
- width: "100%",
18499
- scrollbarWidth: "none"
18500
- } : { display: "flex", gap: 6, flexWrap: "nowrap", justifyContent: "center" };
18501
- return /* @__PURE__ */ jsxs10(
18669
+ const mobile = window.innerWidth <= 480;
18670
+ const gatedLabel = (label) => itemEditOn ? label : "Turn on the star to edit this image";
18671
+ return /* @__PURE__ */ jsx20(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsxs11(
18502
18672
  "div",
18503
18673
  {
18504
18674
  "data-ohw-bridge": "",
18505
18675
  "data-ohw-toolbar": "",
18506
18676
  onMouseDown: (e) => e.stopPropagation(),
18507
- style: {
18508
- display: "flex",
18509
- flexDirection: "column",
18510
- gap: 6,
18511
- alignItems: "center",
18512
- background: OHW_BLUE,
18513
- padding: 8,
18514
- borderRadius: 12,
18515
- boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
18516
- pointerEvents: pending ? "none" : "auto",
18517
- opacity: pending ? 0.6 : 1,
18518
- maxWidth: isMobile ? "calc(100vw - 24px)" : "none"
18519
- },
18677
+ className: cn("flex flex-col items-center gap-1.5 font-sans", pending && "pointer-events-none opacity-60"),
18678
+ style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
18520
18679
  children: [
18521
- /* @__PURE__ */ jsxs10("div", { style: rowStyle, children: [
18522
- VARIANTS.map((v) => /* @__PURE__ */ jsx19(Pill, { label: v.label, active: variant === v.value, onClick: () => onSetVariant(v.value) }, v.value)),
18523
- ALIGNS.map((a) => /* @__PURE__ */ jsx19(Pill, { label: a.label, active: align === a.value, onClick: () => onSetAlign(a.value) }, a.value))
18524
- ] }),
18525
- /* @__PURE__ */ jsxs10("div", { style: rowStyle, children: [
18526
- HOVERS.map((h) => /* @__PURE__ */ jsx19(Pill, { label: h.label, active: hover === h.value, onClick: () => onSetHover(h.value) }, h.value)),
18527
- TREATMENTS.map((t) => /* @__PURE__ */ jsx19(Pill, { label: t.label, active: treatment === t.value, onClick: () => onSetTreatment(t.value) }, t.value))
18528
- ] }),
18529
- /* @__PURE__ */ jsxs10("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
18530
- /* @__PURE__ */ jsx19(Pill, { label: "\u2212", onClick: () => onResize(-8) }),
18531
- /* @__PURE__ */ jsxs10("span", { style: { color: "#ffffff", fontSize: 12, minWidth: 34, textAlign: "center" }, children: [
18532
- sizeValue,
18533
- "px"
18534
- ] }),
18535
- /* @__PURE__ */ jsx19(Pill, { label: "+", onClick: () => onResize(8) })
18536
- ] }),
18537
- /* @__PURE__ */ jsx19("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ jsx19(AltInput, { value: currentAlt, onCommit: onSetAlt }) }),
18538
- /* @__PURE__ */ jsx19("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ jsx19(LinkInput, { value: currentHref, onCommit: (href) => onSetLink(href, currentTarget) }) }),
18539
- currentHref && /* @__PURE__ */ jsxs10("div", { style: rowStyle, children: [
18540
- /* @__PURE__ */ jsx19(Pill, { label: "New tab", active: currentTarget === "new-tab", onClick: () => onSetLink(currentHref, "new-tab") }),
18541
- /* @__PURE__ */ jsx19(Pill, { label: "Same tab", active: currentTarget === "same-tab", onClick: () => onSetLink(currentHref, "same-tab") })
18542
- ] }),
18543
- /* @__PURE__ */ jsxs10("div", { style: rowStyle, children: [
18544
- /* @__PURE__ */ jsx19(Pill, { label: "Change Image", onClick: onChangeImage }),
18545
- /* @__PURE__ */ jsx19(Pill, { label: "+ Item", onClick: onAddItem }),
18546
- /* @__PURE__ */ jsx19(Pill, { label: "\u2212 Item", onClick: onRemoveItem, disabled: itemCount <= 1 })
18547
- ] }),
18548
- /* @__PURE__ */ jsxs10("div", { style: rowStyle, children: [
18549
- /* @__PURE__ */ jsx19(Pill, { label: "\u2190 Move", onClick: onMoveLeft, disabled: index <= 0 }),
18550
- /* @__PURE__ */ jsx19(Pill, { label: "\u2192 Move", onClick: onMoveRight, disabled: index >= itemCount - 1 })
18551
- ] }),
18552
- error && /* @__PURE__ */ jsx19("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
18680
+ /* @__PURE__ */ jsxs11(
18681
+ "div",
18682
+ {
18683
+ className: cn(
18684
+ "flex items-center gap-1 rounded-[10px] border border-border bg-background p-1 whitespace-nowrap",
18685
+ "shadow-[0px_10px_15px_-3px_rgba(0,0,0,0.1),0px_4px_6px_-4px_rgba(0,0,0,0.1)]",
18686
+ mobile ? "flex-wrap justify-center" : "flex-nowrap"
18687
+ ),
18688
+ children: [
18689
+ /* @__PURE__ */ jsxs11(Tooltip, { children: [
18690
+ /* @__PURE__ */ jsx20(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx20(
18691
+ CustomToolbarButton,
18692
+ {
18693
+ type: "button",
18694
+ "aria-label": itemEditOn ? "Done editing this image" : "Edit this image",
18695
+ "aria-pressed": itemEditOn,
18696
+ onClick: onToggleItemEdit,
18697
+ className: "group size-8",
18698
+ children: /* @__PURE__ */ jsx20(
18699
+ Star,
18700
+ {
18701
+ size: 16,
18702
+ fill: "currentColor",
18703
+ strokeWidth: 0,
18704
+ className: cn(
18705
+ "transition-colors",
18706
+ itemEditOn ? "text-[#f59e0b] group-hover:text-[#d97706]" : "text-[#d6d3d1] group-hover:text-[#fbbf24]"
18707
+ ),
18708
+ style: { pointerEvents: "none" }
18709
+ }
18710
+ )
18711
+ }
18712
+ ) }),
18713
+ /* @__PURE__ */ jsx20(TooltipContent, { side: "bottom", sideOffset: 8, children: itemEditOn ? "Done editing this image" : "Edit this image" })
18714
+ ] }),
18715
+ /* @__PURE__ */ jsx20(ToolbarSeparator, {}),
18716
+ /* @__PURE__ */ jsx20(ToolbarAction, { label: gatedLabel("Change image"), onClick: onChangeImage, disabled: !itemEditOn, children: /* @__PURE__ */ jsx20(ImageIcon2, { size: 16, style: { pointerEvents: "none" } }) }),
18717
+ /* @__PURE__ */ jsx20(ToolbarAction, { label: gatedLabel("Link"), onClick: onOpenLink, disabled: !itemEditOn, children: /* @__PURE__ */ jsx20(LinkIcon, { size: 16, style: { pointerEvents: "none" } }) }),
18718
+ /* @__PURE__ */ jsx20(ToolbarAction, { label: gatedLabel("Move left"), onClick: onMoveLeft, disabled: !itemEditOn || index <= 0, children: /* @__PURE__ */ jsx20(ArrowLeft2, { size: 16, style: { pointerEvents: "none" } }) }),
18719
+ /* @__PURE__ */ jsx20(
18720
+ ToolbarAction,
18721
+ {
18722
+ label: gatedLabel("Move right"),
18723
+ onClick: onMoveRight,
18724
+ disabled: !itemEditOn || index >= itemCount - 1,
18725
+ children: /* @__PURE__ */ jsx20(ArrowRight2, { size: 16, style: { pointerEvents: "none" } })
18726
+ }
18727
+ ),
18728
+ /* @__PURE__ */ jsx20(ToolbarAction, { label: gatedLabel("Remove image"), onClick: onRemoveItem, disabled: !itemEditOn || itemCount <= 1, children: /* @__PURE__ */ jsx20(ImageMinus, { size: 16, style: { pointerEvents: "none" } }) }),
18729
+ /* @__PURE__ */ jsx20(ToolbarSeparator, {}),
18730
+ /* @__PURE__ */ jsx20(OptionMenu, { label: "Layout", value: variant, options: VARIANTS, onChange: onSetVariant }),
18731
+ /* @__PURE__ */ jsx20(OptionMenu, { label: "Hover", value: hover, options: HOVERS, onChange: onSetHover }),
18732
+ /* @__PURE__ */ jsx20(
18733
+ ToolbarAction,
18734
+ {
18735
+ label: treatment === "grayscale" ? "Show in color" : "Show grayscale",
18736
+ active: treatment === "grayscale",
18737
+ onClick: () => onSetTreatment(treatment === "grayscale" ? "color" : "grayscale"),
18738
+ children: /* @__PURE__ */ jsx20(Palette, { size: 16, style: { pointerEvents: "none" } })
18739
+ }
18740
+ ),
18741
+ /* @__PURE__ */ jsx20(ToolbarSeparator, {}),
18742
+ ALIGNS.map(({ value, label, Icon }) => /* @__PURE__ */ jsx20(ToolbarAction, { label, active: align === value, onClick: () => onSetAlign(value), children: /* @__PURE__ */ jsx20(Icon, { size: 16, style: { pointerEvents: "none" } }) }, value)),
18743
+ /* @__PURE__ */ jsx20(ToolbarSeparator, {}),
18744
+ /* @__PURE__ */ jsx20(ToolbarAction, { label: "Smaller", onClick: () => onResize(-8), children: /* @__PURE__ */ jsx20(Minus, { size: 16, style: { pointerEvents: "none" } }) }),
18745
+ /* @__PURE__ */ jsxs11("span", { className: "min-w-[40px] text-center text-xs tabular-nums text-muted-foreground", children: [
18746
+ sizeValue,
18747
+ "px"
18748
+ ] }),
18749
+ /* @__PURE__ */ jsx20(ToolbarAction, { label: "Larger", onClick: () => onResize(8), children: /* @__PURE__ */ jsx20(Plus2, { size: 16, style: { pointerEvents: "none" } }) }),
18750
+ /* @__PURE__ */ jsx20(ToolbarSeparator, {}),
18751
+ /* @__PURE__ */ jsx20(ToolbarAction, { label: "Add image", onClick: onAddItem, children: /* @__PURE__ */ jsx20(ImagePlus, { size: 16, style: { pointerEvents: "none" } }) })
18752
+ ]
18753
+ }
18754
+ ),
18755
+ error && /* @__PURE__ */ jsx20("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
18553
18756
  ]
18554
18757
  }
18555
- );
18758
+ ) });
18556
18759
  }
18557
18760
  function findMediaStripSlots(tree) {
18558
18761
  const stack = tree.rows.flatMap((row) => row.blocks);
@@ -18571,31 +18774,23 @@ function AiMediaStripOverlay({
18571
18774
  }) {
18572
18775
  const [hover, setHover] = useState8(null);
18573
18776
  const [active, setActive] = useState8(null);
18777
+ const [itemEditOn, setItemEditOn] = useState8(false);
18778
+ const [linkOpen, setLinkOpen] = useState8(false);
18574
18779
  const [pending, setPending] = useState8(false);
18575
18780
  const [error, setError] = useState8(null);
18576
18781
  const activeRef = useRef5(null);
18577
18782
  activeRef.current = active;
18783
+ const linkOpenRef = useRef5(false);
18784
+ linkOpenRef.current = linkOpen;
18578
18785
  useEffect6(() => {
18579
18786
  const onMove = (e) => {
18787
+ const ref = itemRefFromTarget(e.target);
18580
18788
  const t = e.target;
18581
- if (!(t instanceof HTMLElement)) return;
18582
- 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]")) {
18583
- return;
18584
- }
18585
- const itemEl = t.closest("[data-ohw-media-strip-item]");
18586
- if (!itemEl) {
18587
- setHover(null);
18588
- return;
18589
- }
18590
- const sectionEl = itemEl.closest("[data-ohw-section]");
18591
- const unit = sectionEl ? movableUnit(sectionEl) : null;
18592
- const sectionId = unit ? instanceIdOf(unit) || null : null;
18593
- const index = Number(itemEl.dataset.ohwMediaStripIndex);
18594
- if (!sectionId || Number.isNaN(index)) {
18595
- setHover(null);
18596
- return;
18597
- }
18598
- setHover({ sectionId, index });
18789
+ const overEditable = Boolean(t?.closest?.("[data-ohw-editable]"));
18790
+ setHover((prev) => {
18791
+ const next = ref && !overEditable ? ref : null;
18792
+ return sameRef(prev, next) ? prev : next;
18793
+ });
18599
18794
  };
18600
18795
  const onLeave = () => setHover(null);
18601
18796
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -18607,23 +18802,25 @@ function AiMediaStripOverlay({
18607
18802
  }, []);
18608
18803
  useEffect6(() => {
18609
18804
  const onClick = (e) => {
18610
- const current = activeRef.current;
18611
- if (!current) return;
18612
18805
  const t = e.target;
18613
- if (!(t instanceof HTMLElement)) return;
18614
- if (t.closest("[data-ohw-item-interaction]")) return;
18615
- const itemEl = findMediaStripItemElement(current.sectionId, current.index);
18616
- if (itemEl && itemEl.contains(t)) return;
18617
- setActive(null);
18806
+ if (!(t instanceof Element)) return;
18807
+ if (t.closest(CHROME_SELECTOR)) return;
18808
+ const ref = itemRefFromTarget(t);
18809
+ if (sameRef(activeRef.current, ref)) return;
18810
+ if (ref) clearInteractionChromeRef.current();
18811
+ setItemEditOn(false);
18618
18812
  setError(null);
18813
+ setActive(ref);
18619
18814
  };
18620
18815
  document.addEventListener("click", onClick, true);
18621
18816
  return () => document.removeEventListener("click", onClick, true);
18622
- }, []);
18817
+ }, [clearInteractionChromeRef]);
18623
18818
  useEffect6(() => {
18624
18819
  const onBlur = () => {
18625
18820
  if (!activeRef.current) return;
18626
18821
  setActive(null);
18822
+ setItemEditOn(false);
18823
+ setLinkOpen(false);
18627
18824
  setError(null);
18628
18825
  };
18629
18826
  window.addEventListener("blur", onBlur);
@@ -18633,7 +18830,10 @@ function AiMediaStripOverlay({
18633
18830
  const onKeyDown = (e) => {
18634
18831
  if (e.key !== "Escape") return;
18635
18832
  if (!activeRef.current) return;
18833
+ if (linkOpenRef.current) return;
18834
+ if (document.querySelector('[data-ohw-more-menu][data-state="open"]')) return;
18636
18835
  setActive(null);
18836
+ setItemEditOn(false);
18637
18837
  setError(null);
18638
18838
  };
18639
18839
  document.addEventListener("keydown", onKeyDown);
@@ -18667,137 +18867,133 @@ function AiMediaStripOverlay({
18667
18867
  tree: nextTree,
18668
18868
  label: entry.label,
18669
18869
  media: extraMedia ? { ...entry.media, ...extraMedia } : entry.media,
18670
- ...entry.brand ? { brand: entry.brand } : {}
18870
+ ...entry.brand ? { brand: entry.brand } : {},
18871
+ // The user is already looking at the strip they just edited — recentering the viewport
18872
+ // on every edit reads as the section jumping around.
18873
+ skipScrollIntoView: true
18671
18874
  });
18672
18875
  return true;
18673
18876
  },
18674
18877
  [findEntry, postToParent2, applyTreeLocalRef]
18675
18878
  );
18676
18879
  const activeEntry = active ? findEntry(active.sectionId) : null;
18677
- const hoverHandleRect = useLiveItemRect(!active && hover ? hover : null);
18678
18880
  const activeItemRect = useLiveItemRect(active);
18679
- const TOOLBAR_APPROX_H = 170;
18680
- const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
18681
- return /* @__PURE__ */ jsxs10(Fragment6, { children: [
18682
- hoverHandleRect && hover && /* @__PURE__ */ jsx19(
18683
- "button",
18684
- {
18685
- type: "button",
18686
- "data-ohw-toolbar": "",
18687
- onClick: () => {
18688
- clearInteractionChromeRef.current();
18689
- setActive(hover);
18690
- },
18691
- "aria-label": "Configure media item",
18692
- style: {
18693
- position: "fixed",
18694
- top: hoverHandleRect.top + 8,
18695
- left: hoverHandleRect.left + hoverHandleRect.width - 36,
18696
- width: 28,
18697
- height: 28,
18698
- borderRadius: 8,
18699
- border: `1.5px dashed ${PRIMARY3}`,
18700
- background: "#ffffff",
18701
- color: PRIMARY3,
18702
- display: "flex",
18703
- alignItems: "center",
18704
- justifyContent: "center",
18705
- cursor: "pointer",
18706
- zIndex: 2147483643,
18707
- fontSize: 14,
18708
- fontWeight: 700
18709
- },
18710
- children: /* @__PURE__ */ jsx19(Star, { size: 14, style: { pointerEvents: "none" } })
18711
- }
18712
- ),
18713
- activeItemRect && active && activeEntry && /* @__PURE__ */ jsx19(
18714
- ItemInteractionLayer,
18715
- {
18716
- rect: activeItemRect,
18717
- state: toolbarState,
18718
- toolbar: /* @__PURE__ */ jsx19(
18719
- MediaStripToolbar,
18720
- {
18721
- entry: activeEntry,
18722
- index: active.index,
18723
- pending,
18724
- error,
18725
- onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
18726
- onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
18727
- onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
18728
- onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
18729
- onResize: (delta) => {
18730
- const slots = findMediaStripSlots(activeEntry.tree);
18731
- const variant = slots?.variant === "reel" ? "reel" : "logos";
18732
- const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
18733
- void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
18734
- },
18735
- onSetAlt: (alt) => {
18736
- void runMutation(active.sectionId, (tree) => setMediaStripItemAlt(tree, active.index, alt));
18737
- },
18738
- onSetLink: (href, linkTarget) => {
18881
+ const activeStripRect = useLiveItemRect(active, true);
18882
+ const hoverRect = useLiveItemRect(hover && !sameRef(hover, active) ? hover : null);
18883
+ const activeItems = activeEntry ? mediaStripItems(activeEntry.tree) : [];
18884
+ const activeItem = active ? activeItems[active.index] : void 0;
18885
+ const activeHref = activeItem?.href ?? "";
18886
+ const activeTarget = activeItem?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
18887
+ return /* @__PURE__ */ jsxs11(Fragment6, { children: [
18888
+ hoverRect && /* @__PURE__ */ jsx20(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
18889
+ activeItemRect && activeStripRect && active && activeEntry && /* @__PURE__ */ jsxs11(Fragment6, { children: [
18890
+ /* @__PURE__ */ jsx20(ItemInteractionLayer, { rect: activeItemRect, state: "active-top" }),
18891
+ /* @__PURE__ */ jsx20(StripToolbarSlot, { stripRect: activeStripRect, children: /* @__PURE__ */ jsx20(
18892
+ MediaStripToolbar,
18893
+ {
18894
+ entry: activeEntry,
18895
+ index: active.index,
18896
+ pending,
18897
+ error,
18898
+ itemEditOn,
18899
+ onToggleItemEdit: () => setItemEditOn((on) => !on),
18900
+ onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
18901
+ onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
18902
+ onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
18903
+ onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
18904
+ onResize: (delta) => {
18905
+ const slots = findMediaStripSlots(activeEntry.tree);
18906
+ const variant = slots?.variant === "reel" ? "reel" : "logos";
18907
+ const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
18908
+ void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
18909
+ },
18910
+ onOpenLink: () => setLinkOpen(true),
18911
+ onAddItem: () => {
18912
+ void requestAssetPick(postToParent2).then((picked) => {
18913
+ if (!picked) return;
18914
+ const item = { assetId: picked.assetId, alt: "Logo" };
18739
18915
  void runMutation(
18740
18916
  active.sectionId,
18741
- (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
18742
- );
18743
- },
18744
- onAddItem: () => {
18745
- void requestAssetPick(postToParent2).then((picked) => {
18746
- if (!picked) return;
18747
- const item = { assetId: picked.assetId, alt: "Logo" };
18748
- void runMutation(
18749
- active.sectionId,
18750
- (tree) => addMediaStripItem(tree, item, active.index),
18751
- { [picked.assetId]: picked.url }
18752
- ).then((applied) => {
18753
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
18754
- });
18917
+ (tree) => addMediaStripItem(tree, item, active.index),
18918
+ { [picked.assetId]: picked.url }
18919
+ ).then((applied) => {
18920
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
18755
18921
  });
18756
- },
18757
- onChangeImage: () => {
18758
- void requestAssetPick(postToParent2).then((picked) => {
18759
- if (!picked) return;
18760
- void runMutation(
18761
- active.sectionId,
18762
- (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
18763
- { [picked.assetId]: picked.url }
18764
- );
18765
- });
18766
- },
18767
- onRemoveItem: () => {
18768
- void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
18769
- (applied) => {
18770
- if (applied) setActive(null);
18771
- }
18772
- );
18773
- },
18774
- onMoveLeft: () => {
18775
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
18776
- (applied) => {
18777
- if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
18778
- }
18779
- );
18780
- },
18781
- onMoveRight: () => {
18782
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
18783
- (applied) => {
18784
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
18785
- }
18922
+ });
18923
+ },
18924
+ onChangeImage: () => {
18925
+ void requestAssetPick(postToParent2).then((picked) => {
18926
+ if (!picked) return;
18927
+ void runMutation(
18928
+ active.sectionId,
18929
+ (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
18930
+ { [picked.assetId]: picked.url }
18786
18931
  );
18787
- }
18932
+ });
18933
+ },
18934
+ onRemoveItem: () => {
18935
+ void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
18936
+ (applied) => {
18937
+ if (applied) setActive(null);
18938
+ }
18939
+ );
18940
+ },
18941
+ onMoveLeft: () => {
18942
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
18943
+ (applied) => {
18944
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
18945
+ }
18946
+ );
18947
+ },
18948
+ onMoveRight: () => {
18949
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
18950
+ (applied) => {
18951
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
18952
+ }
18953
+ );
18788
18954
  }
18789
- )
18790
- }
18791
- )
18955
+ }
18956
+ ) }),
18957
+ /* @__PURE__ */ jsx20(
18958
+ "div",
18959
+ {
18960
+ "data-ohw-bridge": "",
18961
+ "data-ohw-item-interaction": "",
18962
+ "aria-hidden": true,
18963
+ className: "pointer-events-none fixed rounded-[4px] px-[7px] py-[3px] font-sans text-[11px] font-medium leading-none text-primary-foreground whitespace-nowrap",
18964
+ style: {
18965
+ top: activeItemRect.top + activeItemRect.height + 10,
18966
+ left: activeItemRect.left - 5,
18967
+ background: PRIMARY3,
18968
+ zIndex: 2147483644
18969
+ },
18970
+ children: "Media item"
18971
+ }
18972
+ ),
18973
+ linkOpen && /* @__PURE__ */ jsx20(
18974
+ LinkModal,
18975
+ {
18976
+ href: activeHref,
18977
+ linkTarget: activeTarget,
18978
+ onClose: () => setLinkOpen(false),
18979
+ onSave: (href, linkTarget) => {
18980
+ void runMutation(
18981
+ active.sectionId,
18982
+ (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
18983
+ );
18984
+ }
18985
+ }
18986
+ )
18987
+ ] })
18792
18988
  ] });
18793
18989
  }
18794
18990
 
18795
18991
  // src/ui/ai-section/AiPricingCardOverlay.tsx
18796
- import { useCallback as useCallback4, useEffect as useEffect7, useLayoutEffect as useLayoutEffect4, useRef as useRef6, useState as useState9 } from "react";
18797
- import { Check as Check2, ChevronDown as ChevronDown3, Minus, Plus as Plus2, Star as Star2 } from "lucide-react";
18798
- import { Fragment as Fragment7, jsx as jsx20, jsxs as jsxs11 } from "react/jsx-runtime";
18992
+ import { useCallback as useCallback4, useEffect as useEffect7, useLayoutEffect as useLayoutEffect5, useRef as useRef6, useState as useState9 } from "react";
18993
+ import { Check as Check3, ChevronDown as ChevronDown4, Minus as Minus2, Plus as Plus3, Star as Star2 } from "lucide-react";
18994
+ import { Fragment as Fragment7, jsx as jsx21, jsxs as jsxs12 } from "react/jsx-runtime";
18799
18995
  var PRIMARY4 = "var(--ohw-primary, #0885FE)";
18800
- 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]";
18996
+ 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]";
18801
18997
  function findPricingCardElement(sectionId, index) {
18802
18998
  const sectionEl = findByInstanceId(sectionId);
18803
18999
  if (!sectionEl) return null;
@@ -18809,7 +19005,7 @@ function findPricingCardElement(sectionId, index) {
18809
19005
  }
18810
19006
  function cardRefFromTarget(target) {
18811
19007
  if (!(target instanceof Element)) return null;
18812
- if (target.closest(CHROME_SELECTOR)) return null;
19008
+ if (target.closest(CHROME_SELECTOR2)) return null;
18813
19009
  const cardEl = target.closest("[data-ohw-pricing-card]");
18814
19010
  if (!cardEl) return null;
18815
19011
  const sectionEl = cardEl.closest("[data-ohw-section]");
@@ -18819,21 +19015,21 @@ function cardRefFromTarget(target) {
18819
19015
  if (!sectionId || Number.isNaN(index)) return null;
18820
19016
  return { sectionId, index };
18821
19017
  }
18822
- function sameRef(a, b) {
19018
+ function sameRef2(a, b) {
18823
19019
  return a?.sectionId === b?.sectionId && a?.index === b?.index;
18824
19020
  }
18825
- function rectOf(el) {
19021
+ function rectOf2(el) {
18826
19022
  if (!el) return null;
18827
19023
  const r2 = el.getBoundingClientRect();
18828
19024
  if (r2.width <= 0 || r2.height <= 0) return null;
18829
19025
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
18830
19026
  }
18831
19027
  function readRect3(ref) {
18832
- return rectOf(ref && findPricingCardElement(ref.sectionId, ref.index));
19028
+ return rectOf2(ref && findPricingCardElement(ref.sectionId, ref.index));
18833
19029
  }
18834
19030
  function readGridRect(ref) {
18835
19031
  const card = ref && findPricingCardElement(ref.sectionId, ref.index);
18836
- return rectOf(card?.closest("[data-ohw-pricing-collection]") ?? card);
19032
+ return rectOf2(card?.closest("[data-ohw-pricing-collection]") ?? card);
18837
19033
  }
18838
19034
  function useLiveCardRect(ref, grid = false) {
18839
19035
  const [rect, setRect] = useState9(null);
@@ -18863,19 +19059,19 @@ function useLiveCardRect(ref, grid = false) {
18863
19059
  }
18864
19060
  var TOOLBAR_GRID_GAP = 52;
18865
19061
  var TOOLBAR_GRID_GAP_MOBILE = 42;
18866
- var TOOLBAR_EDGE_MARGIN2 = 4;
19062
+ var TOOLBAR_EDGE_MARGIN3 = 4;
18867
19063
  function GridToolbarSlot({ gridRect, children }) {
18868
19064
  const slotRef = useRef6(null);
18869
19065
  const [shift, setShift] = useState9(0);
18870
19066
  const centerX = gridRect.left + gridRect.width / 2;
18871
- useLayoutEffect4(() => {
19067
+ useLayoutEffect5(() => {
18872
19068
  const slot = slotRef.current;
18873
19069
  if (!slot) return;
18874
19070
  const clamp = () => {
18875
19071
  const w = slot.offsetWidth;
18876
19072
  if (w <= 0) return;
18877
19073
  const half = w / 2;
18878
- const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
19074
+ const clamped = Math.max(TOOLBAR_EDGE_MARGIN3 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half));
18879
19075
  setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
18880
19076
  };
18881
19077
  clamp();
@@ -18884,7 +19080,7 @@ function GridToolbarSlot({ gridRect, children }) {
18884
19080
  return () => ro.disconnect();
18885
19081
  }, [centerX, children]);
18886
19082
  const mobile = window.innerWidth <= 480;
18887
- return /* @__PURE__ */ jsx20(
19083
+ return /* @__PURE__ */ jsx21(
18888
19084
  "div",
18889
19085
  {
18890
19086
  ref: slotRef,
@@ -18919,7 +19115,7 @@ function requestValidateTree2(postToParent2, tree) {
18919
19115
  });
18920
19116
  }
18921
19117
  function StyleSwatch({ style }) {
18922
- return /* @__PURE__ */ jsx20(
19118
+ return /* @__PURE__ */ jsx21(
18923
19119
  "span",
18924
19120
  {
18925
19121
  "aria-hidden": true,
@@ -18933,35 +19129,35 @@ function StyleSwatch({ style }) {
18933
19129
  );
18934
19130
  }
18935
19131
  function ButtonGlyph() {
18936
- return /* @__PURE__ */ jsxs11("svg", { width: "18", height: "12", viewBox: "0 0 18 12", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
18937
- /* @__PURE__ */ jsx20("rect", { x: "0.75", y: "0.75", width: "16.5", height: "10.5", rx: "3", stroke: "currentColor", strokeWidth: "1.5" }),
18938
- /* @__PURE__ */ jsx20("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
19132
+ return /* @__PURE__ */ jsxs12("svg", { width: "18", height: "12", viewBox: "0 0 18 12", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19133
+ /* @__PURE__ */ jsx21("rect", { x: "0.75", y: "0.75", width: "16.5", height: "10.5", rx: "3", stroke: "currentColor", strokeWidth: "1.5" }),
19134
+ /* @__PURE__ */ jsx21("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
18939
19135
  ] });
18940
19136
  }
18941
19137
  function FeatureGlyph({ mode }) {
18942
- return /* @__PURE__ */ jsxs11("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
18943
- /* @__PURE__ */ jsx20("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
18944
- /* @__PURE__ */ jsx20("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
18945
- /* @__PURE__ */ jsx20("circle", { cx: "13.5", cy: "13.5", r: "4.25", fill: "var(--ohw-background, #ffffff)", stroke: "var(--ohw-background, #ffffff)", strokeWidth: "1" }),
18946
- /* @__PURE__ */ jsx20("circle", { cx: "13.5", cy: "13.5", r: "3.75", fill: "var(--ohw-background, #ffffff)", stroke: PRIMARY4, strokeWidth: "1" }),
18947
- /* @__PURE__ */ jsx20("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
18948
- mode === "add" && /* @__PURE__ */ jsx20("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
19138
+ return /* @__PURE__ */ jsxs12("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19139
+ /* @__PURE__ */ jsx21("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
19140
+ /* @__PURE__ */ jsx21("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
19141
+ /* @__PURE__ */ jsx21("circle", { cx: "13.5", cy: "13.5", r: "4.25", fill: "var(--ohw-background, #ffffff)", stroke: "var(--ohw-background, #ffffff)", strokeWidth: "1" }),
19142
+ /* @__PURE__ */ jsx21("circle", { cx: "13.5", cy: "13.5", r: "3.75", fill: "var(--ohw-background, #ffffff)", stroke: PRIMARY4, strokeWidth: "1" }),
19143
+ /* @__PURE__ */ jsx21("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
19144
+ mode === "add" && /* @__PURE__ */ jsx21("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
18949
19145
  ] });
18950
19146
  }
18951
- function ToolbarAction({
19147
+ function ToolbarAction2({
18952
19148
  label,
18953
19149
  onClick,
18954
19150
  disabled = false,
18955
19151
  children
18956
19152
  }) {
18957
- const button = /* @__PURE__ */ jsx20(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
18958
- return /* @__PURE__ */ jsxs11(Tooltip, { children: [
18959
- /* @__PURE__ */ jsx20(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ jsx20("span", { className: "inline-flex", children: button }) : button }),
18960
- /* @__PURE__ */ jsx20(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19153
+ const button = /* @__PURE__ */ jsx21(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
19154
+ return /* @__PURE__ */ jsxs12(Tooltip, { children: [
19155
+ /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ jsx21("span", { className: "inline-flex", children: button }) : button }),
19156
+ /* @__PURE__ */ jsx21(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
18961
19157
  ] });
18962
19158
  }
18963
- function ToolbarSeparator() {
18964
- return /* @__PURE__ */ jsx20("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19159
+ function ToolbarSeparator2() {
19160
+ return /* @__PURE__ */ jsx21("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
18965
19161
  }
18966
19162
  function CardStyleMenu({
18967
19163
  value,
@@ -18969,7 +19165,7 @@ function CardStyleMenu({
18969
19165
  disabled = false
18970
19166
  }) {
18971
19167
  const current = PRICING_CARD_STYLES.find((s) => s.value === value) ?? PRICING_CARD_STYLES[0];
18972
- const trigger = /* @__PURE__ */ jsxs11(
19168
+ const trigger = /* @__PURE__ */ jsxs12(
18973
19169
  "button",
18974
19170
  {
18975
19171
  type: "button",
@@ -18980,28 +19176,28 @@ function CardStyleMenu({
18980
19176
  disabled && "cursor-not-allowed opacity-40 hover:bg-transparent"
18981
19177
  ),
18982
19178
  children: [
18983
- /* @__PURE__ */ jsx20(StyleSwatch, { style: current.value }),
18984
- /* @__PURE__ */ jsx20("span", { className: "flex-1 text-left", children: current.label }),
18985
- /* @__PURE__ */ jsx20(ChevronDown3, { size: 16, style: { pointerEvents: "none" } })
19179
+ /* @__PURE__ */ jsx21(StyleSwatch, { style: current.value }),
19180
+ /* @__PURE__ */ jsx21("span", { className: "flex-1 text-left", children: current.label }),
19181
+ /* @__PURE__ */ jsx21(ChevronDown4, { size: 16, style: { pointerEvents: "none" } })
18986
19182
  ]
18987
19183
  }
18988
19184
  );
18989
19185
  if (disabled) {
18990
- return /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
18991
- /* @__PURE__ */ jsx20("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
18992
- /* @__PURE__ */ jsxs11(Tooltip, { children: [
18993
- /* @__PURE__ */ jsx20(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx20("span", { className: "inline-flex", children: trigger }) }),
18994
- /* @__PURE__ */ jsx20(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
19186
+ return /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19187
+ /* @__PURE__ */ jsx21("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
19188
+ /* @__PURE__ */ jsxs12(Tooltip, { children: [
19189
+ /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx21("span", { className: "inline-flex", children: trigger }) }),
19190
+ /* @__PURE__ */ jsx21(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
18995
19191
  ] })
18996
19192
  ] });
18997
19193
  }
18998
- return /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
18999
- /* @__PURE__ */ jsx20("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
19000
- /* @__PURE__ */ jsxs11(DropdownMenu, { modal: false, children: [
19001
- /* @__PURE__ */ jsx20(DropdownMenuTrigger, { asChild: true, children: trigger }),
19002
- /* @__PURE__ */ jsx20(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[152px] min-w-[152px] p-1", children: PRICING_CARD_STYLES.map((style) => {
19194
+ return /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19195
+ /* @__PURE__ */ jsx21("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
19196
+ /* @__PURE__ */ jsxs12(DropdownMenu, { modal: false, children: [
19197
+ /* @__PURE__ */ jsx21(DropdownMenuTrigger, { asChild: true, children: trigger }),
19198
+ /* @__PURE__ */ jsx21(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[152px] min-w-[152px] p-1", children: PRICING_CARD_STYLES.map((style) => {
19003
19199
  const selected = style.value === value;
19004
- return /* @__PURE__ */ jsxs11(
19200
+ return /* @__PURE__ */ jsxs12(
19005
19201
  DropdownMenuItem,
19006
19202
  {
19007
19203
  role: "menuitemradio",
@@ -19009,9 +19205,9 @@ function CardStyleMenu({
19009
19205
  onSelect: () => onChange(style.value),
19010
19206
  className: cn("grid grid-cols-[18px_1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
19011
19207
  children: [
19012
- /* @__PURE__ */ jsx20(StyleSwatch, { style: style.value }),
19013
- /* @__PURE__ */ jsx20("span", { children: style.label }),
19014
- selected && /* @__PURE__ */ jsx20(Check2, { size: 16, className: "text-bridge-primary" })
19208
+ /* @__PURE__ */ jsx21(StyleSwatch, { style: style.value }),
19209
+ /* @__PURE__ */ jsx21("span", { children: style.label }),
19210
+ selected && /* @__PURE__ */ jsx21(Check3, { size: 16, className: "text-bridge-primary" })
19015
19211
  ]
19016
19212
  },
19017
19213
  style.value
@@ -19041,7 +19237,7 @@ function PricingCardToolbar({
19041
19237
  const style = pricingCardStyle(entry.tree, index);
19042
19238
  const featuredTag = pricingCardFeaturedTag(entry.tree, index);
19043
19239
  const mobile = window.innerWidth <= 480;
19044
- return /* @__PURE__ */ jsx20(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsxs11(
19240
+ return /* @__PURE__ */ jsx21(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsxs12(
19045
19241
  "div",
19046
19242
  {
19047
19243
  "data-ohw-bridge": "",
@@ -19053,7 +19249,7 @@ function PricingCardToolbar({
19053
19249
  ),
19054
19250
  style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
19055
19251
  children: [
19056
- /* @__PURE__ */ jsxs11(
19252
+ /* @__PURE__ */ jsxs12(
19057
19253
  "div",
19058
19254
  {
19059
19255
  className: cn(
@@ -19062,8 +19258,8 @@ function PricingCardToolbar({
19062
19258
  mobile ? "flex-wrap justify-center" : "flex-nowrap"
19063
19259
  ),
19064
19260
  children: [
19065
- /* @__PURE__ */ jsxs11(Tooltip, { children: [
19066
- /* @__PURE__ */ jsx20(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx20(
19261
+ /* @__PURE__ */ jsxs12(Tooltip, { children: [
19262
+ /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx21(
19067
19263
  CustomToolbarButton,
19068
19264
  {
19069
19265
  type: "button",
@@ -19071,7 +19267,7 @@ function PricingCardToolbar({
19071
19267
  "aria-pressed": featuredTag,
19072
19268
  onClick: () => onSetFeaturedTag(!featuredTag),
19073
19269
  className: "group size-8",
19074
- children: /* @__PURE__ */ jsx20(
19270
+ children: /* @__PURE__ */ jsx21(
19075
19271
  Star2,
19076
19272
  {
19077
19273
  size: 16,
@@ -19086,25 +19282,25 @@ function PricingCardToolbar({
19086
19282
  )
19087
19283
  }
19088
19284
  ) }),
19089
- /* @__PURE__ */ jsx20(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
19285
+ /* @__PURE__ */ jsx21(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
19090
19286
  ] }),
19091
- /* @__PURE__ */ jsx20(ToolbarSeparator, {}),
19092
- /* @__PURE__ */ jsx20(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
19093
- /* @__PURE__ */ jsx20(ToolbarSeparator, {}),
19094
- /* @__PURE__ */ jsx20(ToolbarAction, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ jsx20(ButtonGlyph, {}) }),
19095
- /* @__PURE__ */ jsx20(ToolbarAction, { label: "Remove button", onClick: onRemoveButton, disabled: buttonCount === 0, children: /* @__PURE__ */ jsxs11("span", { className: "relative inline-flex", children: [
19096
- /* @__PURE__ */ jsx20(ButtonGlyph, {}),
19097
- /* @__PURE__ */ jsx20(Minus, { size: 10, strokeWidth: 2.5, className: "absolute -right-1.5 -top-1.5 rounded-full bg-background text-bridge-primary" })
19287
+ /* @__PURE__ */ jsx21(ToolbarSeparator2, {}),
19288
+ /* @__PURE__ */ jsx21(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
19289
+ /* @__PURE__ */ jsx21(ToolbarSeparator2, {}),
19290
+ /* @__PURE__ */ jsx21(ToolbarAction2, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ jsx21(ButtonGlyph, {}) }),
19291
+ /* @__PURE__ */ jsx21(ToolbarAction2, { label: "Remove button", onClick: onRemoveButton, disabled: buttonCount === 0, children: /* @__PURE__ */ jsxs12("span", { className: "relative inline-flex", children: [
19292
+ /* @__PURE__ */ jsx21(ButtonGlyph, {}),
19293
+ /* @__PURE__ */ jsx21(Minus2, { size: 10, strokeWidth: 2.5, className: "absolute -right-1.5 -top-1.5 rounded-full bg-background text-bridge-primary" })
19098
19294
  ] }) }),
19099
- /* @__PURE__ */ jsx20(ToolbarAction, { label: "Add feature", onClick: onAddFeature, disabled: featureCount >= 10, children: /* @__PURE__ */ jsx20(FeatureGlyph, { mode: "add" }) }),
19100
- /* @__PURE__ */ jsx20(ToolbarAction, { label: "Remove feature", onClick: onRemoveFeature, disabled: featureCount <= 1, children: /* @__PURE__ */ jsx20(FeatureGlyph, { mode: "remove" }) }),
19101
- /* @__PURE__ */ jsx20(ToolbarSeparator, {}),
19102
- /* @__PURE__ */ jsx20(ToolbarAction, { label: "Add tier", onClick: onAddTier, disabled: collectionCount >= 4, children: /* @__PURE__ */ jsx20(Plus2, { size: 16, style: { pointerEvents: "none" } }) }),
19103
- /* @__PURE__ */ jsx20(ToolbarAction, { label: "Remove tier", onClick: onRemoveTier, disabled: collectionCount <= 1, children: /* @__PURE__ */ jsx20(Minus, { size: 16, style: { pointerEvents: "none" } }) })
19295
+ /* @__PURE__ */ jsx21(ToolbarAction2, { label: "Add feature", onClick: onAddFeature, disabled: featureCount >= 10, children: /* @__PURE__ */ jsx21(FeatureGlyph, { mode: "add" }) }),
19296
+ /* @__PURE__ */ jsx21(ToolbarAction2, { label: "Remove feature", onClick: onRemoveFeature, disabled: featureCount <= 1, children: /* @__PURE__ */ jsx21(FeatureGlyph, { mode: "remove" }) }),
19297
+ /* @__PURE__ */ jsx21(ToolbarSeparator2, {}),
19298
+ /* @__PURE__ */ jsx21(ToolbarAction2, { label: "Add tier", onClick: onAddTier, disabled: collectionCount >= 4, children: /* @__PURE__ */ jsx21(Plus3, { size: 16, style: { pointerEvents: "none" } }) }),
19299
+ /* @__PURE__ */ jsx21(ToolbarAction2, { label: "Remove tier", onClick: onRemoveTier, disabled: collectionCount <= 1, children: /* @__PURE__ */ jsx21(Minus2, { size: 16, style: { pointerEvents: "none" } }) })
19104
19300
  ]
19105
19301
  }
19106
19302
  ),
19107
- error && /* @__PURE__ */ jsx20("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19303
+ error && /* @__PURE__ */ jsx21("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19108
19304
  ]
19109
19305
  }
19110
19306
  ) });
@@ -19138,7 +19334,7 @@ function AiPricingCardOverlay({
19138
19334
  const overEditable = Boolean(t?.closest?.("[data-ohw-editable], [data-ohw-role], a, button"));
19139
19335
  setHover((prev) => {
19140
19336
  const next = ref && !overEditable ? ref : null;
19141
- return sameRef(prev, next) ? prev : next;
19337
+ return sameRef2(prev, next) ? prev : next;
19142
19338
  });
19143
19339
  };
19144
19340
  const onLeave = () => setHover(null);
@@ -19153,10 +19349,10 @@ function AiPricingCardOverlay({
19153
19349
  const onClick = (e) => {
19154
19350
  const t = e.target;
19155
19351
  if (!(t instanceof Element)) return;
19156
- if (t.closest(CHROME_SELECTOR)) return;
19352
+ if (t.closest(CHROME_SELECTOR2)) return;
19157
19353
  const ref = cardRefFromTarget(t);
19158
- setActive((prev) => sameRef(prev, ref) ? prev : ref);
19159
- if (!ref || !sameRef(activeRef.current, ref)) setError(null);
19354
+ setActive((prev) => sameRef2(prev, ref) ? prev : ref);
19355
+ if (!ref || !sameRef2(activeRef.current, ref)) setError(null);
19160
19356
  };
19161
19357
  document.addEventListener("click", onClick, true);
19162
19358
  return () => document.removeEventListener("click", onClick, true);
@@ -19221,12 +19417,12 @@ function AiPricingCardOverlay({
19221
19417
  const activeEntry = active ? findEntry(active.sectionId) : null;
19222
19418
  const activeCardRect = useLiveCardRect(active);
19223
19419
  const activeGridRect = useLiveCardRect(active, true);
19224
- const hoverRect = useLiveCardRect(hover && !sameRef(hover, active) ? hover : null);
19225
- return /* @__PURE__ */ jsxs11(Fragment7, { children: [
19226
- hoverRect && /* @__PURE__ */ jsx20(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19227
- activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ jsxs11(Fragment7, { children: [
19228
- /* @__PURE__ */ jsx20(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
19229
- /* @__PURE__ */ jsx20(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ jsx20(
19420
+ const hoverRect = useLiveCardRect(hover && !sameRef2(hover, active) ? hover : null);
19421
+ return /* @__PURE__ */ jsxs12(Fragment7, { children: [
19422
+ hoverRect && /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19423
+ activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ jsxs12(Fragment7, { children: [
19424
+ /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
19425
+ /* @__PURE__ */ jsx21(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ jsx21(
19230
19426
  PricingCardToolbar,
19231
19427
  {
19232
19428
  entry: activeEntry,
@@ -19254,7 +19450,7 @@ function AiPricingCardOverlay({
19254
19450
  onRemoveFeature: () => runMutation(active.sectionId, (tree) => removePricingCardFeature(tree, active.index))
19255
19451
  }
19256
19452
  ) }),
19257
- /* @__PURE__ */ jsx20(
19453
+ /* @__PURE__ */ jsx21(
19258
19454
  "div",
19259
19455
  {
19260
19456
  "data-ohw-bridge": "",
@@ -19347,10 +19543,10 @@ function extractYouTubeId(input) {
19347
19543
  }
19348
19544
 
19349
19545
  // src/ui/ai-section/AiVideoOverlay.tsx
19350
- import { Fragment as Fragment8, jsx as jsx21, jsxs as jsxs12 } from "react/jsx-runtime";
19546
+ import { Fragment as Fragment8, jsx as jsx22, jsxs as jsxs13 } from "react/jsx-runtime";
19351
19547
  var PRIMARY5 = "#0885FE";
19352
- var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY5})`;
19353
- var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
19548
+ var OHW_BLUE = `var(--ohw-primary, ${PRIMARY5})`;
19549
+ var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
19354
19550
  function findVideoBlockElement(sectionId, path) {
19355
19551
  const sectionEl = findByInstanceId(sectionId);
19356
19552
  if (!sectionEl) return null;
@@ -19410,12 +19606,12 @@ function requestValidateTree3(postToParent2, tree) {
19410
19606
  postToParent2({ type: "ow:ai-validate-tree-request", id, tree });
19411
19607
  });
19412
19608
  }
19413
- function Pill2({
19609
+ function Pill({
19414
19610
  label,
19415
19611
  onClick,
19416
19612
  disabled
19417
19613
  }) {
19418
- return /* @__PURE__ */ jsx21(
19614
+ return /* @__PURE__ */ jsx22(
19419
19615
  "button",
19420
19616
  {
19421
19617
  type: "button",
@@ -19431,7 +19627,7 @@ function Pill2({
19431
19627
  border: "none",
19432
19628
  cursor: disabled ? "default" : "pointer",
19433
19629
  background: "#ffffff",
19434
- color: OHW_BLUE_DARK2,
19630
+ color: OHW_BLUE_DARK,
19435
19631
  opacity: disabled ? 0.4 : 1,
19436
19632
  fontSize: 12,
19437
19633
  lineHeight: "16px",
@@ -19474,8 +19670,8 @@ function VideoUrlInput({ value, onCommit }) {
19474
19670
  onCommit(id);
19475
19671
  }
19476
19672
  };
19477
- return /* @__PURE__ */ jsxs12("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
19478
- /* @__PURE__ */ jsxs12(
19673
+ return /* @__PURE__ */ jsxs13("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
19674
+ /* @__PURE__ */ jsxs13(
19479
19675
  "div",
19480
19676
  {
19481
19677
  style: {
@@ -19491,12 +19687,12 @@ function VideoUrlInput({ value, onCommit }) {
19491
19687
  boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
19492
19688
  },
19493
19689
  children: [
19494
- /* @__PURE__ */ jsx21(
19690
+ /* @__PURE__ */ jsx22(
19495
19691
  "span",
19496
19692
  {
19497
19693
  style: {
19498
19694
  flex: "0 0 auto",
19499
- color: OHW_BLUE_DARK2,
19695
+ color: OHW_BLUE_DARK,
19500
19696
  fontSize: 10,
19501
19697
  lineHeight: "16px",
19502
19698
  fontWeight: 700,
@@ -19506,7 +19702,7 @@ function VideoUrlInput({ value, onCommit }) {
19506
19702
  children: "Video"
19507
19703
  }
19508
19704
  ),
19509
- /* @__PURE__ */ jsx21(
19705
+ /* @__PURE__ */ jsx22(
19510
19706
  "input",
19511
19707
  {
19512
19708
  type: "text",
@@ -19543,7 +19739,7 @@ function VideoUrlInput({ value, onCommit }) {
19543
19739
  ]
19544
19740
  }
19545
19741
  ),
19546
- invalid && /* @__PURE__ */ jsx21("div", { style: { color: "#FCA5A5", fontSize: 11, textAlign: "center" }, children: "That doesn't look like a YouTube link \u2014 try pasting the video URL again." })
19742
+ invalid && /* @__PURE__ */ jsx22("div", { style: { color: "#FCA5A5", fontSize: 11, textAlign: "center" }, children: "That doesn't look like a YouTube link \u2014 try pasting the video URL again." })
19547
19743
  ] });
19548
19744
  }
19549
19745
  function VideoToolbar({
@@ -19553,7 +19749,7 @@ function VideoToolbar({
19553
19749
  onSetVideoId,
19554
19750
  onRemove
19555
19751
  }) {
19556
- return /* @__PURE__ */ jsxs12(
19752
+ return /* @__PURE__ */ jsxs13(
19557
19753
  "div",
19558
19754
  {
19559
19755
  "data-ohw-bridge": "",
@@ -19565,7 +19761,7 @@ function VideoToolbar({
19565
19761
  gap: 6,
19566
19762
  alignItems: "center",
19567
19763
  width: 280,
19568
- background: OHW_BLUE2,
19764
+ background: OHW_BLUE,
19569
19765
  padding: 8,
19570
19766
  borderRadius: 12,
19571
19767
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -19573,9 +19769,9 @@ function VideoToolbar({
19573
19769
  opacity: pending ? 0.6 : 1
19574
19770
  },
19575
19771
  children: [
19576
- /* @__PURE__ */ jsx21(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
19577
- /* @__PURE__ */ jsx21(Pill2, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
19578
- error && /* @__PURE__ */ jsx21("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
19772
+ /* @__PURE__ */ jsx22(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
19773
+ /* @__PURE__ */ jsx22(Pill, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
19774
+ error && /* @__PURE__ */ jsx22("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
19579
19775
  ]
19580
19776
  }
19581
19777
  );
@@ -19695,8 +19891,8 @@ function AiVideoOverlay({
19695
19891
  const activeBlockRect = useLiveBlockRect(active);
19696
19892
  const TOOLBAR_APPROX_H = 90;
19697
19893
  const toolbarState = activeBlockRect && activeBlockRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
19698
- return /* @__PURE__ */ jsxs12(Fragment8, { children: [
19699
- hoverHandleRect && hover && /* @__PURE__ */ jsx21(
19894
+ return /* @__PURE__ */ jsxs13(Fragment8, { children: [
19895
+ hoverHandleRect && hover && /* @__PURE__ */ jsx22(
19700
19896
  "button",
19701
19897
  {
19702
19898
  type: "button",
@@ -19724,15 +19920,15 @@ function AiVideoOverlay({
19724
19920
  fontSize: 14,
19725
19921
  fontWeight: 700
19726
19922
  },
19727
- children: /* @__PURE__ */ jsx21(Star3, { size: 14, style: { pointerEvents: "none" } })
19923
+ children: /* @__PURE__ */ jsx22(Star3, { size: 14, style: { pointerEvents: "none" } })
19728
19924
  }
19729
19925
  ),
19730
- activeBlockRect && active && activeEntry && /* @__PURE__ */ jsx21(
19926
+ activeBlockRect && active && activeEntry && /* @__PURE__ */ jsx22(
19731
19927
  ItemInteractionLayer,
19732
19928
  {
19733
19929
  rect: activeBlockRect,
19734
19930
  state: toolbarState,
19735
- toolbar: /* @__PURE__ */ jsx21(
19931
+ toolbar: /* @__PURE__ */ jsx22(
19736
19932
  VideoToolbar,
19737
19933
  {
19738
19934
  videoId: videoIdAt(activeEntry.tree, active.path),
@@ -19807,10 +20003,10 @@ function setCarouselAutoplay(tree, on) {
19807
20003
  }
19808
20004
 
19809
20005
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
19810
- import { Fragment as Fragment9, jsx as jsx22, jsxs as jsxs13 } from "react/jsx-runtime";
20006
+ import { Fragment as Fragment9, jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
19811
20007
  var PRIMARY6 = "#0885FE";
19812
- var OHW_BLUE3 = `var(--ohw-primary, ${PRIMARY6})`;
19813
- var OHW_BLUE_DARK3 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
20008
+ var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY6})`;
20009
+ var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
19814
20010
  function findCarouselItemElement(sectionId, index) {
19815
20011
  const sectionEl = findByInstanceId(sectionId);
19816
20012
  if (!sectionEl) return null;
@@ -19889,8 +20085,8 @@ function requestAssetPick2(postToParent2) {
19889
20085
  postToParent2({ type: "ow:asset-pick", id });
19890
20086
  });
19891
20087
  }
19892
- function Pill3({ label, onClick, disabled }) {
19893
- return /* @__PURE__ */ jsx22(
20088
+ function Pill2({ label, onClick, disabled }) {
20089
+ return /* @__PURE__ */ jsx23(
19894
20090
  "button",
19895
20091
  {
19896
20092
  type: "button",
@@ -19906,7 +20102,7 @@ function Pill3({ label, onClick, disabled }) {
19906
20102
  border: "none",
19907
20103
  cursor: disabled ? "default" : "pointer",
19908
20104
  background: "#ffffff",
19909
- color: OHW_BLUE_DARK3,
20105
+ color: OHW_BLUE_DARK2,
19910
20106
  opacity: disabled ? 0.4 : 1,
19911
20107
  fontSize: 12,
19912
20108
  lineHeight: "16px",
@@ -19924,7 +20120,7 @@ function TogglePill({
19924
20120
  onToggle,
19925
20121
  disabled
19926
20122
  }) {
19927
- return /* @__PURE__ */ jsxs13(
20123
+ return /* @__PURE__ */ jsxs14(
19928
20124
  "button",
19929
20125
  {
19930
20126
  type: "button",
@@ -19943,7 +20139,7 @@ function TogglePill({
19943
20139
  border: "none",
19944
20140
  cursor: disabled ? "default" : "pointer",
19945
20141
  background: "#ffffff",
19946
- color: OHW_BLUE_DARK3,
20142
+ color: OHW_BLUE_DARK2,
19947
20143
  opacity: disabled ? 0.4 : 1,
19948
20144
  fontSize: 12,
19949
20145
  lineHeight: "16px",
@@ -19953,7 +20149,7 @@ function TogglePill({
19953
20149
  },
19954
20150
  children: [
19955
20151
  label,
19956
- /* @__PURE__ */ jsx22(
20152
+ /* @__PURE__ */ jsx23(
19957
20153
  "span",
19958
20154
  {
19959
20155
  "aria-hidden": true,
@@ -19966,10 +20162,10 @@ function TogglePill({
19966
20162
  // Off is a grey track, not a washed-out blue: the pill's own white background already
19967
20163
  // carries the brand colour in the text, and a pale blue track next to it read as
19968
20164
  // "disabled control" rather than "switch that is off".
19969
- background: on ? OHW_BLUE_DARK3 : "#CBD5E1",
20165
+ background: on ? OHW_BLUE_DARK2 : "#CBD5E1",
19970
20166
  transition: "background 120ms ease"
19971
20167
  },
19972
- children: /* @__PURE__ */ jsx22(
20168
+ children: /* @__PURE__ */ jsx23(
19973
20169
  "span",
19974
20170
  {
19975
20171
  style: {
@@ -19998,7 +20194,7 @@ function CarouselSlideToolbar({
19998
20194
  onChangeImage,
19999
20195
  onToggleAutoplay
20000
20196
  }) {
20001
- return /* @__PURE__ */ jsxs13(
20197
+ return /* @__PURE__ */ jsxs14(
20002
20198
  "div",
20003
20199
  {
20004
20200
  "data-ohw-bridge": "",
@@ -20009,7 +20205,7 @@ function CarouselSlideToolbar({
20009
20205
  flexDirection: "column",
20010
20206
  gap: 6,
20011
20207
  alignItems: "center",
20012
- background: OHW_BLUE3,
20208
+ background: OHW_BLUE2,
20013
20209
  padding: 8,
20014
20210
  borderRadius: 12,
20015
20211
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -20021,11 +20217,11 @@ function CarouselSlideToolbar({
20021
20217
  pointerEvents: pending ? "none" : "auto"
20022
20218
  },
20023
20219
  children: [
20024
- /* @__PURE__ */ jsxs13("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20025
- /* @__PURE__ */ jsx22(Pill3, { label: "Change Image", onClick: onChangeImage }),
20026
- /* @__PURE__ */ jsx22(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
20220
+ /* @__PURE__ */ jsxs14("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20221
+ /* @__PURE__ */ jsx23(Pill2, { label: "Change Image", onClick: onChangeImage }),
20222
+ /* @__PURE__ */ jsx23(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
20027
20223
  ] }),
20028
- error && /* @__PURE__ */ jsx22("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20224
+ error && /* @__PURE__ */ jsx23("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20029
20225
  ]
20030
20226
  }
20031
20227
  );
@@ -20165,8 +20361,8 @@ function AiFadeCarouselOverlay({
20165
20361
  const activeItemRect = useLiveItemRect2(active);
20166
20362
  const TOOLBAR_APPROX_H = 90;
20167
20363
  const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
20168
- return /* @__PURE__ */ jsxs13(Fragment9, { children: [
20169
- hoverHandleRect && hover && /* @__PURE__ */ jsx22(
20364
+ return /* @__PURE__ */ jsxs14(Fragment9, { children: [
20365
+ hoverHandleRect && hover && /* @__PURE__ */ jsx23(
20170
20366
  "button",
20171
20367
  {
20172
20368
  type: "button",
@@ -20195,10 +20391,10 @@ function AiFadeCarouselOverlay({
20195
20391
  fontSize: 14,
20196
20392
  fontWeight: 700
20197
20393
  },
20198
- children: /* @__PURE__ */ jsx22(Star4, { size: 14, style: { pointerEvents: "none" } })
20394
+ children: /* @__PURE__ */ jsx23(Star4, { size: 14, style: { pointerEvents: "none" } })
20199
20395
  }
20200
20396
  ),
20201
- swapping && /* @__PURE__ */ jsx22(
20397
+ swapping && /* @__PURE__ */ jsx23(
20202
20398
  "div",
20203
20399
  {
20204
20400
  ref: skeletonRef,
@@ -20216,15 +20412,15 @@ function AiFadeCarouselOverlay({
20216
20412
  zIndex: 2147483646,
20217
20413
  pointerEvents: "none"
20218
20414
  },
20219
- children: /* @__PURE__ */ jsx22("style", { children: MEDIA_SKELETON_CSS })
20415
+ children: /* @__PURE__ */ jsx23("style", { children: MEDIA_SKELETON_CSS })
20220
20416
  }
20221
20417
  ),
20222
- activeItemRect && active && activeEntry && /* @__PURE__ */ jsx22(
20418
+ activeItemRect && active && activeEntry && /* @__PURE__ */ jsx23(
20223
20419
  ItemInteractionLayer,
20224
20420
  {
20225
20421
  rect: activeItemRect,
20226
20422
  state: toolbarState,
20227
- toolbar: /* @__PURE__ */ jsx22(
20423
+ toolbar: /* @__PURE__ */ jsx23(
20228
20424
  CarouselSlideToolbar,
20229
20425
  {
20230
20426
  pending,
@@ -20577,118 +20773,11 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
20577
20773
  // src/ui/link-modal/LinkPopover.tsx
20578
20774
  import { useEffect as useEffect13 } from "react";
20579
20775
 
20580
- // src/ui/dialog.tsx
20581
- import * as React9 from "react";
20582
- import { Dialog as DialogPrimitive } from "radix-ui";
20583
- import { X as X2 } from "lucide-react";
20584
- import { jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
20585
- function Dialog2({
20586
- ...props
20587
- }) {
20588
- return /* @__PURE__ */ jsx23(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
20589
- }
20590
- function DialogPortal({
20591
- ...props
20592
- }) {
20593
- return /* @__PURE__ */ jsx23(DialogPrimitive.Portal, { ...props });
20594
- }
20595
- function DialogOverlay({
20596
- className,
20597
- ...props
20598
- }) {
20599
- return /* @__PURE__ */ jsx23(
20600
- DialogPrimitive.Overlay,
20601
- {
20602
- "data-slot": "dialog-overlay",
20603
- "data-ohw-link-modal-root": "",
20604
- className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
20605
- ...props
20606
- }
20607
- );
20608
- }
20609
- var DialogContent = React9.forwardRef(
20610
- ({ className, children, showCloseButton = true, container, ...props }, ref) => {
20611
- const positionMode = container ? "absolute" : "fixed";
20612
- return /* @__PURE__ */ jsxs14(DialogPortal, { container: container ?? void 0, children: [
20613
- /* @__PURE__ */ jsx23(DialogOverlay, { className: cn(positionMode, "inset-0") }),
20614
- /* @__PURE__ */ jsxs14(
20615
- DialogPrimitive.Content,
20616
- {
20617
- ref,
20618
- "data-slot": "dialog-content",
20619
- className: cn(
20620
- positionMode,
20621
- "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",
20622
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
20623
- container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
20624
- className
20625
- ),
20626
- onMouseDown: (e) => e.stopPropagation(),
20627
- ...props,
20628
- children: [
20629
- children,
20630
- showCloseButton ? /* @__PURE__ */ jsx23(
20631
- DialogPrimitive.Close,
20632
- {
20633
- type: "button",
20634
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
20635
- "aria-label": "Close",
20636
- children: /* @__PURE__ */ jsx23(X2, { size: 16, "aria-hidden": true })
20637
- }
20638
- ) : null
20639
- ]
20640
- }
20641
- )
20642
- ] });
20643
- }
20644
- );
20645
- DialogContent.displayName = DialogPrimitive.Content.displayName;
20646
- function DialogHeader({
20647
- className,
20648
- ...props
20649
- }) {
20650
- return /* @__PURE__ */ jsx23("div", { className: cn("flex flex-col gap-1.5", className), ...props });
20651
- }
20652
- function DialogFooter({
20653
- className,
20654
- ...props
20655
- }) {
20656
- return /* @__PURE__ */ jsx23(
20657
- "div",
20658
- {
20659
- className: cn("flex items-center justify-end gap-2", className),
20660
- ...props
20661
- }
20662
- );
20663
- }
20664
- var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx23(
20665
- DialogPrimitive.Title,
20666
- {
20667
- ref,
20668
- className: cn(
20669
- "text-lg font-semibold leading-none tracking-tight text-card-foreground",
20670
- className
20671
- ),
20672
- ...props
20673
- }
20674
- ));
20675
- DialogTitle.displayName = DialogPrimitive.Title.displayName;
20676
- var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx23(
20677
- DialogPrimitive.Description,
20678
- {
20679
- ref,
20680
- className: cn("text-sm text-muted-foreground", className),
20681
- ...props
20682
- }
20683
- ));
20684
- DialogDescription.displayName = DialogPrimitive.Description.displayName;
20685
- var DialogClose = DialogPrimitive.Close;
20686
-
20687
20776
  // src/ui/link-modal/LinkEditorPanel.tsx
20688
20777
  import { Info, X as X4 } from "lucide-react";
20689
20778
 
20690
20779
  // src/ui/link-modal/DestinationBreadcrumb.tsx
20691
- import { ArrowRight as ArrowRight2, File, GalleryVertical } from "lucide-react";
20780
+ import { ArrowRight as ArrowRight3, File, GalleryVertical } from "lucide-react";
20692
20781
  import { jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
20693
20782
  function DestinationBreadcrumb({
20694
20783
  pageTitle,
@@ -20702,7 +20791,7 @@ function DestinationBreadcrumb({
20702
20791
  /* @__PURE__ */ jsx24("span", { className: "text-sm font-medium leading-none text-foreground!", children: pageTitle })
20703
20792
  ] }),
20704
20793
  /* @__PURE__ */ jsx24(
20705
- ArrowRight2,
20794
+ ArrowRight3,
20706
20795
  {
20707
20796
  size: 16,
20708
20797
  className: "shrink-0 text-muted-foreground",
@@ -20814,7 +20903,7 @@ function Label({ className, ...props }) {
20814
20903
  }
20815
20904
 
20816
20905
  // src/ui/link-modal/UrlOrPageInput.tsx
20817
- import { ChevronDown as ChevronDown4, File as File2, X as X3 } from "lucide-react";
20906
+ import { ChevronDown as ChevronDown5, File as File2, X as X3 } from "lucide-react";
20818
20907
  import { jsx as jsx28, jsxs as jsxs17 } from "react/jsx-runtime";
20819
20908
  function FieldChevron({
20820
20909
  onClick
@@ -20827,7 +20916,7 @@ function FieldChevron({
20827
20916
  onClick,
20828
20917
  "aria-label": "Open page list",
20829
20918
  tabIndex: -1,
20830
- children: /* @__PURE__ */ jsx28(ChevronDown4, { size: 16 })
20919
+ children: /* @__PURE__ */ jsx28(ChevronDown5, { size: 16 })
20831
20920
  }
20832
20921
  );
20833
20922
  }
@@ -21057,7 +21146,7 @@ function LinkEditorPanel({ state, onClose }) {
21057
21146
  // src/ui/link-modal/SectionPickerOverlay.tsx
21058
21147
  import { useCallback as useCallback7, useEffect as useEffect11, useMemo as useMemo2, useRef as useRef10, useState as useState13 } from "react";
21059
21148
  import { createPortal as createPortal2 } from "react-dom";
21060
- import { ArrowLeft as ArrowLeft2, Check as Check3 } from "lucide-react";
21149
+ import { ArrowLeft as ArrowLeft3, Check as Check4 } from "lucide-react";
21061
21150
  import { usePathname, useRouter as useRouter2 } from "next/navigation";
21062
21151
 
21063
21152
  // src/lib/parent-scroll.ts
@@ -21334,7 +21423,7 @@ function SectionPickerOverlay({
21334
21423
  className: "h-8 min-w-0 gap-1 border-border bg-background px-2 py-1.5 shadow-sm hover:bg-muted cursor-pointer",
21335
21424
  onClick: onBack,
21336
21425
  children: [
21337
- /* @__PURE__ */ jsx30(ArrowLeft2, { className: "size-4 shrink-0", "aria-hidden": true }),
21426
+ /* @__PURE__ */ jsx30(ArrowLeft3, { className: "size-4 shrink-0", "aria-hidden": true }),
21338
21427
  "Back"
21339
21428
  ]
21340
21429
  }
@@ -21403,7 +21492,7 @@ function SectionPickerOverlay({
21403
21492
  className: "absolute right-3 top-3 flex size-8 items-center justify-center rounded-full text-white",
21404
21493
  style: { backgroundColor: "var(--ohw-primary, #0885fe)" },
21405
21494
  "aria-hidden": true,
21406
- children: /* @__PURE__ */ jsx30(Check3, { className: "size-5" })
21495
+ children: /* @__PURE__ */ jsx30(Check4, { className: "size-5" })
21407
21496
  }
21408
21497
  ) : null
21409
21498
  ]
@@ -21787,6 +21876,7 @@ function shouldUseDevFixtures() {
21787
21876
  function getLogoElement(el) {
21788
21877
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
21789
21878
  if (marked) return marked;
21879
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
21790
21880
  const root = el.closest("nav, [data-ohw-nav-root], footer");
21791
21881
  if (!root) return null;
21792
21882
  const anchor = el.closest("a");
@@ -21856,7 +21946,7 @@ function addFooterColumnWithPersist({
21856
21946
  import {
21857
21947
  useCallback as useCallback9,
21858
21948
  useEffect as useEffect14,
21859
- useLayoutEffect as useLayoutEffect5,
21949
+ useLayoutEffect as useLayoutEffect6,
21860
21950
  useRef as useRef11,
21861
21951
  useState as useState15
21862
21952
  } from "react";
@@ -21953,7 +22043,7 @@ function FloatingPanel({
21953
22043
  return () => window.removeEventListener("message", onParentScroll);
21954
22044
  }, [open]);
21955
22045
  const positionRef = useRef11(position);
21956
- useLayoutEffect5(() => {
22046
+ useLayoutEffect6(() => {
21957
22047
  if (!open) {
21958
22048
  positionRef.current = position;
21959
22049
  return;
@@ -21963,7 +22053,7 @@ function FloatingPanel({
21963
22053
  }
21964
22054
  positionRef.current = position;
21965
22055
  }, [open, position, liveScroll, parentScroll]);
21966
- useLayoutEffect5(() => {
22056
+ useLayoutEffect6(() => {
21967
22057
  if (!open || !panelRef.current) return;
21968
22058
  const el = panelRef.current;
21969
22059
  const header = el.querySelector("[data-ohw-floating-panel-header]");
@@ -22172,7 +22262,7 @@ function SocialsDisplayPanel({ display, onChange, className }) {
22172
22262
  }
22173
22263
 
22174
22264
  // src/ui/button-style-panel.tsx
22175
- import { Check as Check4 } from "lucide-react";
22265
+ import { Check as Check5 } from "lucide-react";
22176
22266
  import { jsx as jsx34, jsxs as jsxs23 } from "react/jsx-runtime";
22177
22267
  var SIZE_LABEL = { s: "S", m: "M", l: "L" };
22178
22268
  function ButtonStylePanel({
@@ -22243,7 +22333,7 @@ function ButtonStylePanel({
22243
22333
  {
22244
22334
  className: "absolute right-0.5 top-0.5 flex size-5 items-center justify-center rounded-full bg-primary text-primary-foreground",
22245
22335
  "aria-hidden": true,
22246
- children: /* @__PURE__ */ jsx34(Check4, { className: "size-3", strokeWidth: 3 })
22336
+ children: /* @__PURE__ */ jsx34(Check5, { className: "size-3", strokeWidth: 3 })
22247
22337
  }
22248
22338
  ) : null
22249
22339
  ]
@@ -23268,7 +23358,7 @@ function useSectionDrag({
23268
23358
  }
23269
23359
 
23270
23360
  // src/ui/footer-container-chrome.tsx
23271
- import { Plus as Plus3 } from "lucide-react";
23361
+ import { Plus as Plus4 } from "lucide-react";
23272
23362
  import { jsx as jsx35, jsxs as jsxs24 } from "react/jsx-runtime";
23273
23363
  function FooterContainerChrome({
23274
23364
  rect,
@@ -23309,7 +23399,7 @@ function FooterContainerChrome({
23309
23399
  if (addDisabled) return;
23310
23400
  onAdd();
23311
23401
  },
23312
- children: /* @__PURE__ */ jsx35(Plus3, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
23402
+ children: /* @__PURE__ */ jsx35(Plus4, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
23313
23403
  }
23314
23404
  ) }),
23315
23405
  /* @__PURE__ */ jsx35(TooltipContent, { side: "bottom", sideOffset: 9, children: addDisabled ? "Maximum columns reached" : "Add item" })
@@ -23488,7 +23578,7 @@ function deleteSelectedNavFooterItem(deps) {
23488
23578
  }
23489
23579
 
23490
23580
  // src/ui/navbar-container-chrome.tsx
23491
- import { Plus as Plus4 } from "lucide-react";
23581
+ import { Plus as Plus5 } from "lucide-react";
23492
23582
  import { jsx as jsx36 } from "react/jsx-runtime";
23493
23583
  function NavbarContainerChrome({
23494
23584
  rect,
@@ -23524,7 +23614,7 @@ function NavbarContainerChrome({
23524
23614
  e.stopPropagation();
23525
23615
  onAdd();
23526
23616
  },
23527
- children: /* @__PURE__ */ jsx36(Plus4, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
23617
+ children: /* @__PURE__ */ jsx36(Plus5, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
23528
23618
  }
23529
23619
  )
23530
23620
  }
@@ -23596,7 +23686,7 @@ function Badge({ className, variant, ...props }) {
23596
23686
  }
23597
23687
 
23598
23688
  // src/OhhwellsBridge.tsx
23599
- import { Brush as Brush2, Link as Link2, Plus as Plus5, Settings } from "lucide-react";
23689
+ import { Brush as Brush2, Link as Link2, Plus as Plus6, Settings } from "lucide-react";
23600
23690
  import { Fragment as Fragment12, jsx as jsx39, jsxs as jsxs25 } from "react/jsx-runtime";
23601
23691
  var PRIMARY7 = "#0885FE";
23602
23692
  var IMAGE_FADE_MS = 300;
@@ -24373,7 +24463,7 @@ function getNavigationSelectionParent(el) {
24373
24463
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
24374
24464
  return resolveFooterLinksContainerFrom(el);
24375
24465
  }
24376
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
24466
+ 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)) {
24377
24467
  return getNavigationRoot(el);
24378
24468
  }
24379
24469
  return null;
@@ -24595,7 +24685,6 @@ var ICONS = {
24595
24685
  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"/>',
24596
24686
  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"/>'
24597
24687
  };
24598
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
24599
24688
  var SELECTION_CHROME_GAP2 = 4;
24600
24689
  var TOOLBAR_STROKE_GAP2 = 4;
24601
24690
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -24693,7 +24782,7 @@ function applyVisibleViewport(el, parentScroll) {
24693
24782
  el.style.top = `${top}px`;
24694
24783
  el.style.height = `${height}px`;
24695
24784
  }
24696
- var TOOLBAR_EDGE_MARGIN3 = 4;
24785
+ var TOOLBAR_EDGE_MARGIN4 = 4;
24697
24786
  function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
24698
24787
  const isAbove = transform.includes("translateY(-100%)");
24699
24788
  let visualTop = isAbove ? top - approxH : top;
@@ -24720,8 +24809,8 @@ function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
24720
24809
  function clampToolbarLeft(centerX, toolbarWidth) {
24721
24810
  const half = toolbarWidth / 2;
24722
24811
  return Math.max(
24723
- TOOLBAR_EDGE_MARGIN3 + half,
24724
- Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half)
24812
+ TOOLBAR_EDGE_MARGIN4 + half,
24813
+ Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN4 - half)
24725
24814
  );
24726
24815
  }
24727
24816
  function calcToolbarPos(rect, parentScroll, toolbarW = 306) {
@@ -25160,7 +25249,7 @@ function OhhwellsBridge() {
25160
25249
  }, []);
25161
25250
  const subdomainFromQuery = searchParams.get("subdomain");
25162
25251
  const [subdomain, setSubdomain] = useState18(() => resolveSubdomain(subdomainFromQuery));
25163
- useLayoutEffect6(() => {
25252
+ useLayoutEffect7(() => {
25164
25253
  const next = resolveSubdomain(subdomainFromQuery);
25165
25254
  if (next !== subdomain) setSubdomain(next);
25166
25255
  if (next) return;
@@ -25611,21 +25700,6 @@ function OhhwellsBridge() {
25611
25700
  const [isItemDragging, setIsItemDragging] = useState18(false);
25612
25701
  const [isFooterFrameSelection, setIsFooterFrameSelection] = useState18(false);
25613
25702
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
25614
- const [floatingPanel, setFloatingPanel] = useState18(null);
25615
- const floatingPanelOpenRef = useRef14(false);
25616
- floatingPanelOpenRef.current = floatingPanel !== null;
25617
- const floatingPanelRef = useRef14(null);
25618
- floatingPanelRef.current = floatingPanel;
25619
- const [floatingPanelPos, setFloatingPanelPos] = useState18(null);
25620
- const buttonCollectionDragRef = useRef14(null);
25621
- const buttonDropInsertIndexRef = useRef14(null);
25622
- const [buttonDropSlots, setButtonDropSlots] = useState18([]);
25623
- const [activeButtonDropIndex, setActiveButtonDropIndex] = useState18(null);
25624
- const buttonPointerDragRef = useRef14(null);
25625
- const [buttonStyleTick, setButtonStyleTick] = useState18(0);
25626
- const [logoSizeDraft, setLogoSizeDraft] = useState18(null);
25627
- const [editorViewport, setEditorViewport] = useState18("desktop");
25628
- const [parentScrollSnap, setParentScrollSnap] = useState18(null);
25629
25703
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = useState18(null);
25630
25704
  const [footerHeadingVisible, setFooterHeadingVisible] = useState18(null);
25631
25705
  const footerDragRef = useRef14(null);
@@ -25651,6 +25725,21 @@ function OhhwellsBridge() {
25651
25725
  const stylesRef = useRef14("");
25652
25726
  const appliedDrawerRef = useRef14(null);
25653
25727
  const pendingDeleteUndoRef = useRef14(null);
25728
+ const [floatingPanel, setFloatingPanel] = useState18(null);
25729
+ const floatingPanelOpenRef = useRef14(false);
25730
+ const floatingPanelRef = useRef14(null);
25731
+ floatingPanelRef.current = floatingPanel;
25732
+ const setFloatingPanelRef = useRef14(setFloatingPanel);
25733
+ const [floatingPanelPos, setFloatingPanelPos] = useState18(null);
25734
+ const buttonCollectionDragRef = useRef14(null);
25735
+ const buttonDropInsertIndexRef = useRef14(null);
25736
+ const [buttonDropSlots, setButtonDropSlots] = useState18([]);
25737
+ const [activeButtonDropIndex, setActiveButtonDropIndex] = useState18(null);
25738
+ const buttonPointerDragRef = useRef14(null);
25739
+ const [buttonStyleTick, setButtonStyleTick] = useState18(0);
25740
+ const [logoSizeDraft, setLogoSizeDraft] = useState18(null);
25741
+ const [editorViewport, setEditorViewport] = useState18("desktop");
25742
+ const [parentScrollSnap, setParentScrollSnap] = useState18(null);
25654
25743
  const [sitePages, setSitePages] = useState18([]);
25655
25744
  const [sectionsByPath, setSectionsByPath] = useState18({});
25656
25745
  const sectionsPrefetchGenRef = useRef14(0);
@@ -25659,7 +25748,18 @@ function OhhwellsBridge() {
25659
25748
  const linkPopoverOpenRef = useRef14(false);
25660
25749
  const linkPopoverGraceUntilRef = useRef14(0);
25661
25750
  setLinkPopoverRef.current = setLinkPopover;
25751
+ setFloatingPanelRef.current = setFloatingPanel;
25662
25752
  linkPopoverSessionRef.current = linkPopover;
25753
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
25754
+ useEffect17(() => {
25755
+ const syncViewport = () => {
25756
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
25757
+ setEditorViewport((prev) => prev === next ? prev : next);
25758
+ };
25759
+ syncViewport();
25760
+ window.addEventListener("resize", syncViewport);
25761
+ return () => window.removeEventListener("resize", syncViewport);
25762
+ }, []);
25663
25763
  useEffect17(() => {
25664
25764
  setSectionOrderPersistHandler((entries) => {
25665
25765
  const currentPath = window.location.pathname;
@@ -27327,7 +27427,7 @@ function OhhwellsBridge() {
27327
27427
  isFooterFrameSelection,
27328
27428
  postToParent2
27329
27429
  ]);
27330
- useLayoutEffect6(() => {
27430
+ useLayoutEffect7(() => {
27331
27431
  if (!subdomain || isEditMode) {
27332
27432
  setFetchState("done");
27333
27433
  return;
@@ -27354,6 +27454,7 @@ function OhhwellsBridge() {
27354
27454
  applyAiFooterFromContent(content);
27355
27455
  if (typeof content[STYLE_STORE_KEY] === "string") {
27356
27456
  stylesRef.current = content[STYLE_STORE_KEY];
27457
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
27357
27458
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
27358
27459
  }
27359
27460
  applyBrandChrome(content);
@@ -27364,12 +27465,12 @@ function OhhwellsBridge() {
27364
27465
  if (key === "__ohw_sections") continue;
27365
27466
  if (key === AI_SECTIONS_KEY) continue;
27366
27467
  if (key === AI_FOOTER_KEY) continue;
27468
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
27469
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
27367
27470
  if (key === BRAND_KIT_KEY) continue;
27368
27471
  if (key === STYLE_STORE_KEY) continue;
27369
27472
  if (BRAND_CHROME_KEYS.has(key)) continue;
27370
- if (key === LOGO_PLACEHOLDER_KEY) continue;
27371
27473
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
27372
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
27373
27474
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
27374
27475
  continue;
27375
27476
  }
@@ -27584,17 +27685,18 @@ function OhhwellsBridge() {
27584
27685
  }
27585
27686
  applyAiFooterFromContent(content);
27586
27687
  if (typeof content[STYLE_STORE_KEY] === "string") {
27688
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
27587
27689
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
27588
27690
  }
27589
27691
  for (const [key, val] of Object.entries(content)) {
27590
27692
  if (key === "__ohw_sections") continue;
27591
27693
  if (key === AI_SECTIONS_KEY) continue;
27592
27694
  if (key === AI_FOOTER_KEY) continue;
27695
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
27696
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
27593
27697
  if (key === BRAND_KIT_KEY) continue;
27594
27698
  if (key === STYLE_STORE_KEY) continue;
27595
27699
  if (BRAND_CHROME_KEYS.has(key)) continue;
27596
- if (key === LOGO_PLACEHOLDER_KEY) continue;
27597
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
27598
27700
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
27599
27701
  continue;
27600
27702
  }
@@ -27667,7 +27769,7 @@ function OhhwellsBridge() {
27667
27769
  if (debounceTimer) clearTimeout(debounceTimer);
27668
27770
  };
27669
27771
  }, [subdomain, isEditMode, pathname]);
27670
- useLayoutEffect6(() => {
27772
+ useLayoutEffect7(() => {
27671
27773
  const el = document.getElementById("ohw-loader");
27672
27774
  if (!el) return;
27673
27775
  const visible = Boolean(subdomain) && fetchState !== "done";
@@ -27851,26 +27953,11 @@ function OhhwellsBridge() {
27851
27953
  const t2 = setTimeout(measure, 500);
27852
27954
  const ro = new ResizeObserver(schedule);
27853
27955
  ro.observe(document.body);
27854
- let lastWidth = window.innerWidth;
27855
- let resizeTimers = [];
27856
- const clearResizeTimers = () => {
27857
- resizeTimers.forEach(clearTimeout);
27858
- resizeTimers = [];
27859
- };
27860
- const handleResize = () => {
27861
- if (window.innerWidth === lastWidth) return;
27862
- lastWidth = window.innerWidth;
27863
- clearResizeTimers();
27864
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
27865
- };
27866
- window.addEventListener("resize", handleResize);
27867
27956
  return () => {
27868
27957
  clearTimeout(t1);
27869
27958
  clearTimeout(t2);
27870
27959
  if (raf != null) cancelAnimationFrame(raf);
27871
27960
  ro.disconnect();
27872
- clearResizeTimers();
27873
- window.removeEventListener("resize", handleResize);
27874
27961
  };
27875
27962
  }, [pathname, isEditMode, postToParent2]);
27876
27963
  useEffect17(() => {
@@ -28169,6 +28256,13 @@ function OhhwellsBridge() {
28169
28256
  if (target.closest("[data-ohw-state-toggle]")) return;
28170
28257
  if (target.closest("[data-ohw-max-badge]")) return;
28171
28258
  if (isInsideLinkEditor(target)) return;
28259
+ if (isInsideFloatingPanel(target)) return;
28260
+ if (target.closest("[data-ohw-form-toolbar]")) return;
28261
+ if (target.closest(
28262
+ '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
28263
+ )) {
28264
+ return;
28265
+ }
28172
28266
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
28173
28267
  clearMediaSelectionRef.current();
28174
28268
  }
@@ -28192,13 +28286,6 @@ function OhhwellsBridge() {
28192
28286
  return;
28193
28287
  }
28194
28288
  }
28195
- if (isInsideFloatingPanel(target)) return;
28196
- if (target.closest("[data-ohw-form-toolbar]")) return;
28197
- if (target.closest(
28198
- '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
28199
- )) {
28200
- return;
28201
- }
28202
28289
  {
28203
28290
  const formEl = getFormElement(target);
28204
28291
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -28352,9 +28439,13 @@ function OhhwellsBridge() {
28352
28439
  if (isIconEditable(editable) && !getSocialItem(editable)) {
28353
28440
  e.preventDefault();
28354
28441
  e.stopPropagation();
28355
- selectIconElementRef.current(editable);
28356
28442
  const aiIcon = editable.hasAttribute("data-ai-icon");
28357
28443
  const featureIcon = aiIcon && /\.features\.\d+\.icon$/.test(editable.dataset.ohwKey ?? "");
28444
+ if (aiIcon) {
28445
+ aiSectionApiRef.current?.selectFromElement(editable);
28446
+ } else {
28447
+ selectIconElementRef.current(editable);
28448
+ }
28358
28449
  postToParentRef.current({
28359
28450
  type: "ow:icon-pick",
28360
28451
  key: editable.dataset.ohwKey ?? "",
@@ -28582,6 +28673,9 @@ function OhhwellsBridge() {
28582
28673
  setHoveredItemRect(null);
28583
28674
  hoveredNavContainerRef.current = null;
28584
28675
  setHoveredNavContainerRect(null);
28676
+ siblingHintElRef.current = null;
28677
+ setSiblingHintRect(null);
28678
+ setSiblingHintRects([]);
28585
28679
  return;
28586
28680
  }
28587
28681
  {
@@ -28702,7 +28796,6 @@ function OhhwellsBridge() {
28702
28796
  hoveredNavContainerRef.current = null;
28703
28797
  setHoveredNavContainerRect(null);
28704
28798
  hoveredItemElRef.current = editable;
28705
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
28706
28799
  }
28707
28800
  }
28708
28801
  }
@@ -29412,7 +29505,11 @@ function OhhwellsBridge() {
29412
29505
  };
29413
29506
  const handleMouseMove = (e) => {
29414
29507
  const { clientX, clientY } = e;
29415
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
29508
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
29509
+ dismissImageHover();
29510
+ clearImageHover();
29511
+ return;
29512
+ }
29416
29513
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) {
29417
29514
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
29418
29515
  formHoverElRef.current = null;
@@ -29441,7 +29538,12 @@ function OhhwellsBridge() {
29441
29538
  setHoveredItemRect(null);
29442
29539
  hoveredNavContainerRef.current = null;
29443
29540
  setHoveredNavContainerRect(null);
29541
+ siblingHintElRef.current = null;
29542
+ setSiblingHintRect(null);
29543
+ setSiblingHintRects([]);
29444
29544
  dismissImageHover();
29545
+ clearImageHover();
29546
+ setSectionGap(null);
29445
29547
  return;
29446
29548
  }
29447
29549
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -29453,7 +29555,11 @@ function OhhwellsBridge() {
29453
29555
  if (e.data?.type !== "ow:pointer-sync") return;
29454
29556
  const { clientX, clientY } = e.data;
29455
29557
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
29456
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
29558
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
29559
+ dismissImageHover();
29560
+ clearImageHover();
29561
+ return;
29562
+ }
29457
29563
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) return;
29458
29564
  if (probeSocialsRowAt(clientX, clientY)) return;
29459
29565
  probeSectionGapAt(clientX, clientY);
@@ -29833,6 +29939,7 @@ function OhhwellsBridge() {
29833
29939
  applyAiFooterFromContent(content);
29834
29940
  if (typeof content[STYLE_STORE_KEY] === "string") {
29835
29941
  stylesRef.current = content[STYLE_STORE_KEY];
29942
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
29836
29943
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
29837
29944
  }
29838
29945
  applyBrandChrome(content);
@@ -29847,12 +29954,12 @@ function OhhwellsBridge() {
29847
29954
  }
29848
29955
  if (key === AI_SECTIONS_KEY) continue;
29849
29956
  if (key === AI_FOOTER_KEY) continue;
29957
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
29958
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
29850
29959
  if (key === BRAND_KIT_KEY) continue;
29851
29960
  if (key === STYLE_STORE_KEY) continue;
29852
29961
  if (BRAND_CHROME_KEYS.has(key)) continue;
29853
- if (key === LOGO_PLACEHOLDER_KEY) continue;
29854
29962
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
29855
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
29856
29963
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
29857
29964
  continue;
29858
29965
  }
@@ -30227,6 +30334,44 @@ function OhhwellsBridge() {
30227
30334
  });
30228
30335
  };
30229
30336
  window.addEventListener("message", handleAiSetStyles);
30337
+ const handleAiSetButtonStyle = (e) => {
30338
+ if (e.data?.type !== "ow:ai-set-button-style") return;
30339
+ const nodeKey = typeof e.data.hrefKey === "string" ? e.data.hrefKey : "";
30340
+ const keyed = nodeKey ? document.querySelector(
30341
+ `[data-ohw-href-key="${CSS.escape(nodeKey)}"], [data-ohw-key="${CSS.escape(nodeKey)}"]`
30342
+ ) : null;
30343
+ const hrefKey = buttonId(keyed);
30344
+ const el = hrefKey ? document.querySelector(`[data-ohw-href-key="${CSS.escape(hrefKey)}"]`) : null;
30345
+ const previous = editContentRef.current[BUTTON_ASSIGNMENTS_KEY] ?? "";
30346
+ if (!el || !hrefKey || !isCtaButton(el)) {
30347
+ postToParentRef.current({ type: "ow:ai-button-style-applied", ok: false, previous, value: previous });
30348
+ return;
30349
+ }
30350
+ const tokens = tokensFromButton(el);
30351
+ const current = assignmentFor(el, editContentRef.current, tokens);
30352
+ const patch = e.data.assignment;
30353
+ const next = {
30354
+ variant: patch?.variant ?? current.variant,
30355
+ size: patch?.size ?? current.size
30356
+ };
30357
+ const value = typeof e.data.assignments === "string" ? e.data.assignments : JSON.stringify(withAssignment(editContentRef.current, hrefKey, next));
30358
+ editContentRef.current = { ...editContentRef.current, [BUTTON_ASSIGNMENTS_KEY]: value };
30359
+ applyButtonStyle(el, next, tokens);
30360
+ postToParentRef.current({
30361
+ type: "ow:change",
30362
+ nodes: [{ key: BUTTON_ASSIGNMENTS_KEY, text: value }]
30363
+ });
30364
+ postToParentRef.current({
30365
+ type: "ow:ai-button-style-applied",
30366
+ ok: true,
30367
+ // The resolved id, which may differ from the key the editor sent — undo must target this one.
30368
+ hrefKey,
30369
+ previous,
30370
+ previousAssignment: current,
30371
+ value
30372
+ });
30373
+ };
30374
+ window.addEventListener("message", handleAiSetButtonStyle);
30230
30375
  const handleGetBrand = (e) => {
30231
30376
  if (e.data?.type !== "ow:get-brand") return;
30232
30377
  const template = deriveTemplateBrand();
@@ -30450,6 +30595,12 @@ function OhhwellsBridge() {
30450
30595
  closeLinkPopoverRef.current();
30451
30596
  return;
30452
30597
  }
30598
+ if (floatingPanelOpenRef.current) {
30599
+ setFloatingPanelRef.current(null);
30600
+ deselectRef.current();
30601
+ deactivateRef.current();
30602
+ return;
30603
+ }
30453
30604
  deselectRef.current();
30454
30605
  deactivateRef.current();
30455
30606
  clearMediaSelectionRef.current();
@@ -31260,6 +31411,7 @@ function OhhwellsBridge() {
31260
31411
  window.removeEventListener("message", handleAiSetSections);
31261
31412
  window.removeEventListener("message", handleAiSetBrand);
31262
31413
  window.removeEventListener("message", handleAiSetStyles);
31414
+ window.removeEventListener("message", handleAiSetButtonStyle);
31263
31415
  window.removeEventListener("message", handleCollectItems);
31264
31416
  window.removeEventListener("message", handleAiApplyItems);
31265
31417
  window.removeEventListener("message", handleGetBrand);
@@ -31271,10 +31423,10 @@ function OhhwellsBridge() {
31271
31423
  window.removeEventListener("message", handleDeleteSection);
31272
31424
  window.removeEventListener("message", handleDuplicateSection);
31273
31425
  window.removeEventListener("message", handleDeactivate);
31274
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
31275
31426
  window.removeEventListener("message", handleToastAction);
31276
31427
  window.removeEventListener("message", handleFormCount);
31277
31428
  window.removeEventListener("message", handleUiEscape);
31429
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
31278
31430
  autoSaveTimers.current.forEach(clearTimeout);
31279
31431
  autoSaveTimers.current.clear();
31280
31432
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -31568,7 +31720,7 @@ function OhhwellsBridge() {
31568
31720
  postToParent2({
31569
31721
  type: "ow:ready",
31570
31722
  version: "1",
31571
- bridgeVersion: "0.1.147",
31723
+ bridgeVersion: "0.1.149",
31572
31724
  path: pathname,
31573
31725
  nodes: collectEditableNodes(editContentRef.current),
31574
31726
  sections
@@ -32304,7 +32456,7 @@ function OhhwellsBridge() {
32304
32456
  className: "flex h-7 w-7 items-center justify-center rounded-md text-foreground transition-colors hover:bg-muted/80",
32305
32457
  onClick: () => setFieldTypePickerOpen((open) => !open),
32306
32458
  "data-ohw-add-field": "",
32307
- children: /* @__PURE__ */ jsx39(Plus5, { size: 15, "aria-hidden": true })
32459
+ children: /* @__PURE__ */ jsx39(Plus6, { size: 15, "aria-hidden": true })
32308
32460
  }
32309
32461
  ),
32310
32462
  /* @__PURE__ */ jsx39("div", { className: "mx-0.5 h-5 w-px bg-border" }),