@ohhwells/bridge 0.1.147 → 0.1.149-next.400

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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
 
@@ -1476,7 +1476,19 @@ function brandTrackedFontFamily(doc, stack) {
1476
1476
  }
1477
1477
  var paintLockByEl = /* @__PURE__ */ new WeakMap();
1478
1478
  var paintObserverByEl = /* @__PURE__ */ new WeakMap();
1479
- var paintingButton = false;
1479
+ var PAINT_GUARD_ATTRS = {
1480
+ attributes: true,
1481
+ attributeFilter: ["style", "class"]
1482
+ };
1483
+ function paintGuarded(el, assignment, tokens) {
1484
+ const observer = paintObserverByEl.get(el);
1485
+ observer?.disconnect();
1486
+ try {
1487
+ paintButtonChrome(el, assignment, tokens);
1488
+ } finally {
1489
+ observer?.observe(el, PAINT_GUARD_ATTRS);
1490
+ }
1491
+ }
1480
1492
  function paintButtonChrome(el, assignment, tokens) {
1481
1493
  const origin = captureButtonOrigin(el);
1482
1494
  const chrome = tokens.variants[assignment.variant];
@@ -1515,14 +1527,11 @@ function paintButtonChrome(el, assignment, tokens) {
1515
1527
  function guardTemplateHover(el) {
1516
1528
  if (paintObserverByEl.has(el)) return;
1517
1529
  const observer = new MutationObserver(() => {
1518
- if (paintingButton) return;
1519
1530
  const lock = paintLockByEl.get(el);
1520
1531
  if (!lock) return;
1521
- paintingButton = true;
1522
- paintButtonChrome(el, lock.assignment, lock.tokens);
1523
- paintingButton = false;
1532
+ paintGuarded(el, lock.assignment, lock.tokens);
1524
1533
  });
1525
- observer.observe(el, { attributes: true, attributeFilter: ["style", "class"] });
1534
+ observer.observe(el, PAINT_GUARD_ATTRS);
1526
1535
  paintObserverByEl.set(el, observer);
1527
1536
  }
1528
1537
  function paintedTokensOf(el) {
@@ -1539,10 +1548,8 @@ function applyButtonStyle(el, assignment, tokens) {
1539
1548
  }
1540
1549
  };
1541
1550
  paintLockByEl.set(el, { assignment, tokens: tracked });
1542
- paintingButton = true;
1543
- paintButtonChrome(el, assignment, tracked);
1544
- paintingButton = false;
1545
1551
  guardTemplateHover(el);
1552
+ paintGuarded(el, assignment, tracked);
1546
1553
  }
1547
1554
  function applyButtonStylesFromContent(doc, content) {
1548
1555
  const assignments = readAssignments(content);
@@ -1624,7 +1631,8 @@ function labelElement2(el) {
1624
1631
  return el.querySelector('[data-ohw-editable="text"], [data-ohw-editable="plain"]') ?? el.querySelector("[data-ohw-key]");
1625
1632
  }
1626
1633
  function buttonLabel(el) {
1627
- return (labelElement2(el)?.textContent ?? el.textContent ?? "").trim();
1634
+ const label = labelElement2(el);
1635
+ return (label ? label.innerHTML : el.textContent ?? "").trim();
1628
1636
  }
1629
1637
  function buttonHref(el) {
1630
1638
  return el.getAttribute("href") ?? "";
@@ -1763,7 +1771,7 @@ function duplicateButton(source, content) {
1763
1771
  stampVariantClass(copy, copyAssignment.variant);
1764
1772
  applyButtonStyle(copy, copyAssignment, paintedTokensOf(source) ?? tokens);
1765
1773
  const copyLabelEl = labelElement2(copy);
1766
- if (copyLabelEl) copyLabelEl.textContent = label;
1774
+ if (copyLabelEl) copyLabelEl.innerHTML = label;
1767
1775
  let collection = buttonCollectionOf(source);
1768
1776
  const collections = readCollections(content);
1769
1777
  let collectionId = collection?.getAttribute(COLLECTION_ATTR) ?? "";
@@ -2089,7 +2097,7 @@ function applySnapshotContent(el, snapshot, content) {
2089
2097
  const label = labelElement2(el);
2090
2098
  if (label) {
2091
2099
  if (label.getAttribute("data-ohw-key") !== labelKey) label.setAttribute("data-ohw-key", labelKey);
2092
- if (label.textContent !== text) label.textContent = text;
2100
+ if (label.innerHTML !== text) label.innerHTML = text;
2093
2101
  }
2094
2102
  }
2095
2103
  function reconcileButtonCollections(doc, content) {
@@ -4174,7 +4182,7 @@ function buildNavLink(index, href, label, template) {
4174
4182
  const span = document.createElement("span");
4175
4183
  span.setAttribute("data-ohw-editable", "text");
4176
4184
  span.setAttribute("data-ohw-key", `nav-${index}-label`);
4177
- span.textContent = label;
4185
+ span.innerHTML = label;
4178
4186
  anchor.appendChild(span);
4179
4187
  return anchor;
4180
4188
  }
@@ -6534,6 +6542,21 @@ function pricingCardFeatureCount(tree, index) {
6534
6542
  const target = findPricingCollection(tree)?.children?.[index];
6535
6543
  return target ? pricingFeatureList(target).length : 0;
6536
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
+ }
6537
6560
  function addPricingCardFeature(tree, index, text) {
6538
6561
  return withPricingCollection(tree, (collection) => {
6539
6562
  const children = collection.children;
@@ -6542,7 +6565,7 @@ function addPricingCardFeature(tree, index, text) {
6542
6565
  const features = pricingFeatureList(target);
6543
6566
  if (features.length >= PRICING_FEATURES_MAX) return false;
6544
6567
  target.slots = target.slots ?? {};
6545
- target.slots.features = [...features, { text }];
6568
+ target.slots.features = [...features, { text, ...prevailingFeatureIcon(features) }];
6546
6569
  return true;
6547
6570
  });
6548
6571
  }
@@ -7725,7 +7748,7 @@ var AI_MOBILE_CSS = [
7725
7748
  // page's first section. Top only (bottom needs no nav clearance); ~96px approximates the
7726
7749
  // template nav's own height (measured ~94px) — there's no shared `--ohw-header-h` var to read
7727
7750
  // an exact figure from (no live measurement exists), so this is a safe approximation, not a
7728
- // pixel-exact fit. Horizontal spacing already comes from `data-ai-container`.
7751
+ // pixel-exact fit. Horizontal spacing already comes from `data-ai-section-inner`.
7729
7752
  '[data-ai-overlay-clearance] [data-ai-overlap-layer="overlay"]{padding-top:96px}',
7730
7753
  // A full-bleed carousel's height is pure arithmetic: it spans the whole window, so 16/9 turns
7731
7754
  // window width straight into height. Measured at 16/9 it came out ~90% of the viewport at EVERY
@@ -7741,8 +7764,10 @@ var AI_MOBILE_CSS = [
7741
7764
  "[data-ai-section]{overflow-x:hidden}",
7742
7765
  // `:not([data-ai-full-bleed])`: this `!important` gutter beat the inline `padding: 0` that a
7743
7766
  // full-bleed section's container sets, so a carousel/media hero that is edge-to-edge on desktop
7744
- // still got a 20px band of section background down both sides on a phone.
7745
- "[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}",
7746
7771
  "[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
7747
7772
  "[data-ai-cell]{width:100%;min-width:0}",
7748
7773
  // The rule above turns every row into a COLUMN flex container on phones, which silently breaks
@@ -7947,6 +7972,31 @@ function pricingCardEmphasis(featured, ctx, hasBg) {
7947
7972
  function textAttrs(ctx, path) {
7948
7973
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
7949
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");
7950
8000
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
7951
8001
  function MediaBox({
7952
8002
  refValue,
@@ -8001,7 +8051,7 @@ function MediaBox({
8001
8051
  position: fill ? "absolute" : void 0,
8002
8052
  inset: fill ? 0 : void 0,
8003
8053
  width: fill ? "100%" : fixedSize ? "auto" : "100%",
8004
- height: fill ? "100%" : void 0,
8054
+ height: fill ? "100%" : fixedSize ? height : void 0,
8005
8055
  aspectRatio: fill ? void 0 : aspectRatio,
8006
8056
  minHeight: fill || fixedSize ? void 0 : height ?? (aspectRatio ? void 0 : 180),
8007
8057
  borderRadius: radius,
@@ -9497,7 +9547,7 @@ function CollectionBlock({ node, ctx, path }) {
9497
9547
  return /* @__PURE__ */ jsx(
9498
9548
  "div",
9499
9549
  {
9500
- "data-ai-grid": "",
9550
+ "data-ai-grid": String(itemsPerRow),
9501
9551
  "data-ohw-pricing-collection": isPricing ? "" : void 0,
9502
9552
  style: {
9503
9553
  display: "grid",
@@ -9799,7 +9849,7 @@ function renderNode(node, ctx, path, ambientAlign) {
9799
9849
  },
9800
9850
  children: [
9801
9851
  i > 0 && ctx.heroOverlapFullBleed ? (
9802
- // 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
9803
9853
  // layer (this group's i===0 sibling) can span edge-to-edge — reintroduced here,
9804
9854
  // scoped to just the overlay text, so a heading/copy/CTA stack doesn't stretch
9805
9855
  // the full section width instead of reading at the usual content width.
@@ -10218,6 +10268,7 @@ function AiTreeRenderer({
10218
10268
  ...fillHeight ? { "data-ai-fill-height": "" } : {},
10219
10269
  ...needsOverlayClearance ? { "data-ai-overlay-clearance": "" } : {},
10220
10270
  ...bgAttrs,
10271
+ "data-ai-responsive": "",
10221
10272
  style: {
10222
10273
  position: "relative",
10223
10274
  padding: `${padCss} 0`,
@@ -10231,12 +10282,13 @@ function AiTreeRenderer({
10231
10282
  ...fillHeight ? { display: "grid" } : {}
10232
10283
  },
10233
10284
  children: [
10234
- isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10285
+ /* @__PURE__ */ jsx("style", { children: AI_RESPONSIVE_CSS }),
10235
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})` } }),
10236
10288
  /* @__PURE__ */ jsx(
10237
10289
  "div",
10238
10290
  {
10239
- "data-ai-container": "",
10291
+ "data-ai-section-inner": "",
10240
10292
  ...fullBleedMedia ? { "data-ai-full-bleed": "" } : {},
10241
10293
  style: {
10242
10294
  position: "relative",
@@ -10253,7 +10305,7 @@ function AiTreeRenderer({
10253
10305
  children: tree.rows.map((row, r2) => /* @__PURE__ */ jsx(
10254
10306
  "div",
10255
10307
  {
10256
- "data-ai-row": "",
10308
+ "data-ai-columns": "",
10257
10309
  style: {
10258
10310
  display: "grid",
10259
10311
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -10278,7 +10330,12 @@ function AiTreeRenderer({
10278
10330
  // the full row height instead of clumping at the top.
10279
10331
  ...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
10280
10332
  },
10281
- 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
+ )
10282
10339
  },
10283
10340
  b
10284
10341
  ))
@@ -10910,9 +10967,10 @@ function applyAiSectionsToDom(state, options) {
10910
10967
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
10911
10968
  continue;
10912
10969
  }
10913
- const isNewMount = !existing || !existing.container.isConnected;
10914
10970
  let container = existing?.container;
10971
+ let isNewMount = false;
10915
10972
  if (!container || !container.isConnected) {
10973
+ isNewMount = true;
10916
10974
  existing?.root.unmount();
10917
10975
  container?.remove();
10918
10976
  container = document.createElement("div");
@@ -17354,6 +17412,7 @@ function MediaOverlay({
17354
17412
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
17355
17413
  );
17356
17414
  }, [isVideo]);
17415
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
17357
17416
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
17358
17417
  const box = {
17359
17418
  position: "fixed",
@@ -17452,11 +17511,10 @@ function MediaOverlay({
17452
17511
  className: "flex items-center justify-center cursor-pointer",
17453
17512
  style: {
17454
17513
  ...box,
17455
- // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
17456
- // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
17457
- // in-document, pointer-events does it natively. The button below opts back in, so
17458
- // Replace still works.
17459
- 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",
17460
17518
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
17461
17519
  // than hovered. Hover keeps the existing tinted preview.
17462
17520
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -17483,17 +17541,17 @@ function MediaOverlay({
17483
17541
  },
17484
17542
  children: [
17485
17543
  isVideo ? /* @__PURE__ */ jsx16(Film, { size: 14 }) : /* @__PURE__ */ jsx16(ImageIcon, { size: 14 }),
17486
- isVideo ? "Replace video" : "Replace image"
17544
+ replaceLabel
17487
17545
  ]
17488
17546
  }
17489
17547
  ),
17490
- replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ jsxs7(
17548
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ jsxs7(
17491
17549
  Button,
17492
17550
  {
17493
17551
  "data-ohw-media-overlay": "",
17494
17552
  variant: "outline",
17495
17553
  size: "sm",
17496
- "aria-label": isVideo ? "Replace video" : "Replace image",
17554
+ "aria-label": replaceLabel,
17497
17555
  className: "gap-1.5 cursor-pointer hover:bg-background",
17498
17556
  style: {
17499
17557
  ...OVERLAY_BUTTON_STYLE,
@@ -17516,7 +17574,7 @@ function MediaOverlay({
17516
17574
  },
17517
17575
  children: [
17518
17576
  isVideo ? /* @__PURE__ */ jsx16(Film, { size: 14 }) : /* @__PURE__ */ jsx16(ImageIcon, { size: 14 }),
17519
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
17577
+ replaceMode === "full" ? replaceLabel : null
17520
17578
  ]
17521
17579
  }
17522
17580
  )
@@ -18025,8 +18083,24 @@ function AiSectionOverlay({
18025
18083
  }
18026
18084
 
18027
18085
  // src/ui/ai-section/AiMediaStripOverlay.tsx
18028
- import { useCallback as useCallback3, useEffect as useEffect6, useRef as useRef5, useState as useState8 } from "react";
18029
- 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";
18030
18104
 
18031
18105
  // src/lib/ai-media-strip-tree.ts
18032
18106
  var MEDIA_STRIP_ITEM_MIN = 1;
@@ -18140,14 +18214,6 @@ function setMediaStripItemAsset(tree, index, assetId) {
18140
18214
  return true;
18141
18215
  });
18142
18216
  }
18143
- function setMediaStripItemAlt(tree, index, alt) {
18144
- return withMediaStrip(tree, (block) => {
18145
- const items = block.items;
18146
- if (index < 0 || index >= items.length) return false;
18147
- items[index] = { ...items[index], alt };
18148
- return true;
18149
- });
18150
- }
18151
18217
  function setMediaStripItemLink(tree, index, link) {
18152
18218
  return withMediaStrip(tree, (block) => {
18153
18219
  const items = block.items;
@@ -18165,11 +18231,117 @@ function setMediaStripItemLink(tree, index, link) {
18165
18231
  });
18166
18232
  }
18167
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
+
18168
18341
  // src/ui/ai-section/AiMediaStripOverlay.tsx
18169
- import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs10 } from "react/jsx-runtime";
18170
- var PRIMARY3 = "#0885FE";
18171
- var OHW_BLUE = `var(--ohw-primary, ${PRIMARY3})`;
18172
- 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]";
18173
18345
  function findMediaStripItemElement(sectionId, index) {
18174
18346
  const sectionEl = findByInstanceId(sectionId);
18175
18347
  if (!sectionEl) return null;
@@ -18179,14 +18351,35 @@ function findMediaStripItemElement(sectionId, index) {
18179
18351
  }
18180
18352
  return null;
18181
18353
  }
18182
- function readRect2(ref) {
18183
- 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) {
18184
18370
  if (!el) return null;
18185
18371
  const r2 = el.getBoundingClientRect();
18186
18372
  if (r2.width <= 0 || r2.height <= 0) return null;
18187
18373
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
18188
18374
  }
18189
- 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) {
18190
18383
  const [rect, setRect] = useState8(null);
18191
18384
  useEffect6(() => {
18192
18385
  if (!ref) {
@@ -18194,7 +18387,7 @@ function useLiveItemRect(ref) {
18194
18387
  return;
18195
18388
  }
18196
18389
  const update = () => {
18197
- const next = readRect2(ref);
18390
+ const next = strip ? readStripRect(ref) : readRect2(ref);
18198
18391
  setRect(
18199
18392
  (prev) => prev && next && prev.top === next.top && prev.left === next.left && prev.width === next.width && prev.height === next.height ? prev : next
18200
18393
  );
@@ -18209,9 +18402,49 @@ function useLiveItemRect(ref) {
18209
18402
  window.removeEventListener("resize", update);
18210
18403
  clearInterval(interval);
18211
18404
  };
18212
- }, [ref?.sectionId, ref?.index]);
18405
+ }, [ref?.sectionId, ref?.index, strip]);
18213
18406
  return rect;
18214
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
+ }
18215
18448
  function requestValidateTree(postToParent2, tree) {
18216
18449
  return new Promise((resolve) => {
18217
18450
  const id = `ai-validate-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -18248,221 +18481,177 @@ function requestAssetPick(postToParent2) {
18248
18481
  postToParent2({ type: "ow:asset-pick", id });
18249
18482
  });
18250
18483
  }
18251
- function Pill({
18484
+ function ToolbarAction({
18252
18485
  label,
18253
18486
  onClick,
18254
- disabled,
18255
- active
18487
+ disabled = false,
18488
+ active = false,
18489
+ children
18256
18490
  }) {
18257
- return /* @__PURE__ */ jsx19(
18258
- "button",
18259
- {
18260
- type: "button",
18261
- onClick,
18262
- disabled,
18263
- style: {
18264
- height: 28,
18265
- padding: "0 10px",
18266
- display: "inline-flex",
18267
- alignItems: "center",
18268
- gap: 4,
18269
- borderRadius: 999,
18270
- border: "none",
18271
- cursor: disabled ? "default" : "pointer",
18272
- background: active ? OHW_BLUE_DARK : "#ffffff",
18273
- color: active ? "#ffffff" : OHW_BLUE_DARK,
18274
- opacity: disabled ? 0.4 : 1,
18275
- fontSize: 12,
18276
- lineHeight: "16px",
18277
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18278
- whiteSpace: "nowrap",
18279
- flex: "0 0 auto"
18280
- },
18281
- children: label
18282
- }
18283
- );
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
+ ] });
18284
18496
  }
18285
- function AltInput({ value, onCommit }) {
18286
- const [draft, setDraft] = useState8(value);
18287
- useEffect6(() => setDraft(value), [value]);
18288
- return /* @__PURE__ */ jsxs10(
18289
- "div",
18290
- {
18291
- style: {
18292
- display: "flex",
18293
- alignItems: "center",
18294
- gap: 6,
18295
- height: 28,
18296
- padding: "0 12px",
18297
- borderRadius: 999,
18298
- background: "#ffffff",
18299
- width: "100%",
18300
- maxWidth: 260,
18301
- boxSizing: "border-box"
18302
- },
18303
- children: [
18304
- /* @__PURE__ */ jsx19(
18305
- "span",
18306
- {
18307
- style: {
18308
- flex: "0 0 auto",
18309
- color: OHW_BLUE_DARK,
18310
- fontSize: 10,
18311
- fontWeight: 700,
18312
- textTransform: "uppercase",
18313
- letterSpacing: 0.4
18314
- },
18315
- children: "Alt"
18316
- }
18317
- ),
18318
- /* @__PURE__ */ jsx19(
18319
- "input",
18320
- {
18321
- type: "text",
18322
- value: draft,
18323
- onChange: (e) => setDraft(e.target.value),
18324
- onBlur: () => {
18325
- if (draft !== value) onCommit(draft);
18326
- },
18327
- onKeyDown: (e) => {
18328
- if (e.key === "Enter") e.currentTarget.blur();
18329
- if (e.key === "Escape") {
18330
- setDraft(value);
18331
- e.currentTarget.blur();
18332
- }
18333
- },
18334
- placeholder: "Describe this image",
18335
- "aria-label": "Image alt text",
18336
- style: {
18337
- flex: 1,
18338
- minWidth: 0,
18339
- border: "none",
18340
- outline: "none",
18341
- background: "transparent",
18342
- fontSize: 12,
18343
- lineHeight: "16px",
18344
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18345
- color: "#1f2937"
18346
- }
18347
- }
18348
- )
18349
- ]
18350
- }
18351
- );
18497
+ function ToolbarSeparator() {
18498
+ return /* @__PURE__ */ jsx20("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
18352
18499
  }
18353
- var VARIANTS = [
18354
- { value: "logos", label: "Logos" },
18355
- { value: "reel", label: "Reel" }
18356
- ];
18357
- var ALIGNS = [
18358
- { value: "left", label: "Left" },
18359
- { value: "center", label: "Center" },
18360
- { value: "right", label: "Right" }
18361
- ];
18362
- var HOVERS = [
18363
- { value: "none", label: "None" },
18364
- { value: "lift", label: "Lift" },
18365
- { value: "zoom", label: "Zoom" },
18366
- { value: "fade", label: "Fade" }
18367
- ];
18368
- var TREATMENTS = [
18369
- { value: "color", label: "Color" },
18370
- { value: "grayscale", label: "Grayscale" }
18371
- ];
18372
- var HTTP_URL_RE = /^https?:\/\/\S+$/i;
18373
- function LinkInput({ value, onCommit }) {
18374
- const [draft, setDraft] = useState8(value);
18375
- const [invalid, setInvalid] = useState8(false);
18376
- useEffect6(() => {
18377
- setDraft(value);
18378
- setInvalid(false);
18379
- }, [value]);
18380
- const commit = () => {
18381
- const next = draft.trim();
18382
- if (next === value) return setInvalid(false);
18383
- if (next && !HTTP_URL_RE.test(next)) return setInvalid(true);
18384
- setInvalid(false);
18385
- onCommit(next);
18386
- };
18387
- return /* @__PURE__ */ jsxs10(
18388
- "div",
18389
- {
18390
- style: {
18391
- display: "flex",
18392
- alignItems: "center",
18393
- gap: 6,
18394
- height: 28,
18395
- padding: "0 12px",
18396
- borderRadius: 999,
18397
- background: "#ffffff",
18398
- width: "100%",
18399
- maxWidth: 260,
18400
- boxSizing: "border-box",
18401
- boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
18402
- },
18403
- children: [
18404
- /* @__PURE__ */ jsx19(
18405
- "span",
18406
- {
18407
- style: {
18408
- flex: "0 0 auto",
18409
- color: OHW_BLUE_DARK,
18410
- fontSize: 10,
18411
- fontWeight: 700,
18412
- textTransform: "uppercase",
18413
- letterSpacing: 0.4
18414
- },
18415
- children: "Link"
18416
- }
18417
- ),
18418
- /* @__PURE__ */ jsx19(
18419
- "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,
18420
18526
  {
18421
- type: "text",
18422
- inputMode: "url",
18423
- value: draft,
18424
- onChange: (e) => setDraft(e.target.value),
18425
- onBlur: commit,
18426
- onKeyDown: (e) => {
18427
- if (e.key === "Enter") e.currentTarget.blur();
18428
- if (e.key === "Escape") {
18429
- setDraft(value);
18430
- setInvalid(false);
18431
- e.currentTarget.blur();
18432
- }
18433
- },
18434
- placeholder: "https://example.com",
18435
- "aria-label": "Image link URL",
18436
- "aria-invalid": invalid,
18437
- style: {
18438
- flex: 1,
18439
- minWidth: 0,
18440
- border: "none",
18441
- outline: "none",
18442
- background: "transparent",
18443
- fontSize: 12,
18444
- lineHeight: "16px",
18445
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18446
- color: "#1f2937"
18447
- }
18448
- }
18449
- )
18450
- ]
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;
18451
18572
  }
18452
- );
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
+ ] }) });
18453
18641
  }
18454
18642
  function MediaStripToolbar({
18455
18643
  entry,
18456
18644
  index,
18457
18645
  pending,
18458
18646
  error,
18647
+ itemEditOn,
18648
+ onToggleItemEdit,
18459
18649
  onSetVariant,
18460
18650
  onSetAlign,
18461
18651
  onSetHover,
18462
18652
  onSetTreatment,
18463
18653
  onResize,
18464
- onSetAlt,
18465
- onSetLink,
18654
+ onOpenLink,
18466
18655
  onAddItem,
18467
18656
  onRemoveItem,
18468
18657
  onChangeImage,
@@ -18471,81 +18660,102 @@ function MediaStripToolbar({
18471
18660
  }) {
18472
18661
  const items = mediaStripItems(entry.tree);
18473
18662
  const itemCount = items.length;
18474
- const currentAlt = items[index]?.alt ?? "";
18475
- const currentHref = items[index]?.href ?? "";
18476
- const currentTarget = items[index]?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
18477
18663
  const slots = findMediaStripSlots(entry.tree);
18478
18664
  const variant = slots?.variant === "reel" ? "reel" : "logos";
18479
18665
  const align = slots?.align === "left" || slots?.align === "right" ? slots.align : "center";
18480
18666
  const hover = slots?.hover === "none" || slots?.hover === "lift" || slots?.hover === "zoom" || slots?.hover === "fade" ? slots.hover : "lift";
18481
18667
  const treatment = slots?.treatment === "color" || slots?.treatment === "grayscale" ? slots.treatment : variant === "logos" ? "grayscale" : "color";
18482
18668
  const sizeValue = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
18483
- const isMobile = window.innerWidth <= 480;
18484
- const rowStyle = isMobile ? {
18485
- display: "flex",
18486
- gap: 6,
18487
- flexWrap: "nowrap",
18488
- justifyContent: "center",
18489
- overflowX: "auto",
18490
- WebkitOverflowScrolling: "touch",
18491
- width: "100%",
18492
- scrollbarWidth: "none"
18493
- } : { display: "flex", gap: 6, flexWrap: "nowrap", justifyContent: "center" };
18494
- 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(
18495
18672
  "div",
18496
18673
  {
18497
18674
  "data-ohw-bridge": "",
18498
18675
  "data-ohw-toolbar": "",
18499
18676
  onMouseDown: (e) => e.stopPropagation(),
18500
- style: {
18501
- display: "flex",
18502
- flexDirection: "column",
18503
- gap: 6,
18504
- alignItems: "center",
18505
- background: OHW_BLUE,
18506
- padding: 8,
18507
- borderRadius: 12,
18508
- boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
18509
- pointerEvents: pending ? "none" : "auto",
18510
- opacity: pending ? 0.6 : 1,
18511
- maxWidth: isMobile ? "calc(100vw - 24px)" : "none"
18512
- },
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 },
18513
18679
  children: [
18514
- /* @__PURE__ */ jsxs10("div", { style: rowStyle, children: [
18515
- VARIANTS.map((v) => /* @__PURE__ */ jsx19(Pill, { label: v.label, active: variant === v.value, onClick: () => onSetVariant(v.value) }, v.value)),
18516
- ALIGNS.map((a) => /* @__PURE__ */ jsx19(Pill, { label: a.label, active: align === a.value, onClick: () => onSetAlign(a.value) }, a.value))
18517
- ] }),
18518
- /* @__PURE__ */ jsxs10("div", { style: rowStyle, children: [
18519
- HOVERS.map((h) => /* @__PURE__ */ jsx19(Pill, { label: h.label, active: hover === h.value, onClick: () => onSetHover(h.value) }, h.value)),
18520
- TREATMENTS.map((t) => /* @__PURE__ */ jsx19(Pill, { label: t.label, active: treatment === t.value, onClick: () => onSetTreatment(t.value) }, t.value))
18521
- ] }),
18522
- /* @__PURE__ */ jsxs10("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
18523
- /* @__PURE__ */ jsx19(Pill, { label: "\u2212", onClick: () => onResize(-8) }),
18524
- /* @__PURE__ */ jsxs10("span", { style: { color: "#ffffff", fontSize: 12, minWidth: 34, textAlign: "center" }, children: [
18525
- sizeValue,
18526
- "px"
18527
- ] }),
18528
- /* @__PURE__ */ jsx19(Pill, { label: "+", onClick: () => onResize(8) })
18529
- ] }),
18530
- /* @__PURE__ */ jsx19("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ jsx19(AltInput, { value: currentAlt, onCommit: onSetAlt }) }),
18531
- /* @__PURE__ */ jsx19("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ jsx19(LinkInput, { value: currentHref, onCommit: (href) => onSetLink(href, currentTarget) }) }),
18532
- currentHref && /* @__PURE__ */ jsxs10("div", { style: rowStyle, children: [
18533
- /* @__PURE__ */ jsx19(Pill, { label: "New tab", active: currentTarget === "new-tab", onClick: () => onSetLink(currentHref, "new-tab") }),
18534
- /* @__PURE__ */ jsx19(Pill, { label: "Same tab", active: currentTarget === "same-tab", onClick: () => onSetLink(currentHref, "same-tab") })
18535
- ] }),
18536
- /* @__PURE__ */ jsxs10("div", { style: rowStyle, children: [
18537
- /* @__PURE__ */ jsx19(Pill, { label: "Change Image", onClick: onChangeImage }),
18538
- /* @__PURE__ */ jsx19(Pill, { label: "+ Item", onClick: onAddItem }),
18539
- /* @__PURE__ */ jsx19(Pill, { label: "\u2212 Item", onClick: onRemoveItem, disabled: itemCount <= 1 })
18540
- ] }),
18541
- /* @__PURE__ */ jsxs10("div", { style: rowStyle, children: [
18542
- /* @__PURE__ */ jsx19(Pill, { label: "\u2190 Move", onClick: onMoveLeft, disabled: index <= 0 }),
18543
- /* @__PURE__ */ jsx19(Pill, { label: "\u2192 Move", onClick: onMoveRight, disabled: index >= itemCount - 1 })
18544
- ] }),
18545
- 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 })
18546
18756
  ]
18547
18757
  }
18548
- );
18758
+ ) });
18549
18759
  }
18550
18760
  function findMediaStripSlots(tree) {
18551
18761
  const stack = tree.rows.flatMap((row) => row.blocks);
@@ -18564,31 +18774,23 @@ function AiMediaStripOverlay({
18564
18774
  }) {
18565
18775
  const [hover, setHover] = useState8(null);
18566
18776
  const [active, setActive] = useState8(null);
18777
+ const [itemEditOn, setItemEditOn] = useState8(false);
18778
+ const [linkOpen, setLinkOpen] = useState8(false);
18567
18779
  const [pending, setPending] = useState8(false);
18568
18780
  const [error, setError] = useState8(null);
18569
18781
  const activeRef = useRef5(null);
18570
18782
  activeRef.current = active;
18783
+ const linkOpenRef = useRef5(false);
18784
+ linkOpenRef.current = linkOpen;
18571
18785
  useEffect6(() => {
18572
18786
  const onMove = (e) => {
18787
+ const ref = itemRefFromTarget(e.target);
18573
18788
  const t = e.target;
18574
- if (!(t instanceof HTMLElement)) return;
18575
- 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]")) {
18576
- return;
18577
- }
18578
- const itemEl = t.closest("[data-ohw-media-strip-item]");
18579
- if (!itemEl) {
18580
- setHover(null);
18581
- return;
18582
- }
18583
- const sectionEl = itemEl.closest("[data-ohw-section]");
18584
- const unit = sectionEl ? movableUnit(sectionEl) : null;
18585
- const sectionId = unit ? instanceIdOf(unit) || null : null;
18586
- const index = Number(itemEl.dataset.ohwMediaStripIndex);
18587
- if (!sectionId || Number.isNaN(index)) {
18588
- setHover(null);
18589
- return;
18590
- }
18591
- 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
+ });
18592
18794
  };
18593
18795
  const onLeave = () => setHover(null);
18594
18796
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -18600,23 +18802,25 @@ function AiMediaStripOverlay({
18600
18802
  }, []);
18601
18803
  useEffect6(() => {
18602
18804
  const onClick = (e) => {
18603
- const current = activeRef.current;
18604
- if (!current) return;
18605
18805
  const t = e.target;
18606
- if (!(t instanceof HTMLElement)) return;
18607
- if (t.closest("[data-ohw-item-interaction]")) return;
18608
- const itemEl = findMediaStripItemElement(current.sectionId, current.index);
18609
- if (itemEl && itemEl.contains(t)) return;
18610
- 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);
18611
18812
  setError(null);
18813
+ setActive(ref);
18612
18814
  };
18613
18815
  document.addEventListener("click", onClick, true);
18614
18816
  return () => document.removeEventListener("click", onClick, true);
18615
- }, []);
18817
+ }, [clearInteractionChromeRef]);
18616
18818
  useEffect6(() => {
18617
18819
  const onBlur = () => {
18618
18820
  if (!activeRef.current) return;
18619
18821
  setActive(null);
18822
+ setItemEditOn(false);
18823
+ setLinkOpen(false);
18620
18824
  setError(null);
18621
18825
  };
18622
18826
  window.addEventListener("blur", onBlur);
@@ -18626,7 +18830,10 @@ function AiMediaStripOverlay({
18626
18830
  const onKeyDown = (e) => {
18627
18831
  if (e.key !== "Escape") return;
18628
18832
  if (!activeRef.current) return;
18833
+ if (linkOpenRef.current) return;
18834
+ if (document.querySelector('[data-ohw-more-menu][data-state="open"]')) return;
18629
18835
  setActive(null);
18836
+ setItemEditOn(false);
18630
18837
  setError(null);
18631
18838
  };
18632
18839
  document.addEventListener("keydown", onKeyDown);
@@ -18660,137 +18867,133 @@ function AiMediaStripOverlay({
18660
18867
  tree: nextTree,
18661
18868
  label: entry.label,
18662
18869
  media: extraMedia ? { ...entry.media, ...extraMedia } : entry.media,
18663
- ...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
18664
18874
  });
18665
18875
  return true;
18666
18876
  },
18667
18877
  [findEntry, postToParent2, applyTreeLocalRef]
18668
18878
  );
18669
18879
  const activeEntry = active ? findEntry(active.sectionId) : null;
18670
- const hoverHandleRect = useLiveItemRect(!active && hover ? hover : null);
18671
18880
  const activeItemRect = useLiveItemRect(active);
18672
- const TOOLBAR_APPROX_H = 170;
18673
- const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
18674
- return /* @__PURE__ */ jsxs10(Fragment6, { children: [
18675
- hoverHandleRect && hover && /* @__PURE__ */ jsx19(
18676
- "button",
18677
- {
18678
- type: "button",
18679
- "data-ohw-toolbar": "",
18680
- onClick: () => {
18681
- clearInteractionChromeRef.current();
18682
- setActive(hover);
18683
- },
18684
- "aria-label": "Configure media item",
18685
- style: {
18686
- position: "fixed",
18687
- top: hoverHandleRect.top + 8,
18688
- left: hoverHandleRect.left + hoverHandleRect.width - 36,
18689
- width: 28,
18690
- height: 28,
18691
- borderRadius: 8,
18692
- border: `1.5px dashed ${PRIMARY3}`,
18693
- background: "#ffffff",
18694
- color: PRIMARY3,
18695
- display: "flex",
18696
- alignItems: "center",
18697
- justifyContent: "center",
18698
- cursor: "pointer",
18699
- zIndex: 2147483643,
18700
- fontSize: 14,
18701
- fontWeight: 700
18702
- },
18703
- children: /* @__PURE__ */ jsx19(Star, { size: 14, style: { pointerEvents: "none" } })
18704
- }
18705
- ),
18706
- activeItemRect && active && activeEntry && /* @__PURE__ */ jsx19(
18707
- ItemInteractionLayer,
18708
- {
18709
- rect: activeItemRect,
18710
- state: toolbarState,
18711
- toolbar: /* @__PURE__ */ jsx19(
18712
- MediaStripToolbar,
18713
- {
18714
- entry: activeEntry,
18715
- index: active.index,
18716
- pending,
18717
- error,
18718
- onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
18719
- onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
18720
- onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
18721
- onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
18722
- onResize: (delta) => {
18723
- const slots = findMediaStripSlots(activeEntry.tree);
18724
- const variant = slots?.variant === "reel" ? "reel" : "logos";
18725
- const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
18726
- void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
18727
- },
18728
- onSetAlt: (alt) => {
18729
- void runMutation(active.sectionId, (tree) => setMediaStripItemAlt(tree, active.index, alt));
18730
- },
18731
- 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" };
18732
18915
  void runMutation(
18733
18916
  active.sectionId,
18734
- (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
18735
- );
18736
- },
18737
- onAddItem: () => {
18738
- void requestAssetPick(postToParent2).then((picked) => {
18739
- if (!picked) return;
18740
- const item = { assetId: picked.assetId, alt: "Logo" };
18741
- void runMutation(
18742
- active.sectionId,
18743
- (tree) => addMediaStripItem(tree, item, active.index),
18744
- { [picked.assetId]: picked.url }
18745
- ).then((applied) => {
18746
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
18747
- });
18748
- });
18749
- },
18750
- onChangeImage: () => {
18751
- void requestAssetPick(postToParent2).then((picked) => {
18752
- if (!picked) return;
18753
- void runMutation(
18754
- active.sectionId,
18755
- (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
18756
- { [picked.assetId]: picked.url }
18757
- );
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 });
18758
18921
  });
18759
- },
18760
- onRemoveItem: () => {
18761
- void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
18762
- (applied) => {
18763
- if (applied) setActive(null);
18764
- }
18765
- );
18766
- },
18767
- onMoveLeft: () => {
18768
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
18769
- (applied) => {
18770
- if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
18771
- }
18772
- );
18773
- },
18774
- onMoveRight: () => {
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
- }
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 }
18779
18931
  );
18780
- }
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
+ );
18781
18954
  }
18782
- )
18783
- }
18784
- )
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
+ ] })
18785
18988
  ] });
18786
18989
  }
18787
18990
 
18788
18991
  // src/ui/ai-section/AiPricingCardOverlay.tsx
18789
- import { useCallback as useCallback4, useEffect as useEffect7, useLayoutEffect as useLayoutEffect4, useRef as useRef6, useState as useState9 } from "react";
18790
- import { Check as Check2, ChevronDown as ChevronDown3, Minus, Plus as Plus2, Star as Star2 } from "lucide-react";
18791
- 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";
18792
18995
  var PRIMARY4 = "var(--ohw-primary, #0885FE)";
18793
- 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]";
18794
18997
  function findPricingCardElement(sectionId, index) {
18795
18998
  const sectionEl = findByInstanceId(sectionId);
18796
18999
  if (!sectionEl) return null;
@@ -18802,7 +19005,7 @@ function findPricingCardElement(sectionId, index) {
18802
19005
  }
18803
19006
  function cardRefFromTarget(target) {
18804
19007
  if (!(target instanceof Element)) return null;
18805
- if (target.closest(CHROME_SELECTOR)) return null;
19008
+ if (target.closest(CHROME_SELECTOR2)) return null;
18806
19009
  const cardEl = target.closest("[data-ohw-pricing-card]");
18807
19010
  if (!cardEl) return null;
18808
19011
  const sectionEl = cardEl.closest("[data-ohw-section]");
@@ -18812,21 +19015,21 @@ function cardRefFromTarget(target) {
18812
19015
  if (!sectionId || Number.isNaN(index)) return null;
18813
19016
  return { sectionId, index };
18814
19017
  }
18815
- function sameRef(a, b) {
19018
+ function sameRef2(a, b) {
18816
19019
  return a?.sectionId === b?.sectionId && a?.index === b?.index;
18817
19020
  }
18818
- function rectOf(el) {
19021
+ function rectOf2(el) {
18819
19022
  if (!el) return null;
18820
19023
  const r2 = el.getBoundingClientRect();
18821
19024
  if (r2.width <= 0 || r2.height <= 0) return null;
18822
19025
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
18823
19026
  }
18824
19027
  function readRect3(ref) {
18825
- return rectOf(ref && findPricingCardElement(ref.sectionId, ref.index));
19028
+ return rectOf2(ref && findPricingCardElement(ref.sectionId, ref.index));
18826
19029
  }
18827
19030
  function readGridRect(ref) {
18828
19031
  const card = ref && findPricingCardElement(ref.sectionId, ref.index);
18829
- return rectOf(card?.closest("[data-ohw-pricing-collection]") ?? card);
19032
+ return rectOf2(card?.closest("[data-ohw-pricing-collection]") ?? card);
18830
19033
  }
18831
19034
  function useLiveCardRect(ref, grid = false) {
18832
19035
  const [rect, setRect] = useState9(null);
@@ -18856,19 +19059,19 @@ function useLiveCardRect(ref, grid = false) {
18856
19059
  }
18857
19060
  var TOOLBAR_GRID_GAP = 52;
18858
19061
  var TOOLBAR_GRID_GAP_MOBILE = 42;
18859
- var TOOLBAR_EDGE_MARGIN2 = 4;
19062
+ var TOOLBAR_EDGE_MARGIN3 = 4;
18860
19063
  function GridToolbarSlot({ gridRect, children }) {
18861
19064
  const slotRef = useRef6(null);
18862
19065
  const [shift, setShift] = useState9(0);
18863
19066
  const centerX = gridRect.left + gridRect.width / 2;
18864
- useLayoutEffect4(() => {
19067
+ useLayoutEffect5(() => {
18865
19068
  const slot = slotRef.current;
18866
19069
  if (!slot) return;
18867
19070
  const clamp = () => {
18868
19071
  const w = slot.offsetWidth;
18869
19072
  if (w <= 0) return;
18870
19073
  const half = w / 2;
18871
- 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));
18872
19075
  setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
18873
19076
  };
18874
19077
  clamp();
@@ -18877,7 +19080,7 @@ function GridToolbarSlot({ gridRect, children }) {
18877
19080
  return () => ro.disconnect();
18878
19081
  }, [centerX, children]);
18879
19082
  const mobile = window.innerWidth <= 480;
18880
- return /* @__PURE__ */ jsx20(
19083
+ return /* @__PURE__ */ jsx21(
18881
19084
  "div",
18882
19085
  {
18883
19086
  ref: slotRef,
@@ -18912,7 +19115,7 @@ function requestValidateTree2(postToParent2, tree) {
18912
19115
  });
18913
19116
  }
18914
19117
  function StyleSwatch({ style }) {
18915
- return /* @__PURE__ */ jsx20(
19118
+ return /* @__PURE__ */ jsx21(
18916
19119
  "span",
18917
19120
  {
18918
19121
  "aria-hidden": true,
@@ -18926,35 +19129,35 @@ function StyleSwatch({ style }) {
18926
19129
  );
18927
19130
  }
18928
19131
  function ButtonGlyph() {
18929
- return /* @__PURE__ */ jsxs11("svg", { width: "18", height: "12", viewBox: "0 0 18 12", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
18930
- /* @__PURE__ */ jsx20("rect", { x: "0.75", y: "0.75", width: "16.5", height: "10.5", rx: "3", stroke: "currentColor", strokeWidth: "1.5" }),
18931
- /* @__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" })
18932
19135
  ] });
18933
19136
  }
18934
19137
  function FeatureGlyph({ mode }) {
18935
- return /* @__PURE__ */ jsxs11("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
18936
- /* @__PURE__ */ jsx20("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
18937
- /* @__PURE__ */ jsx20("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
18938
- /* @__PURE__ */ jsx20("circle", { cx: "13.5", cy: "13.5", r: "4.25", fill: "var(--ohw-background, #ffffff)", stroke: "var(--ohw-background, #ffffff)", strokeWidth: "1" }),
18939
- /* @__PURE__ */ jsx20("circle", { cx: "13.5", cy: "13.5", r: "3.75", fill: "var(--ohw-background, #ffffff)", stroke: PRIMARY4, strokeWidth: "1" }),
18940
- /* @__PURE__ */ jsx20("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
18941
- 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" })
18942
19145
  ] });
18943
19146
  }
18944
- function ToolbarAction({
19147
+ function ToolbarAction2({
18945
19148
  label,
18946
19149
  onClick,
18947
19150
  disabled = false,
18948
19151
  children
18949
19152
  }) {
18950
- const button = /* @__PURE__ */ jsx20(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
18951
- return /* @__PURE__ */ jsxs11(Tooltip, { children: [
18952
- /* @__PURE__ */ jsx20(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ jsx20("span", { className: "inline-flex", children: button }) : button }),
18953
- /* @__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 })
18954
19157
  ] });
18955
19158
  }
18956
- function ToolbarSeparator() {
18957
- 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" });
18958
19161
  }
18959
19162
  function CardStyleMenu({
18960
19163
  value,
@@ -18962,7 +19165,7 @@ function CardStyleMenu({
18962
19165
  disabled = false
18963
19166
  }) {
18964
19167
  const current = PRICING_CARD_STYLES.find((s) => s.value === value) ?? PRICING_CARD_STYLES[0];
18965
- const trigger = /* @__PURE__ */ jsxs11(
19168
+ const trigger = /* @__PURE__ */ jsxs12(
18966
19169
  "button",
18967
19170
  {
18968
19171
  type: "button",
@@ -18973,28 +19176,28 @@ function CardStyleMenu({
18973
19176
  disabled && "cursor-not-allowed opacity-40 hover:bg-transparent"
18974
19177
  ),
18975
19178
  children: [
18976
- /* @__PURE__ */ jsx20(StyleSwatch, { style: current.value }),
18977
- /* @__PURE__ */ jsx20("span", { className: "flex-1 text-left", children: current.label }),
18978
- /* @__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" } })
18979
19182
  ]
18980
19183
  }
18981
19184
  );
18982
19185
  if (disabled) {
18983
- return /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
18984
- /* @__PURE__ */ jsx20("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
18985
- /* @__PURE__ */ jsxs11(Tooltip, { children: [
18986
- /* @__PURE__ */ jsx20(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx20("span", { className: "inline-flex", children: trigger }) }),
18987
- /* @__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" })
18988
19191
  ] })
18989
19192
  ] });
18990
19193
  }
18991
- return /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
18992
- /* @__PURE__ */ jsx20("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
18993
- /* @__PURE__ */ jsxs11(DropdownMenu, { modal: false, children: [
18994
- /* @__PURE__ */ jsx20(DropdownMenuTrigger, { asChild: true, children: trigger }),
18995
- /* @__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) => {
18996
19199
  const selected = style.value === value;
18997
- return /* @__PURE__ */ jsxs11(
19200
+ return /* @__PURE__ */ jsxs12(
18998
19201
  DropdownMenuItem,
18999
19202
  {
19000
19203
  role: "menuitemradio",
@@ -19002,9 +19205,9 @@ function CardStyleMenu({
19002
19205
  onSelect: () => onChange(style.value),
19003
19206
  className: cn("grid grid-cols-[18px_1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
19004
19207
  children: [
19005
- /* @__PURE__ */ jsx20(StyleSwatch, { style: style.value }),
19006
- /* @__PURE__ */ jsx20("span", { children: style.label }),
19007
- 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" })
19008
19211
  ]
19009
19212
  },
19010
19213
  style.value
@@ -19034,7 +19237,7 @@ function PricingCardToolbar({
19034
19237
  const style = pricingCardStyle(entry.tree, index);
19035
19238
  const featuredTag = pricingCardFeaturedTag(entry.tree, index);
19036
19239
  const mobile = window.innerWidth <= 480;
19037
- return /* @__PURE__ */ jsx20(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsxs11(
19240
+ return /* @__PURE__ */ jsx21(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsxs12(
19038
19241
  "div",
19039
19242
  {
19040
19243
  "data-ohw-bridge": "",
@@ -19046,7 +19249,7 @@ function PricingCardToolbar({
19046
19249
  ),
19047
19250
  style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
19048
19251
  children: [
19049
- /* @__PURE__ */ jsxs11(
19252
+ /* @__PURE__ */ jsxs12(
19050
19253
  "div",
19051
19254
  {
19052
19255
  className: cn(
@@ -19055,8 +19258,8 @@ function PricingCardToolbar({
19055
19258
  mobile ? "flex-wrap justify-center" : "flex-nowrap"
19056
19259
  ),
19057
19260
  children: [
19058
- /* @__PURE__ */ jsxs11(Tooltip, { children: [
19059
- /* @__PURE__ */ jsx20(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx20(
19261
+ /* @__PURE__ */ jsxs12(Tooltip, { children: [
19262
+ /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx21(
19060
19263
  CustomToolbarButton,
19061
19264
  {
19062
19265
  type: "button",
@@ -19064,7 +19267,7 @@ function PricingCardToolbar({
19064
19267
  "aria-pressed": featuredTag,
19065
19268
  onClick: () => onSetFeaturedTag(!featuredTag),
19066
19269
  className: "group size-8",
19067
- children: /* @__PURE__ */ jsx20(
19270
+ children: /* @__PURE__ */ jsx21(
19068
19271
  Star2,
19069
19272
  {
19070
19273
  size: 16,
@@ -19079,25 +19282,25 @@ function PricingCardToolbar({
19079
19282
  )
19080
19283
  }
19081
19284
  ) }),
19082
- /* @__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" })
19083
19286
  ] }),
19084
- /* @__PURE__ */ jsx20(ToolbarSeparator, {}),
19085
- /* @__PURE__ */ jsx20(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
19086
- /* @__PURE__ */ jsx20(ToolbarSeparator, {}),
19087
- /* @__PURE__ */ jsx20(ToolbarAction, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ jsx20(ButtonGlyph, {}) }),
19088
- /* @__PURE__ */ jsx20(ToolbarAction, { label: "Remove button", onClick: onRemoveButton, disabled: buttonCount === 0, children: /* @__PURE__ */ jsxs11("span", { className: "relative inline-flex", children: [
19089
- /* @__PURE__ */ jsx20(ButtonGlyph, {}),
19090
- /* @__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" })
19091
19294
  ] }) }),
19092
- /* @__PURE__ */ jsx20(ToolbarAction, { label: "Add feature", onClick: onAddFeature, disabled: featureCount >= 10, children: /* @__PURE__ */ jsx20(FeatureGlyph, { mode: "add" }) }),
19093
- /* @__PURE__ */ jsx20(ToolbarAction, { label: "Remove feature", onClick: onRemoveFeature, disabled: featureCount <= 1, children: /* @__PURE__ */ jsx20(FeatureGlyph, { mode: "remove" }) }),
19094
- /* @__PURE__ */ jsx20(ToolbarSeparator, {}),
19095
- /* @__PURE__ */ jsx20(ToolbarAction, { label: "Add tier", onClick: onAddTier, disabled: collectionCount >= 4, children: /* @__PURE__ */ jsx20(Plus2, { size: 16, style: { pointerEvents: "none" } }) }),
19096
- /* @__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" } }) })
19097
19300
  ]
19098
19301
  }
19099
19302
  ),
19100
- 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 })
19101
19304
  ]
19102
19305
  }
19103
19306
  ) });
@@ -19131,7 +19334,7 @@ function AiPricingCardOverlay({
19131
19334
  const overEditable = Boolean(t?.closest?.("[data-ohw-editable], [data-ohw-role], a, button"));
19132
19335
  setHover((prev) => {
19133
19336
  const next = ref && !overEditable ? ref : null;
19134
- return sameRef(prev, next) ? prev : next;
19337
+ return sameRef2(prev, next) ? prev : next;
19135
19338
  });
19136
19339
  };
19137
19340
  const onLeave = () => setHover(null);
@@ -19146,10 +19349,10 @@ function AiPricingCardOverlay({
19146
19349
  const onClick = (e) => {
19147
19350
  const t = e.target;
19148
19351
  if (!(t instanceof Element)) return;
19149
- if (t.closest(CHROME_SELECTOR)) return;
19352
+ if (t.closest(CHROME_SELECTOR2)) return;
19150
19353
  const ref = cardRefFromTarget(t);
19151
- setActive((prev) => sameRef(prev, ref) ? prev : ref);
19152
- 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);
19153
19356
  };
19154
19357
  document.addEventListener("click", onClick, true);
19155
19358
  return () => document.removeEventListener("click", onClick, true);
@@ -19214,12 +19417,12 @@ function AiPricingCardOverlay({
19214
19417
  const activeEntry = active ? findEntry(active.sectionId) : null;
19215
19418
  const activeCardRect = useLiveCardRect(active);
19216
19419
  const activeGridRect = useLiveCardRect(active, true);
19217
- const hoverRect = useLiveCardRect(hover && !sameRef(hover, active) ? hover : null);
19218
- return /* @__PURE__ */ jsxs11(Fragment7, { children: [
19219
- hoverRect && /* @__PURE__ */ jsx20(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19220
- activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ jsxs11(Fragment7, { children: [
19221
- /* @__PURE__ */ jsx20(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
19222
- /* @__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(
19223
19426
  PricingCardToolbar,
19224
19427
  {
19225
19428
  entry: activeEntry,
@@ -19247,7 +19450,7 @@ function AiPricingCardOverlay({
19247
19450
  onRemoveFeature: () => runMutation(active.sectionId, (tree) => removePricingCardFeature(tree, active.index))
19248
19451
  }
19249
19452
  ) }),
19250
- /* @__PURE__ */ jsx20(
19453
+ /* @__PURE__ */ jsx21(
19251
19454
  "div",
19252
19455
  {
19253
19456
  "data-ohw-bridge": "",
@@ -19340,10 +19543,10 @@ function extractYouTubeId(input) {
19340
19543
  }
19341
19544
 
19342
19545
  // src/ui/ai-section/AiVideoOverlay.tsx
19343
- 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";
19344
19547
  var PRIMARY5 = "#0885FE";
19345
- var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY5})`;
19346
- 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)`;
19347
19550
  function findVideoBlockElement(sectionId, path) {
19348
19551
  const sectionEl = findByInstanceId(sectionId);
19349
19552
  if (!sectionEl) return null;
@@ -19403,12 +19606,12 @@ function requestValidateTree3(postToParent2, tree) {
19403
19606
  postToParent2({ type: "ow:ai-validate-tree-request", id, tree });
19404
19607
  });
19405
19608
  }
19406
- function Pill2({
19609
+ function Pill({
19407
19610
  label,
19408
19611
  onClick,
19409
19612
  disabled
19410
19613
  }) {
19411
- return /* @__PURE__ */ jsx21(
19614
+ return /* @__PURE__ */ jsx22(
19412
19615
  "button",
19413
19616
  {
19414
19617
  type: "button",
@@ -19424,7 +19627,7 @@ function Pill2({
19424
19627
  border: "none",
19425
19628
  cursor: disabled ? "default" : "pointer",
19426
19629
  background: "#ffffff",
19427
- color: OHW_BLUE_DARK2,
19630
+ color: OHW_BLUE_DARK,
19428
19631
  opacity: disabled ? 0.4 : 1,
19429
19632
  fontSize: 12,
19430
19633
  lineHeight: "16px",
@@ -19467,8 +19670,8 @@ function VideoUrlInput({ value, onCommit }) {
19467
19670
  onCommit(id);
19468
19671
  }
19469
19672
  };
19470
- return /* @__PURE__ */ jsxs12("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
19471
- /* @__PURE__ */ jsxs12(
19673
+ return /* @__PURE__ */ jsxs13("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
19674
+ /* @__PURE__ */ jsxs13(
19472
19675
  "div",
19473
19676
  {
19474
19677
  style: {
@@ -19484,12 +19687,12 @@ function VideoUrlInput({ value, onCommit }) {
19484
19687
  boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
19485
19688
  },
19486
19689
  children: [
19487
- /* @__PURE__ */ jsx21(
19690
+ /* @__PURE__ */ jsx22(
19488
19691
  "span",
19489
19692
  {
19490
19693
  style: {
19491
19694
  flex: "0 0 auto",
19492
- color: OHW_BLUE_DARK2,
19695
+ color: OHW_BLUE_DARK,
19493
19696
  fontSize: 10,
19494
19697
  lineHeight: "16px",
19495
19698
  fontWeight: 700,
@@ -19499,7 +19702,7 @@ function VideoUrlInput({ value, onCommit }) {
19499
19702
  children: "Video"
19500
19703
  }
19501
19704
  ),
19502
- /* @__PURE__ */ jsx21(
19705
+ /* @__PURE__ */ jsx22(
19503
19706
  "input",
19504
19707
  {
19505
19708
  type: "text",
@@ -19536,7 +19739,7 @@ function VideoUrlInput({ value, onCommit }) {
19536
19739
  ]
19537
19740
  }
19538
19741
  ),
19539
- 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." })
19540
19743
  ] });
19541
19744
  }
19542
19745
  function VideoToolbar({
@@ -19546,7 +19749,7 @@ function VideoToolbar({
19546
19749
  onSetVideoId,
19547
19750
  onRemove
19548
19751
  }) {
19549
- return /* @__PURE__ */ jsxs12(
19752
+ return /* @__PURE__ */ jsxs13(
19550
19753
  "div",
19551
19754
  {
19552
19755
  "data-ohw-bridge": "",
@@ -19558,7 +19761,7 @@ function VideoToolbar({
19558
19761
  gap: 6,
19559
19762
  alignItems: "center",
19560
19763
  width: 280,
19561
- background: OHW_BLUE2,
19764
+ background: OHW_BLUE,
19562
19765
  padding: 8,
19563
19766
  borderRadius: 12,
19564
19767
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -19566,9 +19769,9 @@ function VideoToolbar({
19566
19769
  opacity: pending ? 0.6 : 1
19567
19770
  },
19568
19771
  children: [
19569
- /* @__PURE__ */ jsx21(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
19570
- /* @__PURE__ */ jsx21(Pill2, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
19571
- 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 })
19572
19775
  ]
19573
19776
  }
19574
19777
  );
@@ -19688,8 +19891,8 @@ function AiVideoOverlay({
19688
19891
  const activeBlockRect = useLiveBlockRect(active);
19689
19892
  const TOOLBAR_APPROX_H = 90;
19690
19893
  const toolbarState = activeBlockRect && activeBlockRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
19691
- return /* @__PURE__ */ jsxs12(Fragment8, { children: [
19692
- hoverHandleRect && hover && /* @__PURE__ */ jsx21(
19894
+ return /* @__PURE__ */ jsxs13(Fragment8, { children: [
19895
+ hoverHandleRect && hover && /* @__PURE__ */ jsx22(
19693
19896
  "button",
19694
19897
  {
19695
19898
  type: "button",
@@ -19717,15 +19920,15 @@ function AiVideoOverlay({
19717
19920
  fontSize: 14,
19718
19921
  fontWeight: 700
19719
19922
  },
19720
- children: /* @__PURE__ */ jsx21(Star3, { size: 14, style: { pointerEvents: "none" } })
19923
+ children: /* @__PURE__ */ jsx22(Star3, { size: 14, style: { pointerEvents: "none" } })
19721
19924
  }
19722
19925
  ),
19723
- activeBlockRect && active && activeEntry && /* @__PURE__ */ jsx21(
19926
+ activeBlockRect && active && activeEntry && /* @__PURE__ */ jsx22(
19724
19927
  ItemInteractionLayer,
19725
19928
  {
19726
19929
  rect: activeBlockRect,
19727
19930
  state: toolbarState,
19728
- toolbar: /* @__PURE__ */ jsx21(
19931
+ toolbar: /* @__PURE__ */ jsx22(
19729
19932
  VideoToolbar,
19730
19933
  {
19731
19934
  videoId: videoIdAt(activeEntry.tree, active.path),
@@ -19800,10 +20003,10 @@ function setCarouselAutoplay(tree, on) {
19800
20003
  }
19801
20004
 
19802
20005
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
19803
- 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";
19804
20007
  var PRIMARY6 = "#0885FE";
19805
- var OHW_BLUE3 = `var(--ohw-primary, ${PRIMARY6})`;
19806
- 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)`;
19807
20010
  function findCarouselItemElement(sectionId, index) {
19808
20011
  const sectionEl = findByInstanceId(sectionId);
19809
20012
  if (!sectionEl) return null;
@@ -19882,8 +20085,8 @@ function requestAssetPick2(postToParent2) {
19882
20085
  postToParent2({ type: "ow:asset-pick", id });
19883
20086
  });
19884
20087
  }
19885
- function Pill3({ label, onClick, disabled }) {
19886
- return /* @__PURE__ */ jsx22(
20088
+ function Pill2({ label, onClick, disabled }) {
20089
+ return /* @__PURE__ */ jsx23(
19887
20090
  "button",
19888
20091
  {
19889
20092
  type: "button",
@@ -19899,7 +20102,7 @@ function Pill3({ label, onClick, disabled }) {
19899
20102
  border: "none",
19900
20103
  cursor: disabled ? "default" : "pointer",
19901
20104
  background: "#ffffff",
19902
- color: OHW_BLUE_DARK3,
20105
+ color: OHW_BLUE_DARK2,
19903
20106
  opacity: disabled ? 0.4 : 1,
19904
20107
  fontSize: 12,
19905
20108
  lineHeight: "16px",
@@ -19917,7 +20120,7 @@ function TogglePill({
19917
20120
  onToggle,
19918
20121
  disabled
19919
20122
  }) {
19920
- return /* @__PURE__ */ jsxs13(
20123
+ return /* @__PURE__ */ jsxs14(
19921
20124
  "button",
19922
20125
  {
19923
20126
  type: "button",
@@ -19936,7 +20139,7 @@ function TogglePill({
19936
20139
  border: "none",
19937
20140
  cursor: disabled ? "default" : "pointer",
19938
20141
  background: "#ffffff",
19939
- color: OHW_BLUE_DARK3,
20142
+ color: OHW_BLUE_DARK2,
19940
20143
  opacity: disabled ? 0.4 : 1,
19941
20144
  fontSize: 12,
19942
20145
  lineHeight: "16px",
@@ -19946,7 +20149,7 @@ function TogglePill({
19946
20149
  },
19947
20150
  children: [
19948
20151
  label,
19949
- /* @__PURE__ */ jsx22(
20152
+ /* @__PURE__ */ jsx23(
19950
20153
  "span",
19951
20154
  {
19952
20155
  "aria-hidden": true,
@@ -19959,10 +20162,10 @@ function TogglePill({
19959
20162
  // Off is a grey track, not a washed-out blue: the pill's own white background already
19960
20163
  // carries the brand colour in the text, and a pale blue track next to it read as
19961
20164
  // "disabled control" rather than "switch that is off".
19962
- background: on ? OHW_BLUE_DARK3 : "#CBD5E1",
20165
+ background: on ? OHW_BLUE_DARK2 : "#CBD5E1",
19963
20166
  transition: "background 120ms ease"
19964
20167
  },
19965
- children: /* @__PURE__ */ jsx22(
20168
+ children: /* @__PURE__ */ jsx23(
19966
20169
  "span",
19967
20170
  {
19968
20171
  style: {
@@ -19991,7 +20194,7 @@ function CarouselSlideToolbar({
19991
20194
  onChangeImage,
19992
20195
  onToggleAutoplay
19993
20196
  }) {
19994
- return /* @__PURE__ */ jsxs13(
20197
+ return /* @__PURE__ */ jsxs14(
19995
20198
  "div",
19996
20199
  {
19997
20200
  "data-ohw-bridge": "",
@@ -20002,7 +20205,7 @@ function CarouselSlideToolbar({
20002
20205
  flexDirection: "column",
20003
20206
  gap: 6,
20004
20207
  alignItems: "center",
20005
- background: OHW_BLUE3,
20208
+ background: OHW_BLUE2,
20006
20209
  padding: 8,
20007
20210
  borderRadius: 12,
20008
20211
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -20014,11 +20217,11 @@ function CarouselSlideToolbar({
20014
20217
  pointerEvents: pending ? "none" : "auto"
20015
20218
  },
20016
20219
  children: [
20017
- /* @__PURE__ */ jsxs13("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20018
- /* @__PURE__ */ jsx22(Pill3, { label: "Change Image", onClick: onChangeImage }),
20019
- /* @__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 })
20020
20223
  ] }),
20021
- 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 })
20022
20225
  ]
20023
20226
  }
20024
20227
  );
@@ -20158,8 +20361,8 @@ function AiFadeCarouselOverlay({
20158
20361
  const activeItemRect = useLiveItemRect2(active);
20159
20362
  const TOOLBAR_APPROX_H = 90;
20160
20363
  const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
20161
- return /* @__PURE__ */ jsxs13(Fragment9, { children: [
20162
- hoverHandleRect && hover && /* @__PURE__ */ jsx22(
20364
+ return /* @__PURE__ */ jsxs14(Fragment9, { children: [
20365
+ hoverHandleRect && hover && /* @__PURE__ */ jsx23(
20163
20366
  "button",
20164
20367
  {
20165
20368
  type: "button",
@@ -20188,10 +20391,10 @@ function AiFadeCarouselOverlay({
20188
20391
  fontSize: 14,
20189
20392
  fontWeight: 700
20190
20393
  },
20191
- children: /* @__PURE__ */ jsx22(Star4, { size: 14, style: { pointerEvents: "none" } })
20394
+ children: /* @__PURE__ */ jsx23(Star4, { size: 14, style: { pointerEvents: "none" } })
20192
20395
  }
20193
20396
  ),
20194
- swapping && /* @__PURE__ */ jsx22(
20397
+ swapping && /* @__PURE__ */ jsx23(
20195
20398
  "div",
20196
20399
  {
20197
20400
  ref: skeletonRef,
@@ -20209,15 +20412,15 @@ function AiFadeCarouselOverlay({
20209
20412
  zIndex: 2147483646,
20210
20413
  pointerEvents: "none"
20211
20414
  },
20212
- children: /* @__PURE__ */ jsx22("style", { children: MEDIA_SKELETON_CSS })
20415
+ children: /* @__PURE__ */ jsx23("style", { children: MEDIA_SKELETON_CSS })
20213
20416
  }
20214
20417
  ),
20215
- activeItemRect && active && activeEntry && /* @__PURE__ */ jsx22(
20418
+ activeItemRect && active && activeEntry && /* @__PURE__ */ jsx23(
20216
20419
  ItemInteractionLayer,
20217
20420
  {
20218
20421
  rect: activeItemRect,
20219
20422
  state: toolbarState,
20220
- toolbar: /* @__PURE__ */ jsx22(
20423
+ toolbar: /* @__PURE__ */ jsx23(
20221
20424
  CarouselSlideToolbar,
20222
20425
  {
20223
20426
  pending,
@@ -20570,118 +20773,11 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
20570
20773
  // src/ui/link-modal/LinkPopover.tsx
20571
20774
  import { useEffect as useEffect13 } from "react";
20572
20775
 
20573
- // src/ui/dialog.tsx
20574
- import * as React9 from "react";
20575
- import { Dialog as DialogPrimitive } from "radix-ui";
20576
- import { X as X2 } from "lucide-react";
20577
- import { jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
20578
- function Dialog2({
20579
- ...props
20580
- }) {
20581
- return /* @__PURE__ */ jsx23(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
20582
- }
20583
- function DialogPortal({
20584
- ...props
20585
- }) {
20586
- return /* @__PURE__ */ jsx23(DialogPrimitive.Portal, { ...props });
20587
- }
20588
- function DialogOverlay({
20589
- className,
20590
- ...props
20591
- }) {
20592
- return /* @__PURE__ */ jsx23(
20593
- DialogPrimitive.Overlay,
20594
- {
20595
- "data-slot": "dialog-overlay",
20596
- "data-ohw-link-modal-root": "",
20597
- className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
20598
- ...props
20599
- }
20600
- );
20601
- }
20602
- var DialogContent = React9.forwardRef(
20603
- ({ className, children, showCloseButton = true, container, ...props }, ref) => {
20604
- const positionMode = container ? "absolute" : "fixed";
20605
- return /* @__PURE__ */ jsxs14(DialogPortal, { container: container ?? void 0, children: [
20606
- /* @__PURE__ */ jsx23(DialogOverlay, { className: cn(positionMode, "inset-0") }),
20607
- /* @__PURE__ */ jsxs14(
20608
- DialogPrimitive.Content,
20609
- {
20610
- ref,
20611
- "data-slot": "dialog-content",
20612
- className: cn(
20613
- positionMode,
20614
- "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",
20615
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
20616
- container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
20617
- className
20618
- ),
20619
- onMouseDown: (e) => e.stopPropagation(),
20620
- ...props,
20621
- children: [
20622
- children,
20623
- showCloseButton ? /* @__PURE__ */ jsx23(
20624
- DialogPrimitive.Close,
20625
- {
20626
- type: "button",
20627
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
20628
- "aria-label": "Close",
20629
- children: /* @__PURE__ */ jsx23(X2, { size: 16, "aria-hidden": true })
20630
- }
20631
- ) : null
20632
- ]
20633
- }
20634
- )
20635
- ] });
20636
- }
20637
- );
20638
- DialogContent.displayName = DialogPrimitive.Content.displayName;
20639
- function DialogHeader({
20640
- className,
20641
- ...props
20642
- }) {
20643
- return /* @__PURE__ */ jsx23("div", { className: cn("flex flex-col gap-1.5", className), ...props });
20644
- }
20645
- function DialogFooter({
20646
- className,
20647
- ...props
20648
- }) {
20649
- return /* @__PURE__ */ jsx23(
20650
- "div",
20651
- {
20652
- className: cn("flex items-center justify-end gap-2", className),
20653
- ...props
20654
- }
20655
- );
20656
- }
20657
- var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx23(
20658
- DialogPrimitive.Title,
20659
- {
20660
- ref,
20661
- className: cn(
20662
- "text-lg font-semibold leading-none tracking-tight text-card-foreground",
20663
- className
20664
- ),
20665
- ...props
20666
- }
20667
- ));
20668
- DialogTitle.displayName = DialogPrimitive.Title.displayName;
20669
- var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx23(
20670
- DialogPrimitive.Description,
20671
- {
20672
- ref,
20673
- className: cn("text-sm text-muted-foreground", className),
20674
- ...props
20675
- }
20676
- ));
20677
- DialogDescription.displayName = DialogPrimitive.Description.displayName;
20678
- var DialogClose = DialogPrimitive.Close;
20679
-
20680
20776
  // src/ui/link-modal/LinkEditorPanel.tsx
20681
20777
  import { Info, X as X4 } from "lucide-react";
20682
20778
 
20683
20779
  // src/ui/link-modal/DestinationBreadcrumb.tsx
20684
- import { ArrowRight as ArrowRight2, File, GalleryVertical } from "lucide-react";
20780
+ import { ArrowRight as ArrowRight3, File, GalleryVertical } from "lucide-react";
20685
20781
  import { jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
20686
20782
  function DestinationBreadcrumb({
20687
20783
  pageTitle,
@@ -20695,7 +20791,7 @@ function DestinationBreadcrumb({
20695
20791
  /* @__PURE__ */ jsx24("span", { className: "text-sm font-medium leading-none text-foreground!", children: pageTitle })
20696
20792
  ] }),
20697
20793
  /* @__PURE__ */ jsx24(
20698
- ArrowRight2,
20794
+ ArrowRight3,
20699
20795
  {
20700
20796
  size: 16,
20701
20797
  className: "shrink-0 text-muted-foreground",
@@ -20807,7 +20903,7 @@ function Label({ className, ...props }) {
20807
20903
  }
20808
20904
 
20809
20905
  // src/ui/link-modal/UrlOrPageInput.tsx
20810
- 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";
20811
20907
  import { jsx as jsx28, jsxs as jsxs17 } from "react/jsx-runtime";
20812
20908
  function FieldChevron({
20813
20909
  onClick
@@ -20820,7 +20916,7 @@ function FieldChevron({
20820
20916
  onClick,
20821
20917
  "aria-label": "Open page list",
20822
20918
  tabIndex: -1,
20823
- children: /* @__PURE__ */ jsx28(ChevronDown4, { size: 16 })
20919
+ children: /* @__PURE__ */ jsx28(ChevronDown5, { size: 16 })
20824
20920
  }
20825
20921
  );
20826
20922
  }
@@ -21050,7 +21146,7 @@ function LinkEditorPanel({ state, onClose }) {
21050
21146
  // src/ui/link-modal/SectionPickerOverlay.tsx
21051
21147
  import { useCallback as useCallback7, useEffect as useEffect11, useMemo as useMemo2, useRef as useRef10, useState as useState13 } from "react";
21052
21148
  import { createPortal as createPortal2 } from "react-dom";
21053
- import { ArrowLeft as ArrowLeft2, Check as Check3 } from "lucide-react";
21149
+ import { ArrowLeft as ArrowLeft3, Check as Check4 } from "lucide-react";
21054
21150
  import { usePathname, useRouter as useRouter2 } from "next/navigation";
21055
21151
 
21056
21152
  // src/lib/parent-scroll.ts
@@ -21327,7 +21423,7 @@ function SectionPickerOverlay({
21327
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",
21328
21424
  onClick: onBack,
21329
21425
  children: [
21330
- /* @__PURE__ */ jsx30(ArrowLeft2, { className: "size-4 shrink-0", "aria-hidden": true }),
21426
+ /* @__PURE__ */ jsx30(ArrowLeft3, { className: "size-4 shrink-0", "aria-hidden": true }),
21331
21427
  "Back"
21332
21428
  ]
21333
21429
  }
@@ -21396,7 +21492,7 @@ function SectionPickerOverlay({
21396
21492
  className: "absolute right-3 top-3 flex size-8 items-center justify-center rounded-full text-white",
21397
21493
  style: { backgroundColor: "var(--ohw-primary, #0885fe)" },
21398
21494
  "aria-hidden": true,
21399
- children: /* @__PURE__ */ jsx30(Check3, { className: "size-5" })
21495
+ children: /* @__PURE__ */ jsx30(Check4, { className: "size-5" })
21400
21496
  }
21401
21497
  ) : null
21402
21498
  ]
@@ -21780,6 +21876,7 @@ function shouldUseDevFixtures() {
21780
21876
  function getLogoElement(el) {
21781
21877
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
21782
21878
  if (marked) return marked;
21879
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
21783
21880
  const root = el.closest("nav, [data-ohw-nav-root], footer");
21784
21881
  if (!root) return null;
21785
21882
  const anchor = el.closest("a");
@@ -21849,7 +21946,7 @@ function addFooterColumnWithPersist({
21849
21946
  import {
21850
21947
  useCallback as useCallback9,
21851
21948
  useEffect as useEffect14,
21852
- useLayoutEffect as useLayoutEffect5,
21949
+ useLayoutEffect as useLayoutEffect6,
21853
21950
  useRef as useRef11,
21854
21951
  useState as useState15
21855
21952
  } from "react";
@@ -21946,7 +22043,7 @@ function FloatingPanel({
21946
22043
  return () => window.removeEventListener("message", onParentScroll);
21947
22044
  }, [open]);
21948
22045
  const positionRef = useRef11(position);
21949
- useLayoutEffect5(() => {
22046
+ useLayoutEffect6(() => {
21950
22047
  if (!open) {
21951
22048
  positionRef.current = position;
21952
22049
  return;
@@ -21956,7 +22053,7 @@ function FloatingPanel({
21956
22053
  }
21957
22054
  positionRef.current = position;
21958
22055
  }, [open, position, liveScroll, parentScroll]);
21959
- useLayoutEffect5(() => {
22056
+ useLayoutEffect6(() => {
21960
22057
  if (!open || !panelRef.current) return;
21961
22058
  const el = panelRef.current;
21962
22059
  const header = el.querySelector("[data-ohw-floating-panel-header]");
@@ -22165,7 +22262,7 @@ function SocialsDisplayPanel({ display, onChange, className }) {
22165
22262
  }
22166
22263
 
22167
22264
  // src/ui/button-style-panel.tsx
22168
- import { Check as Check4 } from "lucide-react";
22265
+ import { Check as Check5 } from "lucide-react";
22169
22266
  import { jsx as jsx34, jsxs as jsxs23 } from "react/jsx-runtime";
22170
22267
  var SIZE_LABEL = { s: "S", m: "M", l: "L" };
22171
22268
  function ButtonStylePanel({
@@ -22236,7 +22333,7 @@ function ButtonStylePanel({
22236
22333
  {
22237
22334
  className: "absolute right-0.5 top-0.5 flex size-5 items-center justify-center rounded-full bg-primary text-primary-foreground",
22238
22335
  "aria-hidden": true,
22239
- children: /* @__PURE__ */ jsx34(Check4, { className: "size-3", strokeWidth: 3 })
22336
+ children: /* @__PURE__ */ jsx34(Check5, { className: "size-3", strokeWidth: 3 })
22240
22337
  }
22241
22338
  ) : null
22242
22339
  ]
@@ -23261,7 +23358,7 @@ function useSectionDrag({
23261
23358
  }
23262
23359
 
23263
23360
  // src/ui/footer-container-chrome.tsx
23264
- import { Plus as Plus3 } from "lucide-react";
23361
+ import { Plus as Plus4 } from "lucide-react";
23265
23362
  import { jsx as jsx35, jsxs as jsxs24 } from "react/jsx-runtime";
23266
23363
  function FooterContainerChrome({
23267
23364
  rect,
@@ -23302,7 +23399,7 @@ function FooterContainerChrome({
23302
23399
  if (addDisabled) return;
23303
23400
  onAdd();
23304
23401
  },
23305
- 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 })
23306
23403
  }
23307
23404
  ) }),
23308
23405
  /* @__PURE__ */ jsx35(TooltipContent, { side: "bottom", sideOffset: 9, children: addDisabled ? "Maximum columns reached" : "Add item" })
@@ -23481,7 +23578,7 @@ function deleteSelectedNavFooterItem(deps) {
23481
23578
  }
23482
23579
 
23483
23580
  // src/ui/navbar-container-chrome.tsx
23484
- import { Plus as Plus4 } from "lucide-react";
23581
+ import { Plus as Plus5 } from "lucide-react";
23485
23582
  import { jsx as jsx36 } from "react/jsx-runtime";
23486
23583
  function NavbarContainerChrome({
23487
23584
  rect,
@@ -23517,7 +23614,7 @@ function NavbarContainerChrome({
23517
23614
  e.stopPropagation();
23518
23615
  onAdd();
23519
23616
  },
23520
- 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 })
23521
23618
  }
23522
23619
  )
23523
23620
  }
@@ -23589,7 +23686,7 @@ function Badge({ className, variant, ...props }) {
23589
23686
  }
23590
23687
 
23591
23688
  // src/OhhwellsBridge.tsx
23592
- 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";
23593
23690
  import { Fragment as Fragment12, jsx as jsx39, jsxs as jsxs25 } from "react/jsx-runtime";
23594
23691
  var PRIMARY7 = "#0885FE";
23595
23692
  var IMAGE_FADE_MS = 300;
@@ -24366,7 +24463,7 @@ function getNavigationSelectionParent(el) {
24366
24463
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
24367
24464
  return resolveFooterLinksContainerFrom(el);
24368
24465
  }
24369
- 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)) {
24370
24467
  return getNavigationRoot(el);
24371
24468
  }
24372
24469
  return null;
@@ -24588,7 +24685,6 @@ var ICONS = {
24588
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"/>',
24589
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"/>'
24590
24687
  };
24591
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
24592
24688
  var SELECTION_CHROME_GAP2 = 4;
24593
24689
  var TOOLBAR_STROKE_GAP2 = 4;
24594
24690
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -24686,7 +24782,7 @@ function applyVisibleViewport(el, parentScroll) {
24686
24782
  el.style.top = `${top}px`;
24687
24783
  el.style.height = `${height}px`;
24688
24784
  }
24689
- var TOOLBAR_EDGE_MARGIN3 = 4;
24785
+ var TOOLBAR_EDGE_MARGIN4 = 4;
24690
24786
  function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
24691
24787
  const isAbove = transform.includes("translateY(-100%)");
24692
24788
  let visualTop = isAbove ? top - approxH : top;
@@ -24713,8 +24809,8 @@ function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
24713
24809
  function clampToolbarLeft(centerX, toolbarWidth) {
24714
24810
  const half = toolbarWidth / 2;
24715
24811
  return Math.max(
24716
- TOOLBAR_EDGE_MARGIN3 + half,
24717
- Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half)
24812
+ TOOLBAR_EDGE_MARGIN4 + half,
24813
+ Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN4 - half)
24718
24814
  );
24719
24815
  }
24720
24816
  function calcToolbarPos(rect, parentScroll, toolbarW = 306) {
@@ -25153,7 +25249,7 @@ function OhhwellsBridge() {
25153
25249
  }, []);
25154
25250
  const subdomainFromQuery = searchParams.get("subdomain");
25155
25251
  const [subdomain, setSubdomain] = useState18(() => resolveSubdomain(subdomainFromQuery));
25156
- useLayoutEffect6(() => {
25252
+ useLayoutEffect7(() => {
25157
25253
  const next = resolveSubdomain(subdomainFromQuery);
25158
25254
  if (next !== subdomain) setSubdomain(next);
25159
25255
  if (next) return;
@@ -25604,21 +25700,6 @@ function OhhwellsBridge() {
25604
25700
  const [isItemDragging, setIsItemDragging] = useState18(false);
25605
25701
  const [isFooterFrameSelection, setIsFooterFrameSelection] = useState18(false);
25606
25702
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
25607
- const [floatingPanel, setFloatingPanel] = useState18(null);
25608
- const floatingPanelOpenRef = useRef14(false);
25609
- floatingPanelOpenRef.current = floatingPanel !== null;
25610
- const floatingPanelRef = useRef14(null);
25611
- floatingPanelRef.current = floatingPanel;
25612
- const [floatingPanelPos, setFloatingPanelPos] = useState18(null);
25613
- const buttonCollectionDragRef = useRef14(null);
25614
- const buttonDropInsertIndexRef = useRef14(null);
25615
- const [buttonDropSlots, setButtonDropSlots] = useState18([]);
25616
- const [activeButtonDropIndex, setActiveButtonDropIndex] = useState18(null);
25617
- const buttonPointerDragRef = useRef14(null);
25618
- const [buttonStyleTick, setButtonStyleTick] = useState18(0);
25619
- const [logoSizeDraft, setLogoSizeDraft] = useState18(null);
25620
- const [editorViewport, setEditorViewport] = useState18("desktop");
25621
- const [parentScrollSnap, setParentScrollSnap] = useState18(null);
25622
25703
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = useState18(null);
25623
25704
  const [footerHeadingVisible, setFooterHeadingVisible] = useState18(null);
25624
25705
  const footerDragRef = useRef14(null);
@@ -25644,6 +25725,21 @@ function OhhwellsBridge() {
25644
25725
  const stylesRef = useRef14("");
25645
25726
  const appliedDrawerRef = useRef14(null);
25646
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);
25647
25743
  const [sitePages, setSitePages] = useState18([]);
25648
25744
  const [sectionsByPath, setSectionsByPath] = useState18({});
25649
25745
  const sectionsPrefetchGenRef = useRef14(0);
@@ -25652,7 +25748,18 @@ function OhhwellsBridge() {
25652
25748
  const linkPopoverOpenRef = useRef14(false);
25653
25749
  const linkPopoverGraceUntilRef = useRef14(0);
25654
25750
  setLinkPopoverRef.current = setLinkPopover;
25751
+ setFloatingPanelRef.current = setFloatingPanel;
25655
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
+ }, []);
25656
25763
  useEffect17(() => {
25657
25764
  setSectionOrderPersistHandler((entries) => {
25658
25765
  const currentPath = window.location.pathname;
@@ -27320,7 +27427,7 @@ function OhhwellsBridge() {
27320
27427
  isFooterFrameSelection,
27321
27428
  postToParent2
27322
27429
  ]);
27323
- useLayoutEffect6(() => {
27430
+ useLayoutEffect7(() => {
27324
27431
  if (!subdomain || isEditMode) {
27325
27432
  setFetchState("done");
27326
27433
  return;
@@ -27347,6 +27454,7 @@ function OhhwellsBridge() {
27347
27454
  applyAiFooterFromContent(content);
27348
27455
  if (typeof content[STYLE_STORE_KEY] === "string") {
27349
27456
  stylesRef.current = content[STYLE_STORE_KEY];
27457
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
27350
27458
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
27351
27459
  }
27352
27460
  applyBrandChrome(content);
@@ -27357,12 +27465,12 @@ function OhhwellsBridge() {
27357
27465
  if (key === "__ohw_sections") continue;
27358
27466
  if (key === AI_SECTIONS_KEY) continue;
27359
27467
  if (key === AI_FOOTER_KEY) continue;
27468
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
27469
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
27360
27470
  if (key === BRAND_KIT_KEY) continue;
27361
27471
  if (key === STYLE_STORE_KEY) continue;
27362
27472
  if (BRAND_CHROME_KEYS.has(key)) continue;
27363
- if (key === LOGO_PLACEHOLDER_KEY) continue;
27364
27473
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
27365
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
27366
27474
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
27367
27475
  continue;
27368
27476
  }
@@ -27577,17 +27685,18 @@ function OhhwellsBridge() {
27577
27685
  }
27578
27686
  applyAiFooterFromContent(content);
27579
27687
  if (typeof content[STYLE_STORE_KEY] === "string") {
27688
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
27580
27689
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
27581
27690
  }
27582
27691
  for (const [key, val] of Object.entries(content)) {
27583
27692
  if (key === "__ohw_sections") continue;
27584
27693
  if (key === AI_SECTIONS_KEY) continue;
27585
27694
  if (key === AI_FOOTER_KEY) continue;
27695
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
27696
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
27586
27697
  if (key === BRAND_KIT_KEY) continue;
27587
27698
  if (key === STYLE_STORE_KEY) continue;
27588
27699
  if (BRAND_CHROME_KEYS.has(key)) continue;
27589
- if (key === LOGO_PLACEHOLDER_KEY) continue;
27590
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
27591
27700
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
27592
27701
  continue;
27593
27702
  }
@@ -27660,7 +27769,7 @@ function OhhwellsBridge() {
27660
27769
  if (debounceTimer) clearTimeout(debounceTimer);
27661
27770
  };
27662
27771
  }, [subdomain, isEditMode, pathname]);
27663
- useLayoutEffect6(() => {
27772
+ useLayoutEffect7(() => {
27664
27773
  const el = document.getElementById("ohw-loader");
27665
27774
  if (!el) return;
27666
27775
  const visible = Boolean(subdomain) && fetchState !== "done";
@@ -27844,26 +27953,11 @@ function OhhwellsBridge() {
27844
27953
  const t2 = setTimeout(measure, 500);
27845
27954
  const ro = new ResizeObserver(schedule);
27846
27955
  ro.observe(document.body);
27847
- let lastWidth = window.innerWidth;
27848
- let resizeTimers = [];
27849
- const clearResizeTimers = () => {
27850
- resizeTimers.forEach(clearTimeout);
27851
- resizeTimers = [];
27852
- };
27853
- const handleResize = () => {
27854
- if (window.innerWidth === lastWidth) return;
27855
- lastWidth = window.innerWidth;
27856
- clearResizeTimers();
27857
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
27858
- };
27859
- window.addEventListener("resize", handleResize);
27860
27956
  return () => {
27861
27957
  clearTimeout(t1);
27862
27958
  clearTimeout(t2);
27863
27959
  if (raf != null) cancelAnimationFrame(raf);
27864
27960
  ro.disconnect();
27865
- clearResizeTimers();
27866
- window.removeEventListener("resize", handleResize);
27867
27961
  };
27868
27962
  }, [pathname, isEditMode, postToParent2]);
27869
27963
  useEffect17(() => {
@@ -28162,6 +28256,13 @@ function OhhwellsBridge() {
28162
28256
  if (target.closest("[data-ohw-state-toggle]")) return;
28163
28257
  if (target.closest("[data-ohw-max-badge]")) return;
28164
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
+ }
28165
28266
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
28166
28267
  clearMediaSelectionRef.current();
28167
28268
  }
@@ -28185,13 +28286,6 @@ function OhhwellsBridge() {
28185
28286
  return;
28186
28287
  }
28187
28288
  }
28188
- if (isInsideFloatingPanel(target)) return;
28189
- if (target.closest("[data-ohw-form-toolbar]")) return;
28190
- if (target.closest(
28191
- '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
28192
- )) {
28193
- return;
28194
- }
28195
28289
  {
28196
28290
  const formEl = getFormElement(target);
28197
28291
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -28345,9 +28439,13 @@ function OhhwellsBridge() {
28345
28439
  if (isIconEditable(editable) && !getSocialItem(editable)) {
28346
28440
  e.preventDefault();
28347
28441
  e.stopPropagation();
28348
- selectIconElementRef.current(editable);
28349
28442
  const aiIcon = editable.hasAttribute("data-ai-icon");
28350
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
+ }
28351
28449
  postToParentRef.current({
28352
28450
  type: "ow:icon-pick",
28353
28451
  key: editable.dataset.ohwKey ?? "",
@@ -28575,6 +28673,9 @@ function OhhwellsBridge() {
28575
28673
  setHoveredItemRect(null);
28576
28674
  hoveredNavContainerRef.current = null;
28577
28675
  setHoveredNavContainerRect(null);
28676
+ siblingHintElRef.current = null;
28677
+ setSiblingHintRect(null);
28678
+ setSiblingHintRects([]);
28578
28679
  return;
28579
28680
  }
28580
28681
  {
@@ -28695,7 +28796,6 @@ function OhhwellsBridge() {
28695
28796
  hoveredNavContainerRef.current = null;
28696
28797
  setHoveredNavContainerRect(null);
28697
28798
  hoveredItemElRef.current = editable;
28698
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
28699
28799
  }
28700
28800
  }
28701
28801
  }
@@ -29405,7 +29505,11 @@ function OhhwellsBridge() {
29405
29505
  };
29406
29506
  const handleMouseMove = (e) => {
29407
29507
  const { clientX, clientY } = e;
29408
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
29508
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
29509
+ dismissImageHover();
29510
+ clearImageHover();
29511
+ return;
29512
+ }
29409
29513
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) {
29410
29514
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
29411
29515
  formHoverElRef.current = null;
@@ -29434,7 +29538,12 @@ function OhhwellsBridge() {
29434
29538
  setHoveredItemRect(null);
29435
29539
  hoveredNavContainerRef.current = null;
29436
29540
  setHoveredNavContainerRect(null);
29541
+ siblingHintElRef.current = null;
29542
+ setSiblingHintRect(null);
29543
+ setSiblingHintRects([]);
29437
29544
  dismissImageHover();
29545
+ clearImageHover();
29546
+ setSectionGap(null);
29438
29547
  return;
29439
29548
  }
29440
29549
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -29446,7 +29555,11 @@ function OhhwellsBridge() {
29446
29555
  if (e.data?.type !== "ow:pointer-sync") return;
29447
29556
  const { clientX, clientY } = e.data;
29448
29557
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
29449
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
29558
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
29559
+ dismissImageHover();
29560
+ clearImageHover();
29561
+ return;
29562
+ }
29450
29563
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) return;
29451
29564
  if (probeSocialsRowAt(clientX, clientY)) return;
29452
29565
  probeSectionGapAt(clientX, clientY);
@@ -29826,6 +29939,7 @@ function OhhwellsBridge() {
29826
29939
  applyAiFooterFromContent(content);
29827
29940
  if (typeof content[STYLE_STORE_KEY] === "string") {
29828
29941
  stylesRef.current = content[STYLE_STORE_KEY];
29942
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
29829
29943
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
29830
29944
  }
29831
29945
  applyBrandChrome(content);
@@ -29840,12 +29954,12 @@ function OhhwellsBridge() {
29840
29954
  }
29841
29955
  if (key === AI_SECTIONS_KEY) continue;
29842
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;
29843
29959
  if (key === BRAND_KIT_KEY) continue;
29844
29960
  if (key === STYLE_STORE_KEY) continue;
29845
29961
  if (BRAND_CHROME_KEYS.has(key)) continue;
29846
- if (key === LOGO_PLACEHOLDER_KEY) continue;
29847
29962
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
29848
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
29849
29963
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
29850
29964
  continue;
29851
29965
  }
@@ -30220,6 +30334,44 @@ function OhhwellsBridge() {
30220
30334
  });
30221
30335
  };
30222
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);
30223
30375
  const handleGetBrand = (e) => {
30224
30376
  if (e.data?.type !== "ow:get-brand") return;
30225
30377
  const template = deriveTemplateBrand();
@@ -30443,6 +30595,12 @@ function OhhwellsBridge() {
30443
30595
  closeLinkPopoverRef.current();
30444
30596
  return;
30445
30597
  }
30598
+ if (floatingPanelOpenRef.current) {
30599
+ setFloatingPanelRef.current(null);
30600
+ deselectRef.current();
30601
+ deactivateRef.current();
30602
+ return;
30603
+ }
30446
30604
  deselectRef.current();
30447
30605
  deactivateRef.current();
30448
30606
  clearMediaSelectionRef.current();
@@ -31253,6 +31411,7 @@ function OhhwellsBridge() {
31253
31411
  window.removeEventListener("message", handleAiSetSections);
31254
31412
  window.removeEventListener("message", handleAiSetBrand);
31255
31413
  window.removeEventListener("message", handleAiSetStyles);
31414
+ window.removeEventListener("message", handleAiSetButtonStyle);
31256
31415
  window.removeEventListener("message", handleCollectItems);
31257
31416
  window.removeEventListener("message", handleAiApplyItems);
31258
31417
  window.removeEventListener("message", handleGetBrand);
@@ -31264,10 +31423,10 @@ function OhhwellsBridge() {
31264
31423
  window.removeEventListener("message", handleDeleteSection);
31265
31424
  window.removeEventListener("message", handleDuplicateSection);
31266
31425
  window.removeEventListener("message", handleDeactivate);
31267
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
31268
31426
  window.removeEventListener("message", handleToastAction);
31269
31427
  window.removeEventListener("message", handleFormCount);
31270
31428
  window.removeEventListener("message", handleUiEscape);
31429
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
31271
31430
  autoSaveTimers.current.forEach(clearTimeout);
31272
31431
  autoSaveTimers.current.clear();
31273
31432
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -31561,7 +31720,7 @@ function OhhwellsBridge() {
31561
31720
  postToParent2({
31562
31721
  type: "ow:ready",
31563
31722
  version: "1",
31564
- bridgeVersion: "0.1.146",
31723
+ bridgeVersion: "0.1.149",
31565
31724
  path: pathname,
31566
31725
  nodes: collectEditableNodes(editContentRef.current),
31567
31726
  sections
@@ -32297,7 +32456,7 @@ function OhhwellsBridge() {
32297
32456
  className: "flex h-7 w-7 items-center justify-center rounded-md text-foreground transition-colors hover:bg-muted/80",
32298
32457
  onClick: () => setFieldTypePickerOpen((open) => !open),
32299
32458
  "data-ohw-add-field": "",
32300
- children: /* @__PURE__ */ jsx39(Plus5, { size: 15, "aria-hidden": true })
32459
+ children: /* @__PURE__ */ jsx39(Plus6, { size: 15, "aria-hidden": true })
32301
32460
  }
32302
32461
  ),
32303
32462
  /* @__PURE__ */ jsx39("div", { className: "mx-0.5 h-5 w-px bg-border" }),