@ohhwells/bridge 0.1.159 → 0.1.160-next.427

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -6763,6 +6763,21 @@ function pricingCardFeatureCount(tree, index) {
6763
6763
  const target = findPricingCollection(tree)?.children?.[index];
6764
6764
  return target ? pricingFeatureList(target).length : 0;
6765
6765
  }
6766
+ function prevailingFeatureIcon(features) {
6767
+ const counts = /* @__PURE__ */ new Map();
6768
+ for (const row of features) {
6769
+ const key = `${row.icon ?? ""}|${row.iconColor ?? ""}`;
6770
+ const entry = counts.get(key) ?? { count: 0, ...row.icon ? { icon: row.icon } : {}, ...row.iconColor ? { iconColor: row.iconColor } : {} };
6771
+ entry.count += 1;
6772
+ counts.set(key, entry);
6773
+ }
6774
+ let best = null;
6775
+ for (const entry of counts.values()) {
6776
+ if (!best || entry.count > best.count) best = entry;
6777
+ }
6778
+ if (!best) return {};
6779
+ return { ...best.icon ? { icon: best.icon } : {}, ...best.iconColor ? { iconColor: best.iconColor } : {} };
6780
+ }
6766
6781
  function addPricingCardFeature(tree, index, text) {
6767
6782
  return withPricingCollection(tree, (collection) => {
6768
6783
  const children = collection.children;
@@ -6771,7 +6786,7 @@ function addPricingCardFeature(tree, index, text) {
6771
6786
  const features = pricingFeatureList(target);
6772
6787
  if (features.length >= PRICING_FEATURES_MAX) return false;
6773
6788
  target.slots = target.slots ?? {};
6774
- target.slots.features = [...features, { text }];
6789
+ target.slots.features = [...features, { text, ...prevailingFeatureIcon(features) }];
6775
6790
  return true;
6776
6791
  });
6777
6792
  }
@@ -7383,6 +7398,23 @@ function applyCustomCodeToHead(content) {
7383
7398
  if (beforeNodes.length > 0) document.head.append(...beforeNodes);
7384
7399
  }
7385
7400
 
7401
+ // src/lib/node-font-size.ts
7402
+ var TEXT_NODE_TYPES = /* @__PURE__ */ new Set(["text", "text/rich", "plain", "link"]);
7403
+ function escapeKey(key) {
7404
+ return typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(key) : key.replace(/["\\]/g, "\\$&");
7405
+ }
7406
+ function measureNodeFontSize(key, type, root) {
7407
+ if (!TEXT_NODE_TYPES.has(type)) return void 0;
7408
+ const el = root.querySelector(`[data-ohw-editable][data-ohw-key="${escapeKey(key)}"]`);
7409
+ if (!el) return void 0;
7410
+ const size = Number.parseFloat(window.getComputedStyle(el).fontSize);
7411
+ return Number.isFinite(size) && size > 0 ? Math.round(size) : void 0;
7412
+ }
7413
+ function withNodeFontSize(node, root) {
7414
+ const fontSize = measureNodeFontSize(node.key, node.type, root);
7415
+ return fontSize === void 0 ? node : { ...node, fontSize };
7416
+ }
7417
+
7386
7418
  // src/lib/selectNavContainer.ts
7387
7419
  function isOnScreen(el) {
7388
7420
  const rect = el.getBoundingClientRect();
@@ -7971,7 +8003,7 @@ var AI_MOBILE_CSS = [
7971
8003
  // page's first section. Top only (bottom needs no nav clearance); ~96px approximates the
7972
8004
  // template nav's own height (measured ~94px) — there's no shared `--ohw-header-h` var to read
7973
8005
  // an exact figure from (no live measurement exists), so this is a safe approximation, not a
7974
- // pixel-exact fit. Horizontal spacing already comes from `data-ai-container`.
8006
+ // pixel-exact fit. Horizontal spacing already comes from `data-ai-section-inner`.
7975
8007
  '[data-ai-overlay-clearance] [data-ai-overlap-layer="overlay"]{padding-top:96px}',
7976
8008
  // A full-bleed carousel's height is pure arithmetic: it spans the whole window, so 16/9 turns
7977
8009
  // window width straight into height. Measured at 16/9 it came out ~90% of the viewport at EVERY
@@ -7987,8 +8019,10 @@ var AI_MOBILE_CSS = [
7987
8019
  "[data-ai-section]{overflow-x:hidden}",
7988
8020
  // `:not([data-ai-full-bleed])`: this `!important` gutter beat the inline `padding: 0` that a
7989
8021
  // full-bleed section's container sets, so a carousel/media hero that is edge-to-edge on desktop
7990
- // still got a 20px band of section background down both sides on a phone.
7991
- "[data-ai-container]:not([data-ai-full-bleed]){padding:0 20px !important}",
8022
+ // still got a 20px band of section background down both sides on a phone. The footer's own
8023
+ // container keeps the unguarded rule below — `data-ai-full-bleed` can never appear on it.
8024
+ "[data-ai-section-inner]:not([data-ai-full-bleed]){padding:0 20px !important}",
8025
+ "[data-ai-container]{padding:0 20px !important}",
7992
8026
  "[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
7993
8027
  "[data-ai-cell]{width:100%;min-width:0}",
7994
8028
  // The rule above turns every row into a COLUMN flex container on phones, which silently breaks
@@ -8193,6 +8227,31 @@ function pricingCardEmphasis(featured, ctx, hasBg) {
8193
8227
  function textAttrs(ctx, path) {
8194
8228
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
8195
8229
  }
8230
+ var AI_RESPONSIVE_CSS = [
8231
+ "@media (max-width: 960px) {",
8232
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
8233
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
8234
+ "}",
8235
+ "@media (max-width: 640px) {",
8236
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
8237
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
8238
+ // Group containers flatten to a column on phones; span placements come along for free.
8239
+ // `:not([data-ai-group="overlap"])` for the same reason as the twin pair in AI_MOBILE_CSS — see
8240
+ // the comment there. This sheet's copy is the narrower selector, so without the exemption it
8241
+ // flattens an `overlap` hero below 640px even though the other rule stands down: measured a 390px
8242
+ // viewport with the heading/CTA in flow *under* the image, 219px of section background where the
8243
+ // layered text used to be.
8244
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) { display: flex !important; flex-direction: column !important; }',
8245
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) > * { grid-column: auto !important; }',
8246
+ // `:not([data-ai-full-bleed])` for the same reason as the twin rule in AI_MOBILE_CSS: this one
8247
+ // is narrower (0,2,0 vs 0,1,0) and also `!important`, so without the guard it wins outright and
8248
+ // a full-bleed hero gets its 20px side band back below 640px only — the one width where the
8249
+ // other rule had already been taught to stand down.
8250
+ " [data-ai-responsive] [data-ai-section-inner]:not([data-ai-full-bleed]) { padding-left: 20px !important; padding-right: 20px !important; }",
8251
+ " [data-ai-responsive] { overflow-x: hidden; }",
8252
+ " [data-ai-responsive] img { max-width: 100%; }",
8253
+ "}"
8254
+ ].join("\n");
8196
8255
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
8197
8256
  function MediaBox({
8198
8257
  refValue,
@@ -8247,7 +8306,7 @@ function MediaBox({
8247
8306
  position: fill ? "absolute" : void 0,
8248
8307
  inset: fill ? 0 : void 0,
8249
8308
  width: fill ? "100%" : fixedSize ? "auto" : "100%",
8250
- height: fill ? "100%" : void 0,
8309
+ height: fill ? "100%" : fixedSize ? height : void 0,
8251
8310
  aspectRatio: fill ? void 0 : aspectRatio,
8252
8311
  minHeight: fill || fixedSize ? void 0 : height ?? (aspectRatio ? void 0 : 180),
8253
8312
  borderRadius: radius,
@@ -9743,7 +9802,7 @@ function CollectionBlock({ node, ctx, path }) {
9743
9802
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
9744
9803
  "div",
9745
9804
  {
9746
- "data-ai-grid": "",
9805
+ "data-ai-grid": String(itemsPerRow),
9747
9806
  "data-ohw-pricing-collection": isPricing ? "" : void 0,
9748
9807
  style: {
9749
9808
  display: "grid",
@@ -10045,7 +10104,7 @@ function renderNode(node, ctx, path, ambientAlign) {
10045
10104
  },
10046
10105
  children: [
10047
10106
  i > 0 && ctx.heroOverlapFullBleed ? (
10048
- // The section's `data-ai-container` gave up its max-width/gutter so the base
10107
+ // The section's `data-ai-section-inner` gave up its max-width/gutter so the base
10049
10108
  // layer (this group's i===0 sibling) can span edge-to-edge — reintroduced here,
10050
10109
  // scoped to just the overlay text, so a heading/copy/CTA stack doesn't stretch
10051
10110
  // the full section width instead of reading at the usual content width.
@@ -10464,6 +10523,7 @@ function AiTreeRenderer({
10464
10523
  ...fillHeight ? { "data-ai-fill-height": "" } : {},
10465
10524
  ...needsOverlayClearance ? { "data-ai-overlay-clearance": "" } : {},
10466
10525
  ...bgAttrs,
10526
+ "data-ai-responsive": "",
10467
10527
  style: {
10468
10528
  position: "relative",
10469
10529
  padding: `${padCss} 0`,
@@ -10477,12 +10537,13 @@ function AiTreeRenderer({
10477
10537
  ...fillHeight ? { display: "grid" } : {}
10478
10538
  },
10479
10539
  children: [
10480
- isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10540
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
10481
10541
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
10542
+ isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10482
10543
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
10483
10544
  "div",
10484
10545
  {
10485
- "data-ai-container": "",
10546
+ "data-ai-section-inner": "",
10486
10547
  ...fullBleedMedia ? { "data-ai-full-bleed": "" } : {},
10487
10548
  style: {
10488
10549
  position: "relative",
@@ -10499,7 +10560,7 @@ function AiTreeRenderer({
10499
10560
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
10500
10561
  "div",
10501
10562
  {
10502
- "data-ai-row": "",
10563
+ "data-ai-columns": "",
10503
10564
  style: {
10504
10565
  display: "grid",
10505
10566
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -11161,9 +11222,10 @@ function applyAiSectionsToDom(state, options) {
11161
11222
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
11162
11223
  continue;
11163
11224
  }
11164
- const isNewMount = !existing || !existing.container.isConnected;
11165
11225
  let container = existing?.container;
11226
+ let isNewMount = false;
11166
11227
  if (!container || !container.isConnected) {
11228
+ isNewMount = true;
11167
11229
  existing?.root.unmount();
11168
11230
  container?.remove();
11169
11231
  container = document.createElement("div");
@@ -17322,7 +17384,7 @@ function apiBase() {
17322
17384
  function bridgeVersion() {
17323
17385
  try {
17324
17386
  if (true) {
17325
- return "0.1.159";
17387
+ return "0.1.160-next.427";
17326
17388
  }
17327
17389
  } catch {
17328
17390
  }
@@ -17336,7 +17398,7 @@ function stampBridgeVersionOnDocument() {
17336
17398
  return;
17337
17399
  }
17338
17400
  try {
17339
- const v = true ? "0.1.159" : "";
17401
+ const v = true ? "0.1.160-next.427" : "";
17340
17402
  if (v) {
17341
17403
  document.documentElement.setAttribute("data-ohw-bridge-version", v);
17342
17404
  window.__OHW_BRIDGE_VERSION__ = v;
@@ -18014,6 +18076,7 @@ function MediaOverlay({
18014
18076
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
18015
18077
  );
18016
18078
  }, [isVideo]);
18079
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
18017
18080
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
18018
18081
  const box = {
18019
18082
  position: "fixed",
@@ -18112,11 +18175,10 @@ function MediaOverlay({
18112
18175
  className: "flex items-center justify-center cursor-pointer",
18113
18176
  style: {
18114
18177
  ...box,
18115
- // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
18116
- // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
18117
- // in-document, pointer-events does it natively. The button below opts back in, so
18118
- // Replace still works.
18119
- pointerEvents: hover.hasTextOverlap || cueOnly ? "none" : "auto",
18178
+ // Visual only. `pointer-events: auto` on this full-bleed rect covered the navbar
18179
+ // (hero bg includes the absolute TopNav) and swallowed CTA press-drag. Text overlay
18180
+ // and cueOnly also need clicks to fall through — Replace / video controls opt back in.
18181
+ pointerEvents: "none",
18120
18182
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
18121
18183
  // than hovered. Hover keeps the existing tinted preview.
18122
18184
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -18143,17 +18205,17 @@ function MediaOverlay({
18143
18205
  },
18144
18206
  children: [
18145
18207
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
18146
- isVideo ? "Replace video" : "Replace image"
18208
+ replaceLabel
18147
18209
  ]
18148
18210
  }
18149
18211
  ),
18150
- replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
18212
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
18151
18213
  Button,
18152
18214
  {
18153
18215
  "data-ohw-media-overlay": "",
18154
18216
  variant: "outline",
18155
18217
  size: "sm",
18156
- "aria-label": isVideo ? "Replace video" : "Replace image",
18218
+ "aria-label": replaceLabel,
18157
18219
  className: "gap-1.5 cursor-pointer hover:bg-background",
18158
18220
  style: {
18159
18221
  ...OVERLAY_BUTTON_STYLE,
@@ -18176,7 +18238,7 @@ function MediaOverlay({
18176
18238
  },
18177
18239
  children: [
18178
18240
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
18179
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
18241
+ replaceMode === "full" ? replaceLabel : null
18180
18242
  ]
18181
18243
  }
18182
18244
  )
@@ -18770,7 +18832,7 @@ function AiSectionOverlay({
18770
18832
 
18771
18833
  // src/ui/ai-section/AiMediaStripOverlay.tsx
18772
18834
  var import_react10 = require("react");
18773
- var import_lucide_react8 = require("lucide-react");
18835
+ var import_lucide_react9 = require("lucide-react");
18774
18836
 
18775
18837
  // src/lib/ai-media-strip-tree.ts
18776
18838
  var MEDIA_STRIP_ITEM_MIN = 1;
@@ -18884,14 +18946,6 @@ function setMediaStripItemAsset(tree, index, assetId) {
18884
18946
  return true;
18885
18947
  });
18886
18948
  }
18887
- function setMediaStripItemAlt(tree, index, alt) {
18888
- return withMediaStrip(tree, (block) => {
18889
- const items = block.items;
18890
- if (index < 0 || index >= items.length) return false;
18891
- items[index] = { ...items[index], alt };
18892
- return true;
18893
- });
18894
- }
18895
18949
  function setMediaStripItemLink(tree, index, link) {
18896
18950
  return withMediaStrip(tree, (block) => {
18897
18951
  const items = block.items;
@@ -18909,11 +18963,117 @@ function setMediaStripItemLink(tree, index, link) {
18909
18963
  });
18910
18964
  }
18911
18965
 
18912
- // src/ui/ai-section/AiMediaStripOverlay.tsx
18966
+ // src/ui/dialog.tsx
18967
+ var React9 = __toESM(require("react"), 1);
18968
+ var import_radix_ui6 = require("radix-ui");
18969
+ var import_lucide_react8 = require("lucide-react");
18913
18970
  var import_jsx_runtime20 = require("react/jsx-runtime");
18914
- var PRIMARY3 = "#0885FE";
18915
- var OHW_BLUE = `var(--ohw-primary, ${PRIMARY3})`;
18916
- var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY3}) 60%, #000000)`;
18971
+ function Dialog2({
18972
+ ...props
18973
+ }) {
18974
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_radix_ui6.Dialog.Root, { "data-slot": "dialog", ...props });
18975
+ }
18976
+ function DialogPortal({
18977
+ ...props
18978
+ }) {
18979
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_radix_ui6.Dialog.Portal, { ...props });
18980
+ }
18981
+ function DialogOverlay({
18982
+ className,
18983
+ ...props
18984
+ }) {
18985
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18986
+ import_radix_ui6.Dialog.Overlay,
18987
+ {
18988
+ "data-slot": "dialog-overlay",
18989
+ "data-ohw-link-modal-root": "",
18990
+ className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
18991
+ ...props
18992
+ }
18993
+ );
18994
+ }
18995
+ var DialogContent = React9.forwardRef(
18996
+ ({ className, children, showCloseButton = true, container, ...props }, ref) => {
18997
+ const positionMode = container ? "absolute" : "fixed";
18998
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(DialogPortal, { container: container ?? void 0, children: [
18999
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
19000
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
19001
+ import_radix_ui6.Dialog.Content,
19002
+ {
19003
+ ref,
19004
+ "data-slot": "dialog-content",
19005
+ className: cn(
19006
+ positionMode,
19007
+ "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",
19008
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
19009
+ container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
19010
+ className
19011
+ ),
19012
+ onMouseDown: (e) => e.stopPropagation(),
19013
+ ...props,
19014
+ children: [
19015
+ children,
19016
+ showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19017
+ import_radix_ui6.Dialog.Close,
19018
+ {
19019
+ type: "button",
19020
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
19021
+ "aria-label": "Close",
19022
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react8.X, { size: 16, "aria-hidden": true })
19023
+ }
19024
+ ) : null
19025
+ ]
19026
+ }
19027
+ )
19028
+ ] });
19029
+ }
19030
+ );
19031
+ DialogContent.displayName = import_radix_ui6.Dialog.Content.displayName;
19032
+ function DialogHeader({
19033
+ className,
19034
+ ...props
19035
+ }) {
19036
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
19037
+ }
19038
+ function DialogFooter({
19039
+ className,
19040
+ ...props
19041
+ }) {
19042
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19043
+ "div",
19044
+ {
19045
+ className: cn("flex items-center justify-end gap-2", className),
19046
+ ...props
19047
+ }
19048
+ );
19049
+ }
19050
+ var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19051
+ import_radix_ui6.Dialog.Title,
19052
+ {
19053
+ ref,
19054
+ className: cn(
19055
+ "text-lg font-semibold leading-none tracking-tight text-card-foreground",
19056
+ className
19057
+ ),
19058
+ ...props
19059
+ }
19060
+ ));
19061
+ DialogTitle.displayName = import_radix_ui6.Dialog.Title.displayName;
19062
+ var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19063
+ import_radix_ui6.Dialog.Description,
19064
+ {
19065
+ ref,
19066
+ className: cn("text-sm text-muted-foreground", className),
19067
+ ...props
19068
+ }
19069
+ ));
19070
+ DialogDescription.displayName = import_radix_ui6.Dialog.Description.displayName;
19071
+ var DialogClose = import_radix_ui6.Dialog.Close;
19072
+
19073
+ // src/ui/ai-section/AiMediaStripOverlay.tsx
19074
+ var import_jsx_runtime21 = require("react/jsx-runtime");
19075
+ var PRIMARY3 = "var(--ohw-primary, #0885FE)";
19076
+ 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]";
18917
19077
  function findMediaStripItemElement(sectionId, index) {
18918
19078
  const sectionEl = findByInstanceId(sectionId);
18919
19079
  if (!sectionEl) return null;
@@ -18923,14 +19083,35 @@ function findMediaStripItemElement(sectionId, index) {
18923
19083
  }
18924
19084
  return null;
18925
19085
  }
18926
- function readRect2(ref) {
18927
- const el = ref && findMediaStripItemElement(ref.sectionId, ref.index);
19086
+ function itemRefFromTarget(target) {
19087
+ if (!(target instanceof Element)) return null;
19088
+ if (target.closest(CHROME_SELECTOR)) return null;
19089
+ const itemEl = target.closest("[data-ohw-media-strip-item]");
19090
+ if (!itemEl) return null;
19091
+ const sectionEl = itemEl.closest("[data-ohw-section]");
19092
+ const unit = sectionEl ? movableUnit(sectionEl) : null;
19093
+ const sectionId = unit ? instanceIdOf(unit) || null : null;
19094
+ const index = Number(itemEl.dataset.ohwMediaStripIndex);
19095
+ if (!sectionId || Number.isNaN(index)) return null;
19096
+ return { sectionId, index };
19097
+ }
19098
+ function sameRef(a, b) {
19099
+ return a?.sectionId === b?.sectionId && a?.index === b?.index;
19100
+ }
19101
+ function rectOf(el) {
18928
19102
  if (!el) return null;
18929
19103
  const r2 = el.getBoundingClientRect();
18930
19104
  if (r2.width <= 0 || r2.height <= 0) return null;
18931
19105
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
18932
19106
  }
18933
- function useLiveItemRect(ref) {
19107
+ function readRect2(ref) {
19108
+ return rectOf(ref && findMediaStripItemElement(ref.sectionId, ref.index));
19109
+ }
19110
+ function readStripRect(ref) {
19111
+ const item = ref && findMediaStripItemElement(ref.sectionId, ref.index);
19112
+ return rectOf(item?.closest("[data-ohw-media-strip]") ?? item);
19113
+ }
19114
+ function useLiveItemRect(ref, strip = false) {
18934
19115
  const [rect, setRect] = (0, import_react10.useState)(null);
18935
19116
  (0, import_react10.useEffect)(() => {
18936
19117
  if (!ref) {
@@ -18938,7 +19119,7 @@ function useLiveItemRect(ref) {
18938
19119
  return;
18939
19120
  }
18940
19121
  const update = () => {
18941
- const next = readRect2(ref);
19122
+ const next = strip ? readStripRect(ref) : readRect2(ref);
18942
19123
  setRect(
18943
19124
  (prev) => prev && next && prev.top === next.top && prev.left === next.left && prev.width === next.width && prev.height === next.height ? prev : next
18944
19125
  );
@@ -18953,9 +19134,49 @@ function useLiveItemRect(ref) {
18953
19134
  window.removeEventListener("resize", update);
18954
19135
  clearInterval(interval);
18955
19136
  };
18956
- }, [ref?.sectionId, ref?.index]);
19137
+ }, [ref?.sectionId, ref?.index, strip]);
18957
19138
  return rect;
18958
19139
  }
19140
+ var TOOLBAR_STRIP_GAP = 52;
19141
+ var TOOLBAR_STRIP_GAP_MOBILE = 42;
19142
+ var TOOLBAR_EDGE_MARGIN2 = 4;
19143
+ function StripToolbarSlot({ stripRect, children }) {
19144
+ const slotRef = (0, import_react10.useRef)(null);
19145
+ const [shift, setShift] = (0, import_react10.useState)(0);
19146
+ const centerX = stripRect.left + stripRect.width / 2;
19147
+ (0, import_react10.useLayoutEffect)(() => {
19148
+ const slot = slotRef.current;
19149
+ if (!slot) return;
19150
+ const clamp = () => {
19151
+ const w = slot.offsetWidth;
19152
+ if (w <= 0) return;
19153
+ const half = w / 2;
19154
+ const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
19155
+ setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
19156
+ };
19157
+ clamp();
19158
+ const ro = new ResizeObserver(clamp);
19159
+ ro.observe(slot);
19160
+ return () => ro.disconnect();
19161
+ }, [centerX, children]);
19162
+ const mobile = window.innerWidth <= 480;
19163
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19164
+ "div",
19165
+ {
19166
+ ref: slotRef,
19167
+ "data-ohw-bridge": "",
19168
+ "data-ohw-item-interaction": "",
19169
+ className: "pointer-events-auto fixed",
19170
+ style: {
19171
+ top: stripRect.top - (mobile ? TOOLBAR_STRIP_GAP_MOBILE : TOOLBAR_STRIP_GAP),
19172
+ left: centerX + shift,
19173
+ transform: "translate(-50%, -100%)",
19174
+ zIndex: 2147483645
19175
+ },
19176
+ children
19177
+ }
19178
+ );
19179
+ }
18959
19180
  function requestValidateTree(postToParent2, tree) {
18960
19181
  return new Promise((resolve) => {
18961
19182
  const id = `ai-validate-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -18992,221 +19213,177 @@ function requestAssetPick(postToParent2) {
18992
19213
  postToParent2({ type: "ow:asset-pick", id });
18993
19214
  });
18994
19215
  }
18995
- function Pill({
19216
+ function ToolbarAction({
18996
19217
  label,
18997
19218
  onClick,
18998
- disabled,
18999
- active
19219
+ disabled = false,
19220
+ active = false,
19221
+ children
19000
19222
  }) {
19001
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19002
- "button",
19003
- {
19004
- type: "button",
19005
- onClick,
19006
- disabled,
19007
- style: {
19008
- height: 28,
19009
- padding: "0 10px",
19010
- display: "inline-flex",
19011
- alignItems: "center",
19012
- gap: 4,
19013
- borderRadius: 999,
19014
- border: "none",
19015
- cursor: disabled ? "default" : "pointer",
19016
- background: active ? OHW_BLUE_DARK : "#ffffff",
19017
- color: active ? "#ffffff" : OHW_BLUE_DARK,
19018
- opacity: disabled ? 0.4 : 1,
19019
- fontSize: 12,
19020
- lineHeight: "16px",
19021
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
19022
- whiteSpace: "nowrap",
19023
- flex: "0 0 auto"
19024
- },
19025
- children: label
19026
- }
19027
- );
19223
+ const button = /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, active, onClick, className: "size-8", children });
19224
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19225
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "inline-flex", children: button }) : button }),
19226
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19227
+ ] });
19028
19228
  }
19029
- function AltInput({ value, onCommit }) {
19030
- const [draft, setDraft] = (0, import_react10.useState)(value);
19031
- (0, import_react10.useEffect)(() => setDraft(value), [value]);
19032
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
19033
- "div",
19034
- {
19035
- style: {
19036
- display: "flex",
19037
- alignItems: "center",
19038
- gap: 6,
19039
- height: 28,
19040
- padding: "0 12px",
19041
- borderRadius: 999,
19042
- background: "#ffffff",
19043
- width: "100%",
19044
- maxWidth: 260,
19045
- boxSizing: "border-box"
19046
- },
19047
- children: [
19048
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19049
- "span",
19050
- {
19051
- style: {
19052
- flex: "0 0 auto",
19053
- color: OHW_BLUE_DARK,
19054
- fontSize: 10,
19055
- fontWeight: 700,
19056
- textTransform: "uppercase",
19057
- letterSpacing: 0.4
19058
- },
19059
- children: "Alt"
19060
- }
19061
- ),
19062
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19063
- "input",
19064
- {
19065
- type: "text",
19066
- value: draft,
19067
- onChange: (e) => setDraft(e.target.value),
19068
- onBlur: () => {
19069
- if (draft !== value) onCommit(draft);
19070
- },
19071
- onKeyDown: (e) => {
19072
- if (e.key === "Enter") e.currentTarget.blur();
19073
- if (e.key === "Escape") {
19074
- setDraft(value);
19075
- e.currentTarget.blur();
19076
- }
19077
- },
19078
- placeholder: "Describe this image",
19079
- "aria-label": "Image alt text",
19080
- style: {
19081
- flex: 1,
19082
- minWidth: 0,
19083
- border: "none",
19084
- outline: "none",
19085
- background: "transparent",
19086
- fontSize: 12,
19087
- lineHeight: "16px",
19088
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
19089
- color: "#1f2937"
19090
- }
19091
- }
19092
- )
19093
- ]
19094
- }
19095
- );
19229
+ function ToolbarSeparator() {
19230
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19096
19231
  }
19097
- var VARIANTS = [
19098
- { value: "logos", label: "Logos" },
19099
- { value: "reel", label: "Reel" }
19100
- ];
19101
- var ALIGNS = [
19102
- { value: "left", label: "Left" },
19103
- { value: "center", label: "Center" },
19104
- { value: "right", label: "Right" }
19105
- ];
19106
- var HOVERS = [
19107
- { value: "none", label: "None" },
19108
- { value: "lift", label: "Lift" },
19109
- { value: "zoom", label: "Zoom" },
19110
- { value: "fade", label: "Fade" }
19111
- ];
19112
- var TREATMENTS = [
19113
- { value: "color", label: "Color" },
19114
- { value: "grayscale", label: "Grayscale" }
19115
- ];
19116
- var HTTP_URL_RE = /^https?:\/\/\S+$/i;
19117
- function LinkInput({ value, onCommit }) {
19118
- const [draft, setDraft] = (0, import_react10.useState)(value);
19119
- const [invalid, setInvalid] = (0, import_react10.useState)(false);
19120
- (0, import_react10.useEffect)(() => {
19121
- setDraft(value);
19122
- setInvalid(false);
19123
- }, [value]);
19124
- const commit = () => {
19125
- const next = draft.trim();
19126
- if (next === value) return setInvalid(false);
19127
- if (next && !HTTP_URL_RE.test(next)) return setInvalid(true);
19128
- setInvalid(false);
19129
- onCommit(next);
19130
- };
19131
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
19132
- "div",
19133
- {
19134
- style: {
19135
- display: "flex",
19136
- alignItems: "center",
19137
- gap: 6,
19138
- height: 28,
19139
- padding: "0 12px",
19140
- borderRadius: 999,
19141
- background: "#ffffff",
19142
- width: "100%",
19143
- maxWidth: 260,
19144
- boxSizing: "border-box",
19145
- boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
19146
- },
19147
- children: [
19148
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19149
- "span",
19150
- {
19151
- style: {
19152
- flex: "0 0 auto",
19153
- color: OHW_BLUE_DARK,
19154
- fontSize: 10,
19155
- fontWeight: 700,
19156
- textTransform: "uppercase",
19157
- letterSpacing: 0.4
19158
- },
19159
- children: "Link"
19160
- }
19161
- ),
19162
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19163
- "input",
19232
+ function OptionMenu({
19233
+ label,
19234
+ value,
19235
+ options,
19236
+ onChange
19237
+ }) {
19238
+ const current = options.find((o) => o.value === value) ?? options[0];
19239
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19240
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xs text-muted-foreground", children: label }),
19241
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DropdownMenu, { modal: false, children: [
19242
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19243
+ "button",
19244
+ {
19245
+ type: "button",
19246
+ "aria-label": label,
19247
+ 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",
19248
+ children: [
19249
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "flex-1 text-left", children: current.label }),
19250
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ChevronDown, { size: 16, style: { pointerEvents: "none" } })
19251
+ ]
19252
+ }
19253
+ ) }),
19254
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[132px] min-w-[132px] p-1", children: options.map((option) => {
19255
+ const selected = option.value === value;
19256
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19257
+ DropdownMenuItem,
19164
19258
  {
19165
- type: "text",
19166
- inputMode: "url",
19167
- value: draft,
19168
- onChange: (e) => setDraft(e.target.value),
19169
- onBlur: commit,
19170
- onKeyDown: (e) => {
19171
- if (e.key === "Enter") e.currentTarget.blur();
19172
- if (e.key === "Escape") {
19173
- setDraft(value);
19174
- setInvalid(false);
19175
- e.currentTarget.blur();
19176
- }
19177
- },
19178
- placeholder: "https://example.com",
19179
- "aria-label": "Image link URL",
19180
- "aria-invalid": invalid,
19181
- style: {
19182
- flex: 1,
19183
- minWidth: 0,
19184
- border: "none",
19185
- outline: "none",
19186
- background: "transparent",
19187
- fontSize: 12,
19188
- lineHeight: "16px",
19189
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
19190
- color: "#1f2937"
19191
- }
19192
- }
19193
- )
19194
- ]
19259
+ role: "menuitemradio",
19260
+ "aria-checked": selected,
19261
+ onSelect: () => onChange(option.value),
19262
+ className: cn("grid grid-cols-[1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
19263
+ children: [
19264
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: option.label }),
19265
+ selected && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Check, { size: 16, className: "text-bridge-primary" })
19266
+ ]
19267
+ },
19268
+ option.value
19269
+ );
19270
+ }) })
19271
+ ] })
19272
+ ] });
19273
+ }
19274
+ var VARIANTS = [
19275
+ { value: "logos", label: "Logos" },
19276
+ { value: "reel", label: "Reel" }
19277
+ ];
19278
+ var ALIGNS = [
19279
+ { value: "left", label: "Align left", Icon: import_lucide_react9.AlignLeft },
19280
+ { value: "center", label: "Align center", Icon: import_lucide_react9.AlignCenter },
19281
+ { value: "right", label: "Align right", Icon: import_lucide_react9.AlignRight }
19282
+ ];
19283
+ var HOVERS = [
19284
+ { value: "none", label: "None" },
19285
+ { value: "lift", label: "Lift" },
19286
+ { value: "zoom", label: "Zoom" },
19287
+ { value: "fade", label: "Fade" }
19288
+ ];
19289
+ var HTTP_URL_RE = /^https?:\/\/\S+$/i;
19290
+ function LinkModal({
19291
+ href,
19292
+ linkTarget,
19293
+ onClose,
19294
+ onSave
19295
+ }) {
19296
+ const [draft, setDraft] = (0, import_react10.useState)(href);
19297
+ const [newTab, setNewTab] = (0, import_react10.useState)(linkTarget !== "same-tab");
19298
+ const [invalid, setInvalid] = (0, import_react10.useState)(false);
19299
+ const save = () => {
19300
+ const next = draft.trim();
19301
+ if (next && !HTTP_URL_RE.test(next)) {
19302
+ setInvalid(true);
19303
+ return;
19195
19304
  }
19196
- );
19305
+ onSave(next, newTab ? "new-tab" : "same-tab");
19306
+ onClose();
19307
+ };
19308
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Dialog2, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DialogContent, { "data-ohw-link-modal-root": "", className: "max-w-[400px] gap-4 p-5", children: [
19309
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DialogHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DialogTitle, { className: "text-base", children: "Image link" }) }),
19310
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex flex-col gap-3", children: [
19311
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19312
+ "input",
19313
+ {
19314
+ autoFocus: true,
19315
+ type: "text",
19316
+ inputMode: "url",
19317
+ value: draft,
19318
+ onChange: (e) => {
19319
+ setDraft(e.target.value);
19320
+ setInvalid(false);
19321
+ },
19322
+ onKeyDown: (e) => {
19323
+ if (e.key === "Enter") save();
19324
+ },
19325
+ placeholder: "https://example.com",
19326
+ "aria-label": "Image link URL",
19327
+ "aria-invalid": invalid,
19328
+ className: cn(
19329
+ "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",
19330
+ invalid && "border-destructive focus:border-destructive"
19331
+ )
19332
+ }
19333
+ ),
19334
+ invalid && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-xs text-destructive", children: "Enter a full URL starting with http:// or https://" }),
19335
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("label", { className: "flex items-center gap-2 text-[13px] text-foreground", children: [
19336
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("input", { type: "checkbox", checked: newTab, onChange: (e) => setNewTab(e.target.checked) }),
19337
+ "Open in new tab"
19338
+ ] })
19339
+ ] }),
19340
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DialogFooter, { children: [
19341
+ href && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19342
+ "button",
19343
+ {
19344
+ type: "button",
19345
+ onClick: () => {
19346
+ onSave("", "new-tab");
19347
+ onClose();
19348
+ },
19349
+ className: "mr-auto rounded-[6px] px-3 py-2 text-[13px] font-medium text-destructive transition-colors hover:bg-muted",
19350
+ children: "Remove link"
19351
+ }
19352
+ ),
19353
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19354
+ "button",
19355
+ {
19356
+ type: "button",
19357
+ onClick: onClose,
19358
+ className: "rounded-[6px] px-3 py-2 text-[13px] font-medium text-foreground transition-colors hover:bg-muted",
19359
+ children: "Cancel"
19360
+ }
19361
+ ),
19362
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19363
+ "button",
19364
+ {
19365
+ type: "button",
19366
+ onClick: save,
19367
+ className: "rounded-[6px] bg-bridge-primary px-3 py-2 text-[13px] font-medium text-primary-foreground transition-opacity hover:opacity-90",
19368
+ children: "Save"
19369
+ }
19370
+ )
19371
+ ] })
19372
+ ] }) });
19197
19373
  }
19198
19374
  function MediaStripToolbar({
19199
19375
  entry,
19200
19376
  index,
19201
19377
  pending,
19202
19378
  error,
19379
+ itemEditOn,
19380
+ onToggleItemEdit,
19203
19381
  onSetVariant,
19204
19382
  onSetAlign,
19205
19383
  onSetHover,
19206
19384
  onSetTreatment,
19207
19385
  onResize,
19208
- onSetAlt,
19209
- onSetLink,
19386
+ onOpenLink,
19210
19387
  onAddItem,
19211
19388
  onRemoveItem,
19212
19389
  onChangeImage,
@@ -19215,81 +19392,102 @@ function MediaStripToolbar({
19215
19392
  }) {
19216
19393
  const items = mediaStripItems(entry.tree);
19217
19394
  const itemCount = items.length;
19218
- const currentAlt = items[index]?.alt ?? "";
19219
- const currentHref = items[index]?.href ?? "";
19220
- const currentTarget = items[index]?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
19221
19395
  const slots = findMediaStripSlots(entry.tree);
19222
19396
  const variant = slots?.variant === "reel" ? "reel" : "logos";
19223
19397
  const align = slots?.align === "left" || slots?.align === "right" ? slots.align : "center";
19224
19398
  const hover = slots?.hover === "none" || slots?.hover === "lift" || slots?.hover === "zoom" || slots?.hover === "fade" ? slots.hover : "lift";
19225
19399
  const treatment = slots?.treatment === "color" || slots?.treatment === "grayscale" ? slots.treatment : variant === "logos" ? "grayscale" : "color";
19226
19400
  const sizeValue = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19227
- const isMobile = window.innerWidth <= 480;
19228
- const rowStyle = isMobile ? {
19229
- display: "flex",
19230
- gap: 6,
19231
- flexWrap: "nowrap",
19232
- justifyContent: "center",
19233
- overflowX: "auto",
19234
- WebkitOverflowScrolling: "touch",
19235
- width: "100%",
19236
- scrollbarWidth: "none"
19237
- } : { display: "flex", gap: 6, flexWrap: "nowrap", justifyContent: "center" };
19238
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
19401
+ const mobile = window.innerWidth <= 480;
19402
+ const gatedLabel = (label) => itemEditOn ? label : "Turn on the star to edit this image";
19403
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19239
19404
  "div",
19240
19405
  {
19241
19406
  "data-ohw-bridge": "",
19242
19407
  "data-ohw-toolbar": "",
19243
19408
  onMouseDown: (e) => e.stopPropagation(),
19244
- style: {
19245
- display: "flex",
19246
- flexDirection: "column",
19247
- gap: 6,
19248
- alignItems: "center",
19249
- background: OHW_BLUE,
19250
- padding: 8,
19251
- borderRadius: 12,
19252
- boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
19253
- pointerEvents: pending ? "none" : "auto",
19254
- opacity: pending ? 0.6 : 1,
19255
- maxWidth: isMobile ? "calc(100vw - 24px)" : "none"
19256
- },
19409
+ className: cn("flex flex-col items-center gap-1.5 font-sans", pending && "pointer-events-none opacity-60"),
19410
+ style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
19257
19411
  children: [
19258
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
19259
- VARIANTS.map((v) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: v.label, active: variant === v.value, onClick: () => onSetVariant(v.value) }, v.value)),
19260
- ALIGNS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: a.label, active: align === a.value, onClick: () => onSetAlign(a.value) }, a.value))
19261
- ] }),
19262
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
19263
- HOVERS.map((h) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: h.label, active: hover === h.value, onClick: () => onSetHover(h.value) }, h.value)),
19264
- TREATMENTS.map((t) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: t.label, active: treatment === t.value, onClick: () => onSetTreatment(t.value) }, t.value))
19265
- ] }),
19266
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
19267
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2212", onClick: () => onResize(-8) }),
19268
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { style: { color: "#ffffff", fontSize: 12, minWidth: 34, textAlign: "center" }, children: [
19269
- sizeValue,
19270
- "px"
19271
- ] }),
19272
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "+", onClick: () => onResize(8) })
19273
- ] }),
19274
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(AltInput, { value: currentAlt, onCommit: onSetAlt }) }),
19275
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(LinkInput, { value: currentHref, onCommit: (href) => onSetLink(href, currentTarget) }) }),
19276
- currentHref && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
19277
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "New tab", active: currentTarget === "new-tab", onClick: () => onSetLink(currentHref, "new-tab") }),
19278
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "Same tab", active: currentTarget === "same-tab", onClick: () => onSetLink(currentHref, "same-tab") })
19279
- ] }),
19280
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
19281
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "Change Image", onClick: onChangeImage }),
19282
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "+ Item", onClick: onAddItem }),
19283
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2212 Item", onClick: onRemoveItem, disabled: itemCount <= 1 })
19284
- ] }),
19285
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
19286
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2190 Move", onClick: onMoveLeft, disabled: index <= 0 }),
19287
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2192 Move", onClick: onMoveRight, disabled: index >= itemCount - 1 })
19288
- ] }),
19289
- error && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
19412
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19413
+ "div",
19414
+ {
19415
+ className: cn(
19416
+ "flex items-center gap-1 rounded-[10px] border border-border bg-background p-1 whitespace-nowrap",
19417
+ "shadow-[0px_10px_15px_-3px_rgba(0,0,0,0.1),0px_4px_6px_-4px_rgba(0,0,0,0.1)]",
19418
+ mobile ? "flex-wrap justify-center" : "flex-nowrap"
19419
+ ),
19420
+ children: [
19421
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19422
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19423
+ CustomToolbarButton,
19424
+ {
19425
+ type: "button",
19426
+ "aria-label": itemEditOn ? "Done editing this image" : "Edit this image",
19427
+ "aria-pressed": itemEditOn,
19428
+ onClick: onToggleItemEdit,
19429
+ className: "group size-8",
19430
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19431
+ import_lucide_react9.Star,
19432
+ {
19433
+ size: 16,
19434
+ fill: "currentColor",
19435
+ strokeWidth: 0,
19436
+ className: cn(
19437
+ "transition-colors",
19438
+ itemEditOn ? "text-[#f59e0b] group-hover:text-[#d97706]" : "text-[#d6d3d1] group-hover:text-[#fbbf24]"
19439
+ ),
19440
+ style: { pointerEvents: "none" }
19441
+ }
19442
+ )
19443
+ }
19444
+ ) }),
19445
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: itemEditOn ? "Done editing this image" : "Edit this image" })
19446
+ ] }),
19447
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19448
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: gatedLabel("Change image"), onClick: onChangeImage, disabled: !itemEditOn, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Image, { size: 16, style: { pointerEvents: "none" } }) }),
19449
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: gatedLabel("Link"), onClick: onOpenLink, disabled: !itemEditOn, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Link, { size: 16, style: { pointerEvents: "none" } }) }),
19450
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: gatedLabel("Move left"), onClick: onMoveLeft, disabled: !itemEditOn || index <= 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ArrowLeft, { size: 16, style: { pointerEvents: "none" } }) }),
19451
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19452
+ ToolbarAction,
19453
+ {
19454
+ label: gatedLabel("Move right"),
19455
+ onClick: onMoveRight,
19456
+ disabled: !itemEditOn || index >= itemCount - 1,
19457
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ArrowRight, { size: 16, style: { pointerEvents: "none" } })
19458
+ }
19459
+ ),
19460
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: gatedLabel("Remove image"), onClick: onRemoveItem, disabled: !itemEditOn || itemCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ImageMinus, { size: 16, style: { pointerEvents: "none" } }) }),
19461
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19462
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(OptionMenu, { label: "Layout", value: variant, options: VARIANTS, onChange: onSetVariant }),
19463
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(OptionMenu, { label: "Hover", value: hover, options: HOVERS, onChange: onSetHover }),
19464
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19465
+ ToolbarAction,
19466
+ {
19467
+ label: treatment === "grayscale" ? "Show in color" : "Show grayscale",
19468
+ active: treatment === "grayscale",
19469
+ onClick: () => onSetTreatment(treatment === "grayscale" ? "color" : "grayscale"),
19470
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Palette, { size: 16, style: { pointerEvents: "none" } })
19471
+ }
19472
+ ),
19473
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19474
+ ALIGNS.map(({ value, label, Icon }) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label, active: align === value, onClick: () => onSetAlign(value), children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Icon, { size: 16, style: { pointerEvents: "none" } }) }, value)),
19475
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19476
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Smaller", onClick: () => onResize(-8), children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Minus, { size: 16, style: { pointerEvents: "none" } }) }),
19477
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "min-w-[40px] text-center text-xs tabular-nums text-muted-foreground", children: [
19478
+ sizeValue,
19479
+ "px"
19480
+ ] }),
19481
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Larger", onClick: () => onResize(8), children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Plus, { size: 16, style: { pointerEvents: "none" } }) }),
19482
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19483
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Add image", onClick: onAddItem, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ImagePlus, { size: 16, style: { pointerEvents: "none" } }) })
19484
+ ]
19485
+ }
19486
+ ),
19487
+ error && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19290
19488
  ]
19291
19489
  }
19292
- );
19490
+ ) });
19293
19491
  }
19294
19492
  function findMediaStripSlots(tree) {
19295
19493
  const stack = tree.rows.flatMap((row) => row.blocks);
@@ -19308,31 +19506,23 @@ function AiMediaStripOverlay({
19308
19506
  }) {
19309
19507
  const [hover, setHover] = (0, import_react10.useState)(null);
19310
19508
  const [active, setActive] = (0, import_react10.useState)(null);
19509
+ const [itemEditOn, setItemEditOn] = (0, import_react10.useState)(false);
19510
+ const [linkOpen, setLinkOpen] = (0, import_react10.useState)(false);
19311
19511
  const [pending, setPending] = (0, import_react10.useState)(false);
19312
19512
  const [error, setError] = (0, import_react10.useState)(null);
19313
19513
  const activeRef = (0, import_react10.useRef)(null);
19314
19514
  activeRef.current = active;
19515
+ const linkOpenRef = (0, import_react10.useRef)(false);
19516
+ linkOpenRef.current = linkOpen;
19315
19517
  (0, import_react10.useEffect)(() => {
19316
19518
  const onMove = (e) => {
19519
+ const ref = itemRefFromTarget(e.target);
19317
19520
  const t = e.target;
19318
- if (!(t instanceof HTMLElement)) return;
19319
- 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]")) {
19320
- return;
19321
- }
19322
- const itemEl = t.closest("[data-ohw-media-strip-item]");
19323
- if (!itemEl) {
19324
- setHover(null);
19325
- return;
19326
- }
19327
- const sectionEl = itemEl.closest("[data-ohw-section]");
19328
- const unit = sectionEl ? movableUnit(sectionEl) : null;
19329
- const sectionId = unit ? instanceIdOf(unit) || null : null;
19330
- const index = Number(itemEl.dataset.ohwMediaStripIndex);
19331
- if (!sectionId || Number.isNaN(index)) {
19332
- setHover(null);
19333
- return;
19334
- }
19335
- setHover({ sectionId, index });
19521
+ const overEditable = Boolean(t?.closest?.("[data-ohw-editable]"));
19522
+ setHover((prev) => {
19523
+ const next = ref && !overEditable ? ref : null;
19524
+ return sameRef(prev, next) ? prev : next;
19525
+ });
19336
19526
  };
19337
19527
  const onLeave = () => setHover(null);
19338
19528
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -19344,23 +19534,25 @@ function AiMediaStripOverlay({
19344
19534
  }, []);
19345
19535
  (0, import_react10.useEffect)(() => {
19346
19536
  const onClick = (e) => {
19347
- const current = activeRef.current;
19348
- if (!current) return;
19349
19537
  const t = e.target;
19350
- if (!(t instanceof HTMLElement)) return;
19351
- if (t.closest("[data-ohw-item-interaction]")) return;
19352
- const itemEl = findMediaStripItemElement(current.sectionId, current.index);
19353
- if (itemEl && itemEl.contains(t)) return;
19354
- setActive(null);
19538
+ if (!(t instanceof Element)) return;
19539
+ if (t.closest(CHROME_SELECTOR)) return;
19540
+ const ref = itemRefFromTarget(t);
19541
+ if (sameRef(activeRef.current, ref)) return;
19542
+ if (ref) clearInteractionChromeRef.current();
19543
+ setItemEditOn(false);
19355
19544
  setError(null);
19545
+ setActive(ref);
19356
19546
  };
19357
19547
  document.addEventListener("click", onClick, true);
19358
19548
  return () => document.removeEventListener("click", onClick, true);
19359
- }, []);
19549
+ }, [clearInteractionChromeRef]);
19360
19550
  (0, import_react10.useEffect)(() => {
19361
19551
  const onBlur = () => {
19362
19552
  if (!activeRef.current) return;
19363
19553
  setActive(null);
19554
+ setItemEditOn(false);
19555
+ setLinkOpen(false);
19364
19556
  setError(null);
19365
19557
  };
19366
19558
  window.addEventListener("blur", onBlur);
@@ -19370,7 +19562,10 @@ function AiMediaStripOverlay({
19370
19562
  const onKeyDown = (e) => {
19371
19563
  if (e.key !== "Escape") return;
19372
19564
  if (!activeRef.current) return;
19565
+ if (linkOpenRef.current) return;
19566
+ if (document.querySelector('[data-ohw-more-menu][data-state="open"]')) return;
19373
19567
  setActive(null);
19568
+ setItemEditOn(false);
19374
19569
  setError(null);
19375
19570
  };
19376
19571
  document.addEventListener("keydown", onKeyDown);
@@ -19404,137 +19599,133 @@ function AiMediaStripOverlay({
19404
19599
  tree: nextTree,
19405
19600
  label: entry.label,
19406
19601
  media: extraMedia ? { ...entry.media, ...extraMedia } : entry.media,
19407
- ...entry.brand ? { brand: entry.brand } : {}
19602
+ ...entry.brand ? { brand: entry.brand } : {},
19603
+ // The user is already looking at the strip they just edited — recentering the viewport
19604
+ // on every edit reads as the section jumping around.
19605
+ skipScrollIntoView: true
19408
19606
  });
19409
19607
  return true;
19410
19608
  },
19411
19609
  [findEntry, postToParent2, applyTreeLocalRef]
19412
19610
  );
19413
19611
  const activeEntry = active ? findEntry(active.sectionId) : null;
19414
- const hoverHandleRect = useLiveItemRect(!active && hover ? hover : null);
19415
19612
  const activeItemRect = useLiveItemRect(active);
19416
- const TOOLBAR_APPROX_H = 170;
19417
- const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
19418
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
19419
- hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19420
- "button",
19421
- {
19422
- type: "button",
19423
- "data-ohw-toolbar": "",
19424
- onClick: () => {
19425
- clearInteractionChromeRef.current();
19426
- setActive(hover);
19427
- },
19428
- "aria-label": "Configure media item",
19429
- style: {
19430
- position: "fixed",
19431
- top: hoverHandleRect.top + 8,
19432
- left: hoverHandleRect.left + hoverHandleRect.width - 36,
19433
- width: 28,
19434
- height: 28,
19435
- borderRadius: 8,
19436
- border: `1.5px dashed ${PRIMARY3}`,
19437
- background: "#ffffff",
19438
- color: PRIMARY3,
19439
- display: "flex",
19440
- alignItems: "center",
19441
- justifyContent: "center",
19442
- cursor: "pointer",
19443
- zIndex: 2147483643,
19444
- fontSize: 14,
19445
- fontWeight: 700
19446
- },
19447
- children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react8.Star, { size: 14, style: { pointerEvents: "none" } })
19448
- }
19449
- ),
19450
- activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19451
- ItemInteractionLayer,
19452
- {
19453
- rect: activeItemRect,
19454
- state: toolbarState,
19455
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19456
- MediaStripToolbar,
19457
- {
19458
- entry: activeEntry,
19459
- index: active.index,
19460
- pending,
19461
- error,
19462
- onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
19463
- onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
19464
- onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
19465
- onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
19466
- onResize: (delta) => {
19467
- const slots = findMediaStripSlots(activeEntry.tree);
19468
- const variant = slots?.variant === "reel" ? "reel" : "logos";
19469
- const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19470
- void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
19471
- },
19472
- onSetAlt: (alt) => {
19473
- void runMutation(active.sectionId, (tree) => setMediaStripItemAlt(tree, active.index, alt));
19474
- },
19475
- onSetLink: (href, linkTarget) => {
19613
+ const activeStripRect = useLiveItemRect(active, true);
19614
+ const hoverRect = useLiveItemRect(hover && !sameRef(hover, active) ? hover : null);
19615
+ const activeItems = activeEntry ? mediaStripItems(activeEntry.tree) : [];
19616
+ const activeItem = active ? activeItems[active.index] : void 0;
19617
+ const activeHref = activeItem?.href ?? "";
19618
+ const activeTarget = activeItem?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
19619
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19620
+ hoverRect && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19621
+ activeItemRect && activeStripRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19622
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: activeItemRect, state: "active-top" }),
19623
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StripToolbarSlot, { stripRect: activeStripRect, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19624
+ MediaStripToolbar,
19625
+ {
19626
+ entry: activeEntry,
19627
+ index: active.index,
19628
+ pending,
19629
+ error,
19630
+ itemEditOn,
19631
+ onToggleItemEdit: () => setItemEditOn((on) => !on),
19632
+ onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
19633
+ onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
19634
+ onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
19635
+ onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
19636
+ onResize: (delta) => {
19637
+ const slots = findMediaStripSlots(activeEntry.tree);
19638
+ const variant = slots?.variant === "reel" ? "reel" : "logos";
19639
+ const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19640
+ void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
19641
+ },
19642
+ onOpenLink: () => setLinkOpen(true),
19643
+ onAddItem: () => {
19644
+ void requestAssetPick(postToParent2).then((picked) => {
19645
+ if (!picked) return;
19646
+ const item = { assetId: picked.assetId, alt: "Logo" };
19476
19647
  void runMutation(
19477
19648
  active.sectionId,
19478
- (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
19479
- );
19480
- },
19481
- onAddItem: () => {
19482
- void requestAssetPick(postToParent2).then((picked) => {
19483
- if (!picked) return;
19484
- const item = { assetId: picked.assetId, alt: "Logo" };
19485
- void runMutation(
19486
- active.sectionId,
19487
- (tree) => addMediaStripItem(tree, item, active.index),
19488
- { [picked.assetId]: picked.url }
19489
- ).then((applied) => {
19490
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19491
- });
19649
+ (tree) => addMediaStripItem(tree, item, active.index),
19650
+ { [picked.assetId]: picked.url }
19651
+ ).then((applied) => {
19652
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19492
19653
  });
19493
- },
19494
- onChangeImage: () => {
19495
- void requestAssetPick(postToParent2).then((picked) => {
19496
- if (!picked) return;
19497
- void runMutation(
19498
- active.sectionId,
19499
- (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
19500
- { [picked.assetId]: picked.url }
19501
- );
19502
- });
19503
- },
19504
- onRemoveItem: () => {
19505
- void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
19506
- (applied) => {
19507
- if (applied) setActive(null);
19508
- }
19509
- );
19510
- },
19511
- onMoveLeft: () => {
19512
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
19513
- (applied) => {
19514
- if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
19515
- }
19516
- );
19517
- },
19518
- onMoveRight: () => {
19519
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
19520
- (applied) => {
19521
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19522
- }
19654
+ });
19655
+ },
19656
+ onChangeImage: () => {
19657
+ void requestAssetPick(postToParent2).then((picked) => {
19658
+ if (!picked) return;
19659
+ void runMutation(
19660
+ active.sectionId,
19661
+ (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
19662
+ { [picked.assetId]: picked.url }
19523
19663
  );
19524
- }
19664
+ });
19665
+ },
19666
+ onRemoveItem: () => {
19667
+ void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
19668
+ (applied) => {
19669
+ if (applied) setActive(null);
19670
+ }
19671
+ );
19672
+ },
19673
+ onMoveLeft: () => {
19674
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
19675
+ (applied) => {
19676
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
19677
+ }
19678
+ );
19679
+ },
19680
+ onMoveRight: () => {
19681
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
19682
+ (applied) => {
19683
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19684
+ }
19685
+ );
19525
19686
  }
19526
- )
19527
- }
19528
- )
19687
+ }
19688
+ ) }),
19689
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19690
+ "div",
19691
+ {
19692
+ "data-ohw-bridge": "",
19693
+ "data-ohw-item-interaction": "",
19694
+ "aria-hidden": true,
19695
+ className: "pointer-events-none fixed rounded-[4px] px-[7px] py-[3px] font-sans text-[11px] font-medium leading-none text-primary-foreground whitespace-nowrap",
19696
+ style: {
19697
+ top: activeItemRect.top + activeItemRect.height + 10,
19698
+ left: activeItemRect.left - 5,
19699
+ background: PRIMARY3,
19700
+ zIndex: 2147483644
19701
+ },
19702
+ children: "Media item"
19703
+ }
19704
+ ),
19705
+ linkOpen && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19706
+ LinkModal,
19707
+ {
19708
+ href: activeHref,
19709
+ linkTarget: activeTarget,
19710
+ onClose: () => setLinkOpen(false),
19711
+ onSave: (href, linkTarget) => {
19712
+ void runMutation(
19713
+ active.sectionId,
19714
+ (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
19715
+ );
19716
+ }
19717
+ }
19718
+ )
19719
+ ] })
19529
19720
  ] });
19530
19721
  }
19531
19722
 
19532
19723
  // src/ui/ai-section/AiPricingCardOverlay.tsx
19533
19724
  var import_react11 = require("react");
19534
- var import_lucide_react9 = require("lucide-react");
19535
- var import_jsx_runtime21 = require("react/jsx-runtime");
19725
+ var import_lucide_react10 = require("lucide-react");
19726
+ var import_jsx_runtime22 = require("react/jsx-runtime");
19536
19727
  var PRIMARY4 = "var(--ohw-primary, #0885FE)";
19537
- 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]";
19728
+ 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]";
19538
19729
  function findPricingCardElement(sectionId, index) {
19539
19730
  const sectionEl = findByInstanceId(sectionId);
19540
19731
  if (!sectionEl) return null;
@@ -19546,7 +19737,7 @@ function findPricingCardElement(sectionId, index) {
19546
19737
  }
19547
19738
  function cardRefFromTarget(target) {
19548
19739
  if (!(target instanceof Element)) return null;
19549
- if (target.closest(CHROME_SELECTOR)) return null;
19740
+ if (target.closest(CHROME_SELECTOR2)) return null;
19550
19741
  const cardEl = target.closest("[data-ohw-pricing-card]");
19551
19742
  if (!cardEl) return null;
19552
19743
  const sectionEl = cardEl.closest("[data-ohw-section]");
@@ -19556,21 +19747,21 @@ function cardRefFromTarget(target) {
19556
19747
  if (!sectionId || Number.isNaN(index)) return null;
19557
19748
  return { sectionId, index };
19558
19749
  }
19559
- function sameRef(a, b) {
19750
+ function sameRef2(a, b) {
19560
19751
  return a?.sectionId === b?.sectionId && a?.index === b?.index;
19561
19752
  }
19562
- function rectOf(el) {
19753
+ function rectOf2(el) {
19563
19754
  if (!el) return null;
19564
19755
  const r2 = el.getBoundingClientRect();
19565
19756
  if (r2.width <= 0 || r2.height <= 0) return null;
19566
19757
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
19567
19758
  }
19568
19759
  function readRect3(ref) {
19569
- return rectOf(ref && findPricingCardElement(ref.sectionId, ref.index));
19760
+ return rectOf2(ref && findPricingCardElement(ref.sectionId, ref.index));
19570
19761
  }
19571
19762
  function readGridRect(ref) {
19572
19763
  const card = ref && findPricingCardElement(ref.sectionId, ref.index);
19573
- return rectOf(card?.closest("[data-ohw-pricing-collection]") ?? card);
19764
+ return rectOf2(card?.closest("[data-ohw-pricing-collection]") ?? card);
19574
19765
  }
19575
19766
  function useLiveCardRect(ref, grid = false) {
19576
19767
  const [rect, setRect] = (0, import_react11.useState)(null);
@@ -19600,7 +19791,7 @@ function useLiveCardRect(ref, grid = false) {
19600
19791
  }
19601
19792
  var TOOLBAR_GRID_GAP = 52;
19602
19793
  var TOOLBAR_GRID_GAP_MOBILE = 42;
19603
- var TOOLBAR_EDGE_MARGIN2 = 4;
19794
+ var TOOLBAR_EDGE_MARGIN3 = 4;
19604
19795
  function GridToolbarSlot({ gridRect, children }) {
19605
19796
  const slotRef = (0, import_react11.useRef)(null);
19606
19797
  const [shift, setShift] = (0, import_react11.useState)(0);
@@ -19612,7 +19803,7 @@ function GridToolbarSlot({ gridRect, children }) {
19612
19803
  const w = slot.offsetWidth;
19613
19804
  if (w <= 0) return;
19614
19805
  const half = w / 2;
19615
- const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
19806
+ const clamped = Math.max(TOOLBAR_EDGE_MARGIN3 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half));
19616
19807
  setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
19617
19808
  };
19618
19809
  clamp();
@@ -19621,7 +19812,7 @@ function GridToolbarSlot({ gridRect, children }) {
19621
19812
  return () => ro.disconnect();
19622
19813
  }, [centerX, children]);
19623
19814
  const mobile = window.innerWidth <= 480;
19624
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19815
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19625
19816
  "div",
19626
19817
  {
19627
19818
  ref: slotRef,
@@ -19656,7 +19847,7 @@ function requestValidateTree2(postToParent2, tree) {
19656
19847
  });
19657
19848
  }
19658
19849
  function StyleSwatch({ style }) {
19659
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19850
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19660
19851
  "span",
19661
19852
  {
19662
19853
  "aria-hidden": true,
@@ -19670,35 +19861,35 @@ function StyleSwatch({ style }) {
19670
19861
  );
19671
19862
  }
19672
19863
  function ButtonGlyph() {
19673
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("svg", { width: "18", height: "12", viewBox: "0 0 18 12", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19674
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("rect", { x: "0.75", y: "0.75", width: "16.5", height: "10.5", rx: "3", stroke: "currentColor", strokeWidth: "1.5" }),
19675
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
19864
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("svg", { width: "18", height: "12", viewBox: "0 0 18 12", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19865
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("rect", { x: "0.75", y: "0.75", width: "16.5", height: "10.5", rx: "3", stroke: "currentColor", strokeWidth: "1.5" }),
19866
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
19676
19867
  ] });
19677
19868
  }
19678
19869
  function FeatureGlyph({ mode }) {
19679
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19680
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
19681
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
19682
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("circle", { cx: "13.5", cy: "13.5", r: "4.25", fill: "var(--ohw-background, #ffffff)", stroke: "var(--ohw-background, #ffffff)", strokeWidth: "1" }),
19683
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("circle", { cx: "13.5", cy: "13.5", r: "3.75", fill: "var(--ohw-background, #ffffff)", stroke: PRIMARY4, strokeWidth: "1" }),
19684
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
19685
- mode === "add" && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
19870
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19871
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
19872
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
19873
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("circle", { cx: "13.5", cy: "13.5", r: "4.25", fill: "var(--ohw-background, #ffffff)", stroke: "var(--ohw-background, #ffffff)", strokeWidth: "1" }),
19874
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("circle", { cx: "13.5", cy: "13.5", r: "3.75", fill: "var(--ohw-background, #ffffff)", stroke: PRIMARY4, strokeWidth: "1" }),
19875
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
19876
+ mode === "add" && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
19686
19877
  ] });
19687
19878
  }
19688
- function ToolbarAction({
19879
+ function ToolbarAction2({
19689
19880
  label,
19690
19881
  onClick,
19691
19882
  disabled = false,
19692
19883
  children
19693
19884
  }) {
19694
- const button = /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
19695
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19696
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "inline-flex", children: button }) : button }),
19697
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19885
+ const button = /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
19886
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Tooltip, { children: [
19887
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "inline-flex", children: button }) : button }),
19888
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19698
19889
  ] });
19699
19890
  }
19700
- function ToolbarSeparator() {
19701
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19891
+ function ToolbarSeparator2() {
19892
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19702
19893
  }
19703
19894
  function CardStyleMenu({
19704
19895
  value,
@@ -19706,7 +19897,7 @@ function CardStyleMenu({
19706
19897
  disabled = false
19707
19898
  }) {
19708
19899
  const current = PRICING_CARD_STYLES.find((s) => s.value === value) ?? PRICING_CARD_STYLES[0];
19709
- const trigger = /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19900
+ const trigger = /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19710
19901
  "button",
19711
19902
  {
19712
19903
  type: "button",
@@ -19717,28 +19908,28 @@ function CardStyleMenu({
19717
19908
  disabled && "cursor-not-allowed opacity-40 hover:bg-transparent"
19718
19909
  ),
19719
19910
  children: [
19720
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StyleSwatch, { style: current.value }),
19721
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "flex-1 text-left", children: current.label }),
19722
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ChevronDown, { size: 16, style: { pointerEvents: "none" } })
19911
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(StyleSwatch, { style: current.value }),
19912
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "flex-1 text-left", children: current.label }),
19913
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.ChevronDown, { size: 16, style: { pointerEvents: "none" } })
19723
19914
  ]
19724
19915
  }
19725
19916
  );
19726
19917
  if (disabled) {
19727
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19728
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
19729
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19730
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "inline-flex", children: trigger }) }),
19731
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
19918
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19919
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
19920
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Tooltip, { children: [
19921
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "inline-flex", children: trigger }) }),
19922
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
19732
19923
  ] })
19733
19924
  ] });
19734
19925
  }
19735
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19736
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
19737
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DropdownMenu, { modal: false, children: [
19738
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuTrigger, { asChild: true, children: trigger }),
19739
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[152px] min-w-[152px] p-1", children: PRICING_CARD_STYLES.map((style) => {
19926
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19927
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
19928
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(DropdownMenu, { modal: false, children: [
19929
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(DropdownMenuTrigger, { asChild: true, children: trigger }),
19930
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[152px] min-w-[152px] p-1", children: PRICING_CARD_STYLES.map((style) => {
19740
19931
  const selected = style.value === value;
19741
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19932
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19742
19933
  DropdownMenuItem,
19743
19934
  {
19744
19935
  role: "menuitemradio",
@@ -19746,9 +19937,9 @@ function CardStyleMenu({
19746
19937
  onSelect: () => onChange(style.value),
19747
19938
  className: cn("grid grid-cols-[18px_1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
19748
19939
  children: [
19749
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StyleSwatch, { style: style.value }),
19750
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: style.label }),
19751
- selected && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Check, { size: 16, className: "text-bridge-primary" })
19940
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(StyleSwatch, { style: style.value }),
19941
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { children: style.label }),
19942
+ selected && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Check, { size: 16, className: "text-bridge-primary" })
19752
19943
  ]
19753
19944
  },
19754
19945
  style.value
@@ -19778,7 +19969,7 @@ function PricingCardToolbar({
19778
19969
  const style = pricingCardStyle(entry.tree, index);
19779
19970
  const featuredTag = pricingCardFeaturedTag(entry.tree, index);
19780
19971
  const mobile = window.innerWidth <= 480;
19781
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19972
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19782
19973
  "div",
19783
19974
  {
19784
19975
  "data-ohw-bridge": "",
@@ -19790,7 +19981,7 @@ function PricingCardToolbar({
19790
19981
  ),
19791
19982
  style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
19792
19983
  children: [
19793
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19984
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19794
19985
  "div",
19795
19986
  {
19796
19987
  className: cn(
@@ -19799,8 +19990,8 @@ function PricingCardToolbar({
19799
19990
  mobile ? "flex-wrap justify-center" : "flex-nowrap"
19800
19991
  ),
19801
19992
  children: [
19802
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19803
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19993
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Tooltip, { children: [
19994
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19804
19995
  CustomToolbarButton,
19805
19996
  {
19806
19997
  type: "button",
@@ -19808,8 +19999,8 @@ function PricingCardToolbar({
19808
19999
  "aria-pressed": featuredTag,
19809
20000
  onClick: () => onSetFeaturedTag(!featuredTag),
19810
20001
  className: "group size-8",
19811
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19812
- import_lucide_react9.Star,
20002
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20003
+ import_lucide_react10.Star,
19813
20004
  {
19814
20005
  size: 16,
19815
20006
  fill: "currentColor",
@@ -19823,25 +20014,25 @@ function PricingCardToolbar({
19823
20014
  )
19824
20015
  }
19825
20016
  ) }),
19826
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
20017
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
19827
20018
  ] }),
19828
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19829
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
19830
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19831
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ButtonGlyph, {}) }),
19832
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Remove button", onClick: onRemoveButton, disabled: buttonCount === 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "relative inline-flex", children: [
19833
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ButtonGlyph, {}),
19834
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Minus, { size: 10, strokeWidth: 2.5, className: "absolute -right-1.5 -top-1.5 rounded-full bg-background text-bridge-primary" })
20019
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarSeparator2, {}),
20020
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
20021
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarSeparator2, {}),
20022
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ButtonGlyph, {}) }),
20023
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Remove button", onClick: onRemoveButton, disabled: buttonCount === 0, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("span", { className: "relative inline-flex", children: [
20024
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ButtonGlyph, {}),
20025
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Minus, { size: 10, strokeWidth: 2.5, className: "absolute -right-1.5 -top-1.5 rounded-full bg-background text-bridge-primary" })
19835
20026
  ] }) }),
19836
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Add feature", onClick: onAddFeature, disabled: featureCount >= 10, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(FeatureGlyph, { mode: "add" }) }),
19837
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Remove feature", onClick: onRemoveFeature, disabled: featureCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(FeatureGlyph, { mode: "remove" }) }),
19838
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19839
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Add tier", onClick: onAddTier, disabled: collectionCount >= 4, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Plus, { size: 16, style: { pointerEvents: "none" } }) }),
19840
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Remove tier", onClick: onRemoveTier, disabled: collectionCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Minus, { size: 16, style: { pointerEvents: "none" } }) })
20027
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Add feature", onClick: onAddFeature, disabled: featureCount >= 10, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(FeatureGlyph, { mode: "add" }) }),
20028
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Remove feature", onClick: onRemoveFeature, disabled: featureCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(FeatureGlyph, { mode: "remove" }) }),
20029
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarSeparator2, {}),
20030
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Add tier", onClick: onAddTier, disabled: collectionCount >= 4, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Plus, { size: 16, style: { pointerEvents: "none" } }) }),
20031
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Remove tier", onClick: onRemoveTier, disabled: collectionCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Minus, { size: 16, style: { pointerEvents: "none" } }) })
19841
20032
  ]
19842
20033
  }
19843
20034
  ),
19844
- error && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
20035
+ error && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19845
20036
  ]
19846
20037
  }
19847
20038
  ) });
@@ -19875,7 +20066,7 @@ function AiPricingCardOverlay({
19875
20066
  const overEditable = Boolean(t?.closest?.("[data-ohw-editable], [data-ohw-role], a, button"));
19876
20067
  setHover((prev) => {
19877
20068
  const next = ref && !overEditable ? ref : null;
19878
- return sameRef(prev, next) ? prev : next;
20069
+ return sameRef2(prev, next) ? prev : next;
19879
20070
  });
19880
20071
  };
19881
20072
  const onLeave = () => setHover(null);
@@ -19890,10 +20081,10 @@ function AiPricingCardOverlay({
19890
20081
  const onClick = (e) => {
19891
20082
  const t = e.target;
19892
20083
  if (!(t instanceof Element)) return;
19893
- if (t.closest(CHROME_SELECTOR)) return;
20084
+ if (t.closest(CHROME_SELECTOR2)) return;
19894
20085
  const ref = cardRefFromTarget(t);
19895
- setActive((prev) => sameRef(prev, ref) ? prev : ref);
19896
- if (!ref || !sameRef(activeRef.current, ref)) setError(null);
20086
+ setActive((prev) => sameRef2(prev, ref) ? prev : ref);
20087
+ if (!ref || !sameRef2(activeRef.current, ref)) setError(null);
19897
20088
  };
19898
20089
  document.addEventListener("click", onClick, true);
19899
20090
  return () => document.removeEventListener("click", onClick, true);
@@ -19958,12 +20149,12 @@ function AiPricingCardOverlay({
19958
20149
  const activeEntry = active ? findEntry(active.sectionId) : null;
19959
20150
  const activeCardRect = useLiveCardRect(active);
19960
20151
  const activeGridRect = useLiveCardRect(active, true);
19961
- const hoverRect = useLiveCardRect(hover && !sameRef(hover, active) ? hover : null);
19962
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19963
- hoverRect && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19964
- activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19965
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
19966
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
20152
+ const hoverRect = useLiveCardRect(hover && !sameRef2(hover, active) ? hover : null);
20153
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
20154
+ hoverRect && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
20155
+ activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
20156
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
20157
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19967
20158
  PricingCardToolbar,
19968
20159
  {
19969
20160
  entry: activeEntry,
@@ -19991,7 +20182,7 @@ function AiPricingCardOverlay({
19991
20182
  onRemoveFeature: () => runMutation(active.sectionId, (tree) => removePricingCardFeature(tree, active.index))
19992
20183
  }
19993
20184
  ) }),
19994
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
20185
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19995
20186
  "div",
19996
20187
  {
19997
20188
  "data-ohw-bridge": "",
@@ -20013,7 +20204,7 @@ function AiPricingCardOverlay({
20013
20204
 
20014
20205
  // src/ui/ai-section/AiVideoOverlay.tsx
20015
20206
  var import_react12 = require("react");
20016
- var import_lucide_react10 = require("lucide-react");
20207
+ var import_lucide_react11 = require("lucide-react");
20017
20208
 
20018
20209
  // src/lib/ai-video-tree.ts
20019
20210
  function resolveVideoBlock(tree, path) {
@@ -20084,10 +20275,10 @@ function extractYouTubeId(input) {
20084
20275
  }
20085
20276
 
20086
20277
  // src/ui/ai-section/AiVideoOverlay.tsx
20087
- var import_jsx_runtime22 = require("react/jsx-runtime");
20278
+ var import_jsx_runtime23 = require("react/jsx-runtime");
20088
20279
  var PRIMARY5 = "#0885FE";
20089
- var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY5})`;
20090
- var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
20280
+ var OHW_BLUE = `var(--ohw-primary, ${PRIMARY5})`;
20281
+ var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
20091
20282
  function findVideoBlockElement(sectionId, path) {
20092
20283
  const sectionEl = findByInstanceId(sectionId);
20093
20284
  if (!sectionEl) return null;
@@ -20147,12 +20338,12 @@ function requestValidateTree3(postToParent2, tree) {
20147
20338
  postToParent2({ type: "ow:ai-validate-tree-request", id, tree });
20148
20339
  });
20149
20340
  }
20150
- function Pill2({
20341
+ function Pill({
20151
20342
  label,
20152
20343
  onClick,
20153
20344
  disabled
20154
20345
  }) {
20155
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20346
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20156
20347
  "button",
20157
20348
  {
20158
20349
  type: "button",
@@ -20168,7 +20359,7 @@ function Pill2({
20168
20359
  border: "none",
20169
20360
  cursor: disabled ? "default" : "pointer",
20170
20361
  background: "#ffffff",
20171
- color: OHW_BLUE_DARK2,
20362
+ color: OHW_BLUE_DARK,
20172
20363
  opacity: disabled ? 0.4 : 1,
20173
20364
  fontSize: 12,
20174
20365
  lineHeight: "16px",
@@ -20190,7 +20381,7 @@ function VideoUrlInput({ value, onCommit }) {
20190
20381
  setDraft(value ? watchUrl(value) : "");
20191
20382
  setInvalid(false);
20192
20383
  }, [value]);
20193
- const commit = () => {
20384
+ const commit2 = () => {
20194
20385
  const next = draft.trim();
20195
20386
  if (!next) {
20196
20387
  setInvalid(false);
@@ -20211,8 +20402,8 @@ function VideoUrlInput({ value, onCommit }) {
20211
20402
  onCommit(id);
20212
20403
  }
20213
20404
  };
20214
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
20215
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
20405
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
20406
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20216
20407
  "div",
20217
20408
  {
20218
20409
  style: {
@@ -20228,12 +20419,12 @@ function VideoUrlInput({ value, onCommit }) {
20228
20419
  boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
20229
20420
  },
20230
20421
  children: [
20231
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20422
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20232
20423
  "span",
20233
20424
  {
20234
20425
  style: {
20235
20426
  flex: "0 0 auto",
20236
- color: OHW_BLUE_DARK2,
20427
+ color: OHW_BLUE_DARK,
20237
20428
  fontSize: 10,
20238
20429
  lineHeight: "16px",
20239
20430
  fontWeight: 700,
@@ -20243,14 +20434,14 @@ function VideoUrlInput({ value, onCommit }) {
20243
20434
  children: "Video"
20244
20435
  }
20245
20436
  ),
20246
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20437
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20247
20438
  "input",
20248
20439
  {
20249
20440
  type: "text",
20250
20441
  inputMode: "url",
20251
20442
  value: draft,
20252
20443
  onChange: (e) => handleChange(e.target.value),
20253
- onBlur: commit,
20444
+ onBlur: commit2,
20254
20445
  onKeyDown: (e) => {
20255
20446
  if (e.key === "Enter") e.currentTarget.blur();
20256
20447
  if (e.key === "Escape") {
@@ -20280,7 +20471,7 @@ function VideoUrlInput({ value, onCommit }) {
20280
20471
  ]
20281
20472
  }
20282
20473
  ),
20283
- invalid && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, textAlign: "center" }, children: "That doesn't look like a YouTube link \u2014 try pasting the video URL again." })
20474
+ invalid && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, textAlign: "center" }, children: "That doesn't look like a YouTube link \u2014 try pasting the video URL again." })
20284
20475
  ] });
20285
20476
  }
20286
20477
  function VideoToolbar({
@@ -20290,7 +20481,7 @@ function VideoToolbar({
20290
20481
  onSetVideoId,
20291
20482
  onRemove
20292
20483
  }) {
20293
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
20484
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20294
20485
  "div",
20295
20486
  {
20296
20487
  "data-ohw-bridge": "",
@@ -20302,7 +20493,7 @@ function VideoToolbar({
20302
20493
  gap: 6,
20303
20494
  alignItems: "center",
20304
20495
  width: 280,
20305
- background: OHW_BLUE2,
20496
+ background: OHW_BLUE,
20306
20497
  padding: 8,
20307
20498
  borderRadius: 12,
20308
20499
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -20310,9 +20501,9 @@ function VideoToolbar({
20310
20501
  opacity: pending ? 0.6 : 1
20311
20502
  },
20312
20503
  children: [
20313
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
20314
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Pill2, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
20315
- error && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20504
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
20505
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Pill, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
20506
+ error && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20316
20507
  ]
20317
20508
  }
20318
20509
  );
@@ -20432,8 +20623,8 @@ function AiVideoOverlay({
20432
20623
  const activeBlockRect = useLiveBlockRect(active);
20433
20624
  const TOOLBAR_APPROX_H = 90;
20434
20625
  const toolbarState = activeBlockRect && activeBlockRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
20435
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
20436
- hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20626
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
20627
+ hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20437
20628
  "button",
20438
20629
  {
20439
20630
  type: "button",
@@ -20461,15 +20652,15 @@ function AiVideoOverlay({
20461
20652
  fontSize: 14,
20462
20653
  fontWeight: 700
20463
20654
  },
20464
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Star, { size: 14, style: { pointerEvents: "none" } })
20655
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.Star, { size: 14, style: { pointerEvents: "none" } })
20465
20656
  }
20466
20657
  ),
20467
- activeBlockRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20658
+ activeBlockRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20468
20659
  ItemInteractionLayer,
20469
20660
  {
20470
20661
  rect: activeBlockRect,
20471
20662
  state: toolbarState,
20472
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20663
+ toolbar: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20473
20664
  VideoToolbar,
20474
20665
  {
20475
20666
  videoId: videoIdAt(activeEntry.tree, active.path),
@@ -20493,7 +20684,7 @@ function AiVideoOverlay({
20493
20684
 
20494
20685
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
20495
20686
  var import_react13 = require("react");
20496
- var import_lucide_react11 = require("lucide-react");
20687
+ var import_lucide_react12 = require("lucide-react");
20497
20688
 
20498
20689
  // src/lib/ai-carousel-tree.ts
20499
20690
  function isCarouselBlock(node) {
@@ -20544,10 +20735,10 @@ function setCarouselAutoplay(tree, on) {
20544
20735
  }
20545
20736
 
20546
20737
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
20547
- var import_jsx_runtime23 = require("react/jsx-runtime");
20738
+ var import_jsx_runtime24 = require("react/jsx-runtime");
20548
20739
  var PRIMARY6 = "#0885FE";
20549
- var OHW_BLUE3 = `var(--ohw-primary, ${PRIMARY6})`;
20550
- var OHW_BLUE_DARK3 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
20740
+ var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY6})`;
20741
+ var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
20551
20742
  function findCarouselItemElement(sectionId, index) {
20552
20743
  const sectionEl = findByInstanceId(sectionId);
20553
20744
  if (!sectionEl) return null;
@@ -20626,8 +20817,8 @@ function requestAssetPick2(postToParent2) {
20626
20817
  postToParent2({ type: "ow:asset-pick", id });
20627
20818
  });
20628
20819
  }
20629
- function Pill3({ label, onClick, disabled }) {
20630
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20820
+ function Pill2({ label, onClick, disabled }) {
20821
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20631
20822
  "button",
20632
20823
  {
20633
20824
  type: "button",
@@ -20643,7 +20834,7 @@ function Pill3({ label, onClick, disabled }) {
20643
20834
  border: "none",
20644
20835
  cursor: disabled ? "default" : "pointer",
20645
20836
  background: "#ffffff",
20646
- color: OHW_BLUE_DARK3,
20837
+ color: OHW_BLUE_DARK2,
20647
20838
  opacity: disabled ? 0.4 : 1,
20648
20839
  fontSize: 12,
20649
20840
  lineHeight: "16px",
@@ -20661,7 +20852,7 @@ function TogglePill({
20661
20852
  onToggle,
20662
20853
  disabled
20663
20854
  }) {
20664
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20855
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
20665
20856
  "button",
20666
20857
  {
20667
20858
  type: "button",
@@ -20680,7 +20871,7 @@ function TogglePill({
20680
20871
  border: "none",
20681
20872
  cursor: disabled ? "default" : "pointer",
20682
20873
  background: "#ffffff",
20683
- color: OHW_BLUE_DARK3,
20874
+ color: OHW_BLUE_DARK2,
20684
20875
  opacity: disabled ? 0.4 : 1,
20685
20876
  fontSize: 12,
20686
20877
  lineHeight: "16px",
@@ -20690,7 +20881,7 @@ function TogglePill({
20690
20881
  },
20691
20882
  children: [
20692
20883
  label,
20693
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20884
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20694
20885
  "span",
20695
20886
  {
20696
20887
  "aria-hidden": true,
@@ -20703,10 +20894,10 @@ function TogglePill({
20703
20894
  // Off is a grey track, not a washed-out blue: the pill's own white background already
20704
20895
  // carries the brand colour in the text, and a pale blue track next to it read as
20705
20896
  // "disabled control" rather than "switch that is off".
20706
- background: on ? OHW_BLUE_DARK3 : "#CBD5E1",
20897
+ background: on ? OHW_BLUE_DARK2 : "#CBD5E1",
20707
20898
  transition: "background 120ms ease"
20708
20899
  },
20709
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20900
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20710
20901
  "span",
20711
20902
  {
20712
20903
  style: {
@@ -20735,7 +20926,7 @@ function CarouselSlideToolbar({
20735
20926
  onChangeImage,
20736
20927
  onToggleAutoplay
20737
20928
  }) {
20738
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20929
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
20739
20930
  "div",
20740
20931
  {
20741
20932
  "data-ohw-bridge": "",
@@ -20746,7 +20937,7 @@ function CarouselSlideToolbar({
20746
20937
  flexDirection: "column",
20747
20938
  gap: 6,
20748
20939
  alignItems: "center",
20749
- background: OHW_BLUE3,
20940
+ background: OHW_BLUE2,
20750
20941
  padding: 8,
20751
20942
  borderRadius: 12,
20752
20943
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -20758,11 +20949,11 @@ function CarouselSlideToolbar({
20758
20949
  pointerEvents: pending ? "none" : "auto"
20759
20950
  },
20760
20951
  children: [
20761
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20762
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Pill3, { label: "Change Image", onClick: onChangeImage }),
20763
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
20952
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20953
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(Pill2, { label: "Change Image", onClick: onChangeImage }),
20954
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
20764
20955
  ] }),
20765
- error && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20956
+ error && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20766
20957
  ]
20767
20958
  }
20768
20959
  );
@@ -20902,8 +21093,8 @@ function AiFadeCarouselOverlay({
20902
21093
  const activeItemRect = useLiveItemRect2(active);
20903
21094
  const TOOLBAR_APPROX_H = 90;
20904
21095
  const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
20905
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
20906
- hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
21096
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
21097
+ hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20907
21098
  "button",
20908
21099
  {
20909
21100
  type: "button",
@@ -20932,10 +21123,10 @@ function AiFadeCarouselOverlay({
20932
21123
  fontSize: 14,
20933
21124
  fontWeight: 700
20934
21125
  },
20935
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.Star, { size: 14, style: { pointerEvents: "none" } })
21126
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react12.Star, { size: 14, style: { pointerEvents: "none" } })
20936
21127
  }
20937
21128
  ),
20938
- swapping && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
21129
+ swapping && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20939
21130
  "div",
20940
21131
  {
20941
21132
  ref: skeletonRef,
@@ -20953,15 +21144,15 @@ function AiFadeCarouselOverlay({
20953
21144
  zIndex: 2147483646,
20954
21145
  pointerEvents: "none"
20955
21146
  },
20956
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("style", { children: MEDIA_SKELETON_CSS })
21147
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("style", { children: MEDIA_SKELETON_CSS })
20957
21148
  }
20958
21149
  ),
20959
- activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
21150
+ activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20960
21151
  ItemInteractionLayer,
20961
21152
  {
20962
21153
  rect: activeItemRect,
20963
21154
  state: toolbarState,
20964
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
21155
+ toolbar: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20965
21156
  CarouselSlideToolbar,
20966
21157
  {
20967
21158
  pending,
@@ -21179,113 +21370,6 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
21179
21370
  // src/ui/link-modal/LinkPopover.tsx
21180
21371
  var import_react17 = require("react");
21181
21372
 
21182
- // src/ui/dialog.tsx
21183
- var React9 = __toESM(require("react"), 1);
21184
- var import_radix_ui6 = require("radix-ui");
21185
- var import_lucide_react12 = require("lucide-react");
21186
- var import_jsx_runtime24 = require("react/jsx-runtime");
21187
- function Dialog2({
21188
- ...props
21189
- }) {
21190
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_radix_ui6.Dialog.Root, { "data-slot": "dialog", ...props });
21191
- }
21192
- function DialogPortal({
21193
- ...props
21194
- }) {
21195
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_radix_ui6.Dialog.Portal, { ...props });
21196
- }
21197
- function DialogOverlay({
21198
- className,
21199
- ...props
21200
- }) {
21201
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21202
- import_radix_ui6.Dialog.Overlay,
21203
- {
21204
- "data-slot": "dialog-overlay",
21205
- "data-ohw-link-modal-root": "",
21206
- className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
21207
- ...props
21208
- }
21209
- );
21210
- }
21211
- var DialogContent = React9.forwardRef(
21212
- ({ className, children, showCloseButton = true, container, ...props }, ref) => {
21213
- const positionMode = container ? "absolute" : "fixed";
21214
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(DialogPortal, { container: container ?? void 0, children: [
21215
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
21216
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
21217
- import_radix_ui6.Dialog.Content,
21218
- {
21219
- ref,
21220
- "data-slot": "dialog-content",
21221
- className: cn(
21222
- positionMode,
21223
- "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",
21224
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
21225
- container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
21226
- className
21227
- ),
21228
- onMouseDown: (e) => e.stopPropagation(),
21229
- ...props,
21230
- children: [
21231
- children,
21232
- showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21233
- import_radix_ui6.Dialog.Close,
21234
- {
21235
- type: "button",
21236
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
21237
- "aria-label": "Close",
21238
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react12.X, { size: 16, "aria-hidden": true })
21239
- }
21240
- ) : null
21241
- ]
21242
- }
21243
- )
21244
- ] });
21245
- }
21246
- );
21247
- DialogContent.displayName = import_radix_ui6.Dialog.Content.displayName;
21248
- function DialogHeader({
21249
- className,
21250
- ...props
21251
- }) {
21252
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
21253
- }
21254
- function DialogFooter({
21255
- className,
21256
- ...props
21257
- }) {
21258
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21259
- "div",
21260
- {
21261
- className: cn("flex items-center justify-end gap-2", className),
21262
- ...props
21263
- }
21264
- );
21265
- }
21266
- var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21267
- import_radix_ui6.Dialog.Title,
21268
- {
21269
- ref,
21270
- className: cn(
21271
- "text-lg font-semibold leading-none tracking-tight text-card-foreground",
21272
- className
21273
- ),
21274
- ...props
21275
- }
21276
- ));
21277
- DialogTitle.displayName = import_radix_ui6.Dialog.Title.displayName;
21278
- var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21279
- import_radix_ui6.Dialog.Description,
21280
- {
21281
- ref,
21282
- className: cn("text-sm text-muted-foreground", className),
21283
- ...props
21284
- }
21285
- ));
21286
- DialogDescription.displayName = import_radix_ui6.Dialog.Description.displayName;
21287
- var DialogClose = import_radix_ui6.Dialog.Close;
21288
-
21289
21373
  // src/ui/link-modal/LinkEditorPanel.tsx
21290
21374
  var import_lucide_react16 = require("lucide-react");
21291
21375
 
@@ -22389,6 +22473,7 @@ function shouldUseDevFixtures() {
22389
22473
  function getLogoElement(el) {
22390
22474
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
22391
22475
  if (marked) return marked;
22476
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
22392
22477
  const root = el.closest("nav, [data-ohw-nav-root], footer");
22393
22478
  if (!root) return null;
22394
22479
  const anchor = el.closest("a");
@@ -24083,6 +24168,102 @@ function deleteSelectedNavFooterItem(deps) {
24083
24168
  return false;
24084
24169
  }
24085
24170
 
24171
+ // src/lib/nav-page-sync.ts
24172
+ function labelKeyFor(hrefKey) {
24173
+ return hrefKey.replace(/-href$/, "-label");
24174
+ }
24175
+ function isPageLinkKey(key) {
24176
+ return isNavbarHrefKey(key) || isFooterHrefKey(key);
24177
+ }
24178
+ function currentHref(key, anchor, content) {
24179
+ const stored = content[key];
24180
+ if (typeof stored === "string" && stored.length > 0) return stored;
24181
+ return anchor.getAttribute("href") ?? "";
24182
+ }
24183
+ function normalizePath2(value) {
24184
+ if (value.length > 1 && value.endsWith("/")) return value.slice(0, -1);
24185
+ return value;
24186
+ }
24187
+ function pointsAt(href, path) {
24188
+ const normalized = normalizePath2(href);
24189
+ const target = normalizePath2(path);
24190
+ if (normalized === target) return true;
24191
+ return (normalized.startsWith(`${target}#`) || normalized.startsWith(`${target}?`)) && target !== "/";
24192
+ }
24193
+ function retargetHref(href, oldPath, newPath) {
24194
+ const normalized = normalizePath2(href);
24195
+ const target = normalizePath2(oldPath);
24196
+ if (normalized === target) return newPath;
24197
+ return `${newPath}${normalized.slice(target.length)}`;
24198
+ }
24199
+ function setLabelText(key, text) {
24200
+ document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`).forEach((el) => {
24201
+ el.textContent = text;
24202
+ });
24203
+ }
24204
+ function commit(delta, deps) {
24205
+ if (Object.keys(delta).length === 0) return;
24206
+ deps.setEditContent({ ...deps.getEditContent(), ...delta });
24207
+ deps.postToParent({
24208
+ type: "ow:change",
24209
+ nodes: Object.entries(delta).map(([key, text]) => ({ key, text }))
24210
+ });
24211
+ }
24212
+ function navLinksTo(path, content) {
24213
+ return Array.from(document.querySelectorAll("[data-ohw-href-key]")).filter((anchor) => {
24214
+ const key = anchor.getAttribute("data-ohw-href-key");
24215
+ return isNavbarHrefKey(key) && pointsAt(currentHref(key, anchor, content), path);
24216
+ }).map((anchor) => anchor.getAttribute("data-ohw-href-key"));
24217
+ }
24218
+ function addNavLinkForPage(page, deps) {
24219
+ const content = deps.getEditContent();
24220
+ if (navLinksTo(page.path, content).length > 0) return false;
24221
+ const roots = listNavbarRootItems();
24222
+ const afterAnchor = roots[roots.length - 1] ?? null;
24223
+ let inserted;
24224
+ try {
24225
+ inserted = insertNavbarItem(page.path, page.title, afterAnchor);
24226
+ } catch {
24227
+ return false;
24228
+ }
24229
+ const { hrefKey, labelKey, index } = inserted;
24230
+ deps.applyLinkByKey(hrefKey, page.path);
24231
+ setLabelText(labelKey, page.title);
24232
+ commit(
24233
+ {
24234
+ [hrefKey]: page.path,
24235
+ [labelKey]: page.title,
24236
+ [NAV_COUNT_KEY]: String(index + 1),
24237
+ [NAV_ORDER_KEY]: getNavbarOrderJson()
24238
+ },
24239
+ deps
24240
+ );
24241
+ return true;
24242
+ }
24243
+ function retargetPageLinks(change, deps) {
24244
+ const { oldPath, newPath, oldTitle, newTitle } = change;
24245
+ if (!oldPath || !newPath || normalizePath2(oldPath) === normalizePath2(newPath)) return 0;
24246
+ const content = deps.getEditContent();
24247
+ const delta = {};
24248
+ for (const anchor of Array.from(document.querySelectorAll("[data-ohw-href-key]"))) {
24249
+ const hrefKey = anchor.getAttribute("data-ohw-href-key");
24250
+ if (!isPageLinkKey(hrefKey)) continue;
24251
+ const href = currentHref(hrefKey, anchor, content);
24252
+ if (!pointsAt(href, oldPath)) continue;
24253
+ const nextHref = retargetHref(href, oldPath, newPath);
24254
+ deps.applyLinkByKey(hrefKey, nextHref);
24255
+ delta[hrefKey] = nextHref;
24256
+ if (!oldTitle || !newTitle) continue;
24257
+ const labelKey = labelKeyFor(hrefKey);
24258
+ const label = content[labelKey] ?? anchor.querySelector('[data-ohw-editable="text"]')?.textContent ?? "";
24259
+ if (label.trim() !== oldTitle) continue;
24260
+ setLabelText(labelKey, newTitle);
24261
+ delta[labelKey] = newTitle;
24262
+ }
24263
+ commit(delta, deps);
24264
+ return Object.keys(delta).filter((key) => key.endsWith("-href")).length;
24265
+ }
24266
+
24086
24267
  // src/ui/navbar-container-chrome.tsx
24087
24268
  var import_lucide_react21 = require("lucide-react");
24088
24269
  var import_jsx_runtime37 = require("react/jsx-runtime");
@@ -24970,7 +25151,7 @@ function getNavigationSelectionParent(el) {
24970
25151
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
24971
25152
  return resolveFooterLinksContainerFrom(el);
24972
25153
  }
24973
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
25154
+ 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)) {
24974
25155
  return getNavigationRoot(el);
24975
25156
  }
24976
25157
  return null;
@@ -25192,7 +25373,6 @@ var ICONS = {
25192
25373
  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"/>',
25193
25374
  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"/>'
25194
25375
  };
25195
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
25196
25376
  var SELECTION_CHROME_GAP2 = 4;
25197
25377
  var TOOLBAR_STROKE_GAP2 = 4;
25198
25378
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -25290,7 +25470,7 @@ function applyVisibleViewport(el, parentScroll) {
25290
25470
  el.style.top = `${top}px`;
25291
25471
  el.style.height = `${height}px`;
25292
25472
  }
25293
- var TOOLBAR_EDGE_MARGIN3 = 4;
25473
+ var TOOLBAR_EDGE_MARGIN4 = 4;
25294
25474
  function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
25295
25475
  const isAbove = transform.includes("translateY(-100%)");
25296
25476
  let visualTop = isAbove ? top - approxH : top;
@@ -25317,8 +25497,8 @@ function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
25317
25497
  function clampToolbarLeft(centerX, toolbarWidth) {
25318
25498
  const half = toolbarWidth / 2;
25319
25499
  return Math.max(
25320
- TOOLBAR_EDGE_MARGIN3 + half,
25321
- Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half)
25500
+ TOOLBAR_EDGE_MARGIN4 + half,
25501
+ Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN4 - half)
25322
25502
  );
25323
25503
  }
25324
25504
  function calcToolbarPos(rect, parentScroll, toolbarW = 306) {
@@ -26198,21 +26378,6 @@ function OhhwellsBridge() {
26198
26378
  const [isItemDragging, setIsItemDragging] = (0, import_react21.useState)(false);
26199
26379
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react21.useState)(false);
26200
26380
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
26201
- const [floatingPanel, setFloatingPanel] = (0, import_react21.useState)(null);
26202
- const floatingPanelOpenRef = (0, import_react21.useRef)(false);
26203
- floatingPanelOpenRef.current = floatingPanel !== null;
26204
- const floatingPanelRef = (0, import_react21.useRef)(null);
26205
- floatingPanelRef.current = floatingPanel;
26206
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react21.useState)(null);
26207
- const buttonCollectionDragRef = (0, import_react21.useRef)(null);
26208
- const buttonDropInsertIndexRef = (0, import_react21.useRef)(null);
26209
- const [buttonDropSlots, setButtonDropSlots] = (0, import_react21.useState)([]);
26210
- const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react21.useState)(null);
26211
- const buttonPointerDragRef = (0, import_react21.useRef)(null);
26212
- const [buttonStyleTick, setButtonStyleTick] = (0, import_react21.useState)(0);
26213
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react21.useState)(null);
26214
- const [editorViewport, setEditorViewport] = (0, import_react21.useState)("desktop");
26215
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react21.useState)(null);
26216
26381
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react21.useState)(null);
26217
26382
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react21.useState)(null);
26218
26383
  const footerDragRef = (0, import_react21.useRef)(null);
@@ -26238,6 +26403,21 @@ function OhhwellsBridge() {
26238
26403
  const stylesRef = (0, import_react21.useRef)("");
26239
26404
  const appliedDrawerRef = (0, import_react21.useRef)(null);
26240
26405
  const pendingDeleteUndoRef = (0, import_react21.useRef)(null);
26406
+ const [floatingPanel, setFloatingPanel] = (0, import_react21.useState)(null);
26407
+ const floatingPanelOpenRef = (0, import_react21.useRef)(false);
26408
+ const floatingPanelRef = (0, import_react21.useRef)(null);
26409
+ floatingPanelRef.current = floatingPanel;
26410
+ const setFloatingPanelRef = (0, import_react21.useRef)(setFloatingPanel);
26411
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react21.useState)(null);
26412
+ const buttonCollectionDragRef = (0, import_react21.useRef)(null);
26413
+ const buttonDropInsertIndexRef = (0, import_react21.useRef)(null);
26414
+ const [buttonDropSlots, setButtonDropSlots] = (0, import_react21.useState)([]);
26415
+ const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react21.useState)(null);
26416
+ const buttonPointerDragRef = (0, import_react21.useRef)(null);
26417
+ const [buttonStyleTick, setButtonStyleTick] = (0, import_react21.useState)(0);
26418
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react21.useState)(null);
26419
+ const [editorViewport, setEditorViewport] = (0, import_react21.useState)("desktop");
26420
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react21.useState)(null);
26241
26421
  const [sitePages, setSitePages] = (0, import_react21.useState)([]);
26242
26422
  const collectionItemsRef = (0, import_react21.useRef)(/* @__PURE__ */ new Map());
26243
26423
  const [sectionsByPath, setSectionsByPath] = (0, import_react21.useState)({});
@@ -26247,7 +26427,18 @@ function OhhwellsBridge() {
26247
26427
  const linkPopoverOpenRef = (0, import_react21.useRef)(false);
26248
26428
  const linkPopoverGraceUntilRef = (0, import_react21.useRef)(0);
26249
26429
  setLinkPopoverRef.current = setLinkPopover;
26430
+ setFloatingPanelRef.current = setFloatingPanel;
26250
26431
  linkPopoverSessionRef.current = linkPopover;
26432
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
26433
+ (0, import_react21.useEffect)(() => {
26434
+ const syncViewport = () => {
26435
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
26436
+ setEditorViewport((prev) => prev === next ? prev : next);
26437
+ };
26438
+ syncViewport();
26439
+ window.addEventListener("resize", syncViewport);
26440
+ return () => window.removeEventListener("resize", syncViewport);
26441
+ }, []);
26251
26442
  (0, import_react21.useEffect)(() => {
26252
26443
  setSectionOrderPersistHandler((entries) => {
26253
26444
  const currentPath = window.location.pathname;
@@ -26396,6 +26587,43 @@ function OhhwellsBridge() {
26396
26587
  if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
26397
26588
  return () => window.removeEventListener("message", onSitePages);
26398
26589
  }, [isEditMode, postToParent2]);
26590
+ (0, import_react21.useEffect)(() => {
26591
+ if (!isEditMode) return;
26592
+ const deps = {
26593
+ getEditContent: () => editContentRef.current,
26594
+ setEditContent: (next) => {
26595
+ editContentRef.current = next;
26596
+ },
26597
+ applyLinkByKey,
26598
+ postToParent: postToParent2
26599
+ };
26600
+ const onPageMessage = (e) => {
26601
+ if (e.data?.type === "ow:page-created") {
26602
+ const { path, title } = e.data;
26603
+ if (typeof path !== "string" || !path) return;
26604
+ if (addNavLinkForPage({ path, title: typeof title === "string" && title ? title : path }, deps)) {
26605
+ enforceLinkHrefs();
26606
+ }
26607
+ return;
26608
+ }
26609
+ if (e.data?.type === "ow:page-renamed") {
26610
+ const { oldPath, newPath, oldTitle, newTitle } = e.data;
26611
+ if (typeof oldPath !== "string" || typeof newPath !== "string") return;
26612
+ const moved = retargetPageLinks(
26613
+ {
26614
+ oldPath,
26615
+ newPath,
26616
+ oldTitle: typeof oldTitle === "string" ? oldTitle : void 0,
26617
+ newTitle: typeof newTitle === "string" ? newTitle : void 0
26618
+ },
26619
+ deps
26620
+ );
26621
+ if (moved > 0) enforceLinkHrefs();
26622
+ }
26623
+ };
26624
+ window.addEventListener("message", onPageMessage);
26625
+ return () => window.removeEventListener("message", onPageMessage);
26626
+ }, [isEditMode, postToParent2]);
26399
26627
  (0, import_react21.useEffect)(() => {
26400
26628
  if (!isEditMode || shouldUseDevFixtures()) return;
26401
26629
  void loadAllSectionsManifest().then((manifest) => {
@@ -27944,6 +28172,7 @@ function OhhwellsBridge() {
27944
28172
  applyAiFooterFromContent(content);
27945
28173
  if (typeof content[STYLE_STORE_KEY] === "string") {
27946
28174
  stylesRef.current = content[STYLE_STORE_KEY];
28175
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
27947
28176
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
27948
28177
  }
27949
28178
  applyBrandChrome(content);
@@ -27954,12 +28183,12 @@ function OhhwellsBridge() {
27954
28183
  if (key === "__ohw_sections") continue;
27955
28184
  if (key === AI_SECTIONS_KEY) continue;
27956
28185
  if (key === AI_FOOTER_KEY) continue;
28186
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
28187
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
27957
28188
  if (key === BRAND_KIT_KEY) continue;
27958
28189
  if (key === STYLE_STORE_KEY) continue;
27959
28190
  if (BRAND_CHROME_KEYS.has(key)) continue;
27960
- if (key === LOGO_PLACEHOLDER_KEY) continue;
27961
28191
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
27962
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
27963
28192
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
27964
28193
  continue;
27965
28194
  }
@@ -28190,17 +28419,18 @@ function OhhwellsBridge() {
28190
28419
  }
28191
28420
  applyAiFooterFromContent(content);
28192
28421
  if (typeof content[STYLE_STORE_KEY] === "string") {
28422
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
28193
28423
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
28194
28424
  }
28195
28425
  for (const [key, val] of Object.entries(content)) {
28196
28426
  if (key === "__ohw_sections") continue;
28197
28427
  if (key === AI_SECTIONS_KEY) continue;
28198
28428
  if (key === AI_FOOTER_KEY) continue;
28429
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
28430
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
28199
28431
  if (key === BRAND_KIT_KEY) continue;
28200
28432
  if (key === STYLE_STORE_KEY) continue;
28201
28433
  if (BRAND_CHROME_KEYS.has(key)) continue;
28202
- if (key === LOGO_PLACEHOLDER_KEY) continue;
28203
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
28204
28434
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
28205
28435
  continue;
28206
28436
  }
@@ -28467,26 +28697,11 @@ function OhhwellsBridge() {
28467
28697
  const t2 = setTimeout(measure, 500);
28468
28698
  const ro = new ResizeObserver(schedule);
28469
28699
  ro.observe(document.body);
28470
- let lastWidth = window.innerWidth;
28471
- let resizeTimers = [];
28472
- const clearResizeTimers = () => {
28473
- resizeTimers.forEach(clearTimeout);
28474
- resizeTimers = [];
28475
- };
28476
- const handleResize = () => {
28477
- if (window.innerWidth === lastWidth) return;
28478
- lastWidth = window.innerWidth;
28479
- clearResizeTimers();
28480
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
28481
- };
28482
- window.addEventListener("resize", handleResize);
28483
28700
  return () => {
28484
28701
  clearTimeout(t1);
28485
28702
  clearTimeout(t2);
28486
28703
  if (raf != null) cancelAnimationFrame(raf);
28487
28704
  ro.disconnect();
28488
- clearResizeTimers();
28489
- window.removeEventListener("resize", handleResize);
28490
28705
  };
28491
28706
  }, [pathname, isEditMode, postToParent2]);
28492
28707
  (0, import_react21.useEffect)(() => {
@@ -28783,6 +28998,13 @@ function OhhwellsBridge() {
28783
28998
  if (target.closest("[data-ohw-state-toggle]")) return;
28784
28999
  if (target.closest("[data-ohw-max-badge]")) return;
28785
29000
  if (isInsideLinkEditor(target)) return;
29001
+ if (isInsideFloatingPanel(target)) return;
29002
+ if (target.closest("[data-ohw-form-toolbar]")) return;
29003
+ if (target.closest(
29004
+ '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
29005
+ )) {
29006
+ return;
29007
+ }
28786
29008
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
28787
29009
  clearMediaSelectionRef.current();
28788
29010
  }
@@ -28806,8 +29028,6 @@ function OhhwellsBridge() {
28806
29028
  return;
28807
29029
  }
28808
29030
  }
28809
- if (isInsideFloatingPanel(target)) return;
28810
- if (target.closest("[data-ohw-form-toolbar]")) return;
28811
29031
  const collectionItem = matchCollectionItemClick(target, collectionItemsRef.current);
28812
29032
  if (collectionItem) {
28813
29033
  e.preventDefault();
@@ -28819,11 +29039,6 @@ function OhhwellsBridge() {
28819
29039
  });
28820
29040
  return;
28821
29041
  }
28822
- if (target.closest(
28823
- '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
28824
- )) {
28825
- return;
28826
- }
28827
29042
  {
28828
29043
  const formEl = getFormElement(target);
28829
29044
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -28977,9 +29192,13 @@ function OhhwellsBridge() {
28977
29192
  if (isIconEditable(editable) && !getSocialItem(editable)) {
28978
29193
  e.preventDefault();
28979
29194
  e.stopPropagation();
28980
- selectIconElementRef.current(editable);
28981
29195
  const aiIcon = editable.hasAttribute("data-ai-icon");
28982
29196
  const featureIcon = aiIcon && /\.features\.\d+\.icon$/.test(editable.dataset.ohwKey ?? "");
29197
+ if (aiIcon) {
29198
+ aiSectionApiRef.current?.selectFromElement(editable);
29199
+ } else {
29200
+ selectIconElementRef.current(editable);
29201
+ }
28983
29202
  postToParentRef.current({
28984
29203
  type: "ow:icon-pick",
28985
29204
  key: editable.dataset.ohwKey ?? "",
@@ -29207,6 +29426,9 @@ function OhhwellsBridge() {
29207
29426
  setHoveredItemRect(null);
29208
29427
  hoveredNavContainerRef.current = null;
29209
29428
  setHoveredNavContainerRect(null);
29429
+ siblingHintElRef.current = null;
29430
+ setSiblingHintRect(null);
29431
+ setSiblingHintRects([]);
29210
29432
  return;
29211
29433
  }
29212
29434
  {
@@ -29327,7 +29549,6 @@ function OhhwellsBridge() {
29327
29549
  hoveredNavContainerRef.current = null;
29328
29550
  setHoveredNavContainerRect(null);
29329
29551
  hoveredItemElRef.current = editable;
29330
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
29331
29552
  }
29332
29553
  }
29333
29554
  }
@@ -30037,7 +30258,11 @@ function OhhwellsBridge() {
30037
30258
  };
30038
30259
  const handleMouseMove = (e) => {
30039
30260
  const { clientX, clientY } = e;
30040
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
30261
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
30262
+ dismissImageHover();
30263
+ clearImageHover();
30264
+ return;
30265
+ }
30041
30266
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) {
30042
30267
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
30043
30268
  formHoverElRef.current = null;
@@ -30066,7 +30291,12 @@ function OhhwellsBridge() {
30066
30291
  setHoveredItemRect(null);
30067
30292
  hoveredNavContainerRef.current = null;
30068
30293
  setHoveredNavContainerRect(null);
30294
+ siblingHintElRef.current = null;
30295
+ setSiblingHintRect(null);
30296
+ setSiblingHintRects([]);
30069
30297
  dismissImageHover();
30298
+ clearImageHover();
30299
+ setSectionGap(null);
30070
30300
  return;
30071
30301
  }
30072
30302
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -30078,7 +30308,11 @@ function OhhwellsBridge() {
30078
30308
  if (e.data?.type !== "ow:pointer-sync") return;
30079
30309
  const { clientX, clientY } = e.data;
30080
30310
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
30081
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
30311
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
30312
+ dismissImageHover();
30313
+ clearImageHover();
30314
+ return;
30315
+ }
30082
30316
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) return;
30083
30317
  if (probeSocialsRowAt(clientX, clientY)) return;
30084
30318
  probeSectionGapAt(clientX, clientY);
@@ -30458,6 +30692,7 @@ function OhhwellsBridge() {
30458
30692
  applyAiFooterFromContent(content);
30459
30693
  if (typeof content[STYLE_STORE_KEY] === "string") {
30460
30694
  stylesRef.current = content[STYLE_STORE_KEY];
30695
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
30461
30696
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
30462
30697
  }
30463
30698
  applyBrandChrome(content);
@@ -30472,12 +30707,12 @@ function OhhwellsBridge() {
30472
30707
  }
30473
30708
  if (key === AI_SECTIONS_KEY) continue;
30474
30709
  if (key === AI_FOOTER_KEY) continue;
30710
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
30711
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
30475
30712
  if (key === BRAND_KIT_KEY) continue;
30476
30713
  if (key === STYLE_STORE_KEY) continue;
30477
30714
  if (BRAND_CHROME_KEYS.has(key)) continue;
30478
- if (key === LOGO_PLACEHOLDER_KEY) continue;
30479
30715
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
30480
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
30481
30716
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
30482
30717
  continue;
30483
30718
  }
@@ -31113,6 +31348,12 @@ function OhhwellsBridge() {
31113
31348
  closeLinkPopoverRef.current();
31114
31349
  return;
31115
31350
  }
31351
+ if (floatingPanelOpenRef.current) {
31352
+ setFloatingPanelRef.current(null);
31353
+ deselectRef.current();
31354
+ deactivateRef.current();
31355
+ return;
31356
+ }
31116
31357
  deselectRef.current();
31117
31358
  deactivateRef.current();
31118
31359
  clearMediaSelectionRef.current();
@@ -31501,7 +31742,7 @@ function OhhwellsBridge() {
31501
31742
  const sectionEl = foundEl && !foundEl.hasAttribute("data-ohw-ai-template-hidden") && !foundEl.hasAttribute("data-ohw-ai-removed") && !foundEl.hasAttribute("data-ohw-ai-replaced-by") ? foundEl : null;
31502
31743
  const nodes = sectionEl ? collectEditableNodes(void 0, sectionEl).filter(
31503
31744
  (n) => n.key && !n.key.startsWith("__ohw_") && !["meta", "sections", "video-setting"].includes(n.type)
31504
- ).map((n) => ({ ...n, text: n.text ?? "", type: normalizeAiNodeType(n.type) })) : [];
31745
+ ).map((n) => ({ ...n, text: n.text ?? "", type: normalizeAiNodeType(n.type) })).map((n) => withNodeFontSize(n, sectionEl)) : [];
31505
31746
  const items = sectionEl ? collectItemGroups(sectionEl) : [];
31506
31747
  const box = sectionEl?.getBoundingClientRect();
31507
31748
  const rect = box ? {
@@ -31948,10 +32189,10 @@ function OhhwellsBridge() {
31948
32189
  window.removeEventListener("message", handleDuplicateSection);
31949
32190
  window.removeEventListener("message", handleDeactivate);
31950
32191
  window.removeEventListener("message", handleSelectNav);
31951
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
31952
32192
  window.removeEventListener("message", handleToastAction);
31953
32193
  window.removeEventListener("message", handleFormCount);
31954
32194
  window.removeEventListener("message", handleUiEscape);
32195
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
31955
32196
  autoSaveTimers.current.forEach(clearTimeout);
31956
32197
  autoSaveTimers.current.clear();
31957
32198
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -32245,7 +32486,7 @@ function OhhwellsBridge() {
32245
32486
  postToParent2({
32246
32487
  type: "ow:ready",
32247
32488
  version: "1",
32248
- bridgeVersion: "0.1.159",
32489
+ bridgeVersion: "0.1.160-next.427",
32249
32490
  path: pathname,
32250
32491
  nodes: collectEditableNodes(editContentRef.current),
32251
32492
  sections