@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.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/OhhwellsBridge.tsx
4
- import React12, { useCallback as useCallback12, useEffect as useEffect17, useLayoutEffect as useLayoutEffect6, useRef as useRef14, useState as useState18 } from "react";
4
+ import React12, { useCallback as useCallback12, useEffect as useEffect17, useLayoutEffect as useLayoutEffect7, useRef as useRef14, useState as useState18 } from "react";
5
5
  import { createRoot as createRoot3 } from "react-dom/client";
6
6
  import { flushSync as flushSync3 } from "react-dom";
7
7
 
@@ -6649,6 +6649,21 @@ function pricingCardFeatureCount(tree, index) {
6649
6649
  const target = findPricingCollection(tree)?.children?.[index];
6650
6650
  return target ? pricingFeatureList(target).length : 0;
6651
6651
  }
6652
+ function prevailingFeatureIcon(features) {
6653
+ const counts = /* @__PURE__ */ new Map();
6654
+ for (const row of features) {
6655
+ const key = `${row.icon ?? ""}|${row.iconColor ?? ""}`;
6656
+ const entry = counts.get(key) ?? { count: 0, ...row.icon ? { icon: row.icon } : {}, ...row.iconColor ? { iconColor: row.iconColor } : {} };
6657
+ entry.count += 1;
6658
+ counts.set(key, entry);
6659
+ }
6660
+ let best = null;
6661
+ for (const entry of counts.values()) {
6662
+ if (!best || entry.count > best.count) best = entry;
6663
+ }
6664
+ if (!best) return {};
6665
+ return { ...best.icon ? { icon: best.icon } : {}, ...best.iconColor ? { iconColor: best.iconColor } : {} };
6666
+ }
6652
6667
  function addPricingCardFeature(tree, index, text) {
6653
6668
  return withPricingCollection(tree, (collection) => {
6654
6669
  const children = collection.children;
@@ -6657,7 +6672,7 @@ function addPricingCardFeature(tree, index, text) {
6657
6672
  const features = pricingFeatureList(target);
6658
6673
  if (features.length >= PRICING_FEATURES_MAX) return false;
6659
6674
  target.slots = target.slots ?? {};
6660
- target.slots.features = [...features, { text }];
6675
+ target.slots.features = [...features, { text, ...prevailingFeatureIcon(features) }];
6661
6676
  return true;
6662
6677
  });
6663
6678
  }
@@ -7269,6 +7284,23 @@ function applyCustomCodeToHead(content) {
7269
7284
  if (beforeNodes.length > 0) document.head.append(...beforeNodes);
7270
7285
  }
7271
7286
 
7287
+ // src/lib/node-font-size.ts
7288
+ var TEXT_NODE_TYPES = /* @__PURE__ */ new Set(["text", "text/rich", "plain", "link"]);
7289
+ function escapeKey(key) {
7290
+ return typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(key) : key.replace(/["\\]/g, "\\$&");
7291
+ }
7292
+ function measureNodeFontSize(key, type, root) {
7293
+ if (!TEXT_NODE_TYPES.has(type)) return void 0;
7294
+ const el = root.querySelector(`[data-ohw-editable][data-ohw-key="${escapeKey(key)}"]`);
7295
+ if (!el) return void 0;
7296
+ const size = Number.parseFloat(window.getComputedStyle(el).fontSize);
7297
+ return Number.isFinite(size) && size > 0 ? Math.round(size) : void 0;
7298
+ }
7299
+ function withNodeFontSize(node, root) {
7300
+ const fontSize = measureNodeFontSize(node.key, node.type, root);
7301
+ return fontSize === void 0 ? node : { ...node, fontSize };
7302
+ }
7303
+
7272
7304
  // src/lib/selectNavContainer.ts
7273
7305
  function isOnScreen(el) {
7274
7306
  const rect = el.getBoundingClientRect();
@@ -7857,7 +7889,7 @@ var AI_MOBILE_CSS = [
7857
7889
  // page's first section. Top only (bottom needs no nav clearance); ~96px approximates the
7858
7890
  // template nav's own height (measured ~94px) — there's no shared `--ohw-header-h` var to read
7859
7891
  // an exact figure from (no live measurement exists), so this is a safe approximation, not a
7860
- // pixel-exact fit. Horizontal spacing already comes from `data-ai-container`.
7892
+ // pixel-exact fit. Horizontal spacing already comes from `data-ai-section-inner`.
7861
7893
  '[data-ai-overlay-clearance] [data-ai-overlap-layer="overlay"]{padding-top:96px}',
7862
7894
  // A full-bleed carousel's height is pure arithmetic: it spans the whole window, so 16/9 turns
7863
7895
  // window width straight into height. Measured at 16/9 it came out ~90% of the viewport at EVERY
@@ -7873,8 +7905,10 @@ var AI_MOBILE_CSS = [
7873
7905
  "[data-ai-section]{overflow-x:hidden}",
7874
7906
  // `:not([data-ai-full-bleed])`: this `!important` gutter beat the inline `padding: 0` that a
7875
7907
  // full-bleed section's container sets, so a carousel/media hero that is edge-to-edge on desktop
7876
- // still got a 20px band of section background down both sides on a phone.
7877
- "[data-ai-container]:not([data-ai-full-bleed]){padding:0 20px !important}",
7908
+ // still got a 20px band of section background down both sides on a phone. The footer's own
7909
+ // container keeps the unguarded rule below — `data-ai-full-bleed` can never appear on it.
7910
+ "[data-ai-section-inner]:not([data-ai-full-bleed]){padding:0 20px !important}",
7911
+ "[data-ai-container]{padding:0 20px !important}",
7878
7912
  "[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
7879
7913
  "[data-ai-cell]{width:100%;min-width:0}",
7880
7914
  // The rule above turns every row into a COLUMN flex container on phones, which silently breaks
@@ -8079,6 +8113,31 @@ function pricingCardEmphasis(featured, ctx, hasBg) {
8079
8113
  function textAttrs(ctx, path) {
8080
8114
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
8081
8115
  }
8116
+ var AI_RESPONSIVE_CSS = [
8117
+ "@media (max-width: 960px) {",
8118
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
8119
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
8120
+ "}",
8121
+ "@media (max-width: 640px) {",
8122
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
8123
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
8124
+ // Group containers flatten to a column on phones; span placements come along for free.
8125
+ // `:not([data-ai-group="overlap"])` for the same reason as the twin pair in AI_MOBILE_CSS — see
8126
+ // the comment there. This sheet's copy is the narrower selector, so without the exemption it
8127
+ // flattens an `overlap` hero below 640px even though the other rule stands down: measured a 390px
8128
+ // viewport with the heading/CTA in flow *under* the image, 219px of section background where the
8129
+ // layered text used to be.
8130
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) { display: flex !important; flex-direction: column !important; }',
8131
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) > * { grid-column: auto !important; }',
8132
+ // `:not([data-ai-full-bleed])` for the same reason as the twin rule in AI_MOBILE_CSS: this one
8133
+ // is narrower (0,2,0 vs 0,1,0) and also `!important`, so without the guard it wins outright and
8134
+ // a full-bleed hero gets its 20px side band back below 640px only — the one width where the
8135
+ // other rule had already been taught to stand down.
8136
+ " [data-ai-responsive] [data-ai-section-inner]:not([data-ai-full-bleed]) { padding-left: 20px !important; padding-right: 20px !important; }",
8137
+ " [data-ai-responsive] { overflow-x: hidden; }",
8138
+ " [data-ai-responsive] img { max-width: 100%; }",
8139
+ "}"
8140
+ ].join("\n");
8082
8141
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
8083
8142
  function MediaBox({
8084
8143
  refValue,
@@ -8133,7 +8192,7 @@ function MediaBox({
8133
8192
  position: fill ? "absolute" : void 0,
8134
8193
  inset: fill ? 0 : void 0,
8135
8194
  width: fill ? "100%" : fixedSize ? "auto" : "100%",
8136
- height: fill ? "100%" : void 0,
8195
+ height: fill ? "100%" : fixedSize ? height : void 0,
8137
8196
  aspectRatio: fill ? void 0 : aspectRatio,
8138
8197
  minHeight: fill || fixedSize ? void 0 : height ?? (aspectRatio ? void 0 : 180),
8139
8198
  borderRadius: radius,
@@ -9629,7 +9688,7 @@ function CollectionBlock({ node, ctx, path }) {
9629
9688
  return /* @__PURE__ */ jsx(
9630
9689
  "div",
9631
9690
  {
9632
- "data-ai-grid": "",
9691
+ "data-ai-grid": String(itemsPerRow),
9633
9692
  "data-ohw-pricing-collection": isPricing ? "" : void 0,
9634
9693
  style: {
9635
9694
  display: "grid",
@@ -9931,7 +9990,7 @@ function renderNode(node, ctx, path, ambientAlign) {
9931
9990
  },
9932
9991
  children: [
9933
9992
  i > 0 && ctx.heroOverlapFullBleed ? (
9934
- // The section's `data-ai-container` gave up its max-width/gutter so the base
9993
+ // The section's `data-ai-section-inner` gave up its max-width/gutter so the base
9935
9994
  // layer (this group's i===0 sibling) can span edge-to-edge — reintroduced here,
9936
9995
  // scoped to just the overlay text, so a heading/copy/CTA stack doesn't stretch
9937
9996
  // the full section width instead of reading at the usual content width.
@@ -10350,6 +10409,7 @@ function AiTreeRenderer({
10350
10409
  ...fillHeight ? { "data-ai-fill-height": "" } : {},
10351
10410
  ...needsOverlayClearance ? { "data-ai-overlay-clearance": "" } : {},
10352
10411
  ...bgAttrs,
10412
+ "data-ai-responsive": "",
10353
10413
  style: {
10354
10414
  position: "relative",
10355
10415
  padding: `${padCss} 0`,
@@ -10363,12 +10423,13 @@ function AiTreeRenderer({
10363
10423
  ...fillHeight ? { display: "grid" } : {}
10364
10424
  },
10365
10425
  children: [
10366
- isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10426
+ /* @__PURE__ */ jsx("style", { children: AI_RESPONSIVE_CSS }),
10367
10427
  /* @__PURE__ */ jsx("style", { children: AI_MOBILE_CSS }),
10428
+ isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10368
10429
  /* @__PURE__ */ jsx(
10369
10430
  "div",
10370
10431
  {
10371
- "data-ai-container": "",
10432
+ "data-ai-section-inner": "",
10372
10433
  ...fullBleedMedia ? { "data-ai-full-bleed": "" } : {},
10373
10434
  style: {
10374
10435
  position: "relative",
@@ -10385,7 +10446,7 @@ function AiTreeRenderer({
10385
10446
  children: tree.rows.map((row, r2) => /* @__PURE__ */ jsx(
10386
10447
  "div",
10387
10448
  {
10388
- "data-ai-row": "",
10449
+ "data-ai-columns": "",
10389
10450
  style: {
10390
10451
  display: "grid",
10391
10452
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -11047,9 +11108,10 @@ function applyAiSectionsToDom(state, options) {
11047
11108
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
11048
11109
  continue;
11049
11110
  }
11050
- const isNewMount = !existing || !existing.container.isConnected;
11051
11111
  let container = existing?.container;
11112
+ let isNewMount = false;
11052
11113
  if (!container || !container.isConnected) {
11114
+ isNewMount = true;
11053
11115
  existing?.root.unmount();
11054
11116
  container?.remove();
11055
11117
  container = document.createElement("div");
@@ -17208,7 +17270,7 @@ function apiBase() {
17208
17270
  function bridgeVersion() {
17209
17271
  try {
17210
17272
  if (true) {
17211
- return "0.1.159";
17273
+ return "0.1.160-next.427";
17212
17274
  }
17213
17275
  } catch {
17214
17276
  }
@@ -17222,7 +17284,7 @@ function stampBridgeVersionOnDocument() {
17222
17284
  return;
17223
17285
  }
17224
17286
  try {
17225
- const v = true ? "0.1.159" : "";
17287
+ const v = true ? "0.1.160-next.427" : "";
17226
17288
  if (v) {
17227
17289
  document.documentElement.setAttribute("data-ohw-bridge-version", v);
17228
17290
  window.__OHW_BRIDGE_VERSION__ = v;
@@ -17900,6 +17962,7 @@ function MediaOverlay({
17900
17962
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
17901
17963
  );
17902
17964
  }, [isVideo]);
17965
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
17903
17966
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
17904
17967
  const box = {
17905
17968
  position: "fixed",
@@ -17998,11 +18061,10 @@ function MediaOverlay({
17998
18061
  className: "flex items-center justify-center cursor-pointer",
17999
18062
  style: {
18000
18063
  ...box,
18001
- // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
18002
- // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
18003
- // in-document, pointer-events does it natively. The button below opts back in, so
18004
- // Replace still works.
18005
- pointerEvents: hover.hasTextOverlap || cueOnly ? "none" : "auto",
18064
+ // Visual only. `pointer-events: auto` on this full-bleed rect covered the navbar
18065
+ // (hero bg includes the absolute TopNav) and swallowed CTA press-drag. Text overlay
18066
+ // and cueOnly also need clicks to fall through — Replace / video controls opt back in.
18067
+ pointerEvents: "none",
18006
18068
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
18007
18069
  // than hovered. Hover keeps the existing tinted preview.
18008
18070
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -18029,17 +18091,17 @@ function MediaOverlay({
18029
18091
  },
18030
18092
  children: [
18031
18093
  isVideo ? /* @__PURE__ */ jsx17(Film, { size: 14 }) : /* @__PURE__ */ jsx17(ImageIcon, { size: 14 }),
18032
- isVideo ? "Replace video" : "Replace image"
18094
+ replaceLabel
18033
18095
  ]
18034
18096
  }
18035
18097
  ),
18036
- replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ jsxs8(
18098
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ jsxs8(
18037
18099
  Button,
18038
18100
  {
18039
18101
  "data-ohw-media-overlay": "",
18040
18102
  variant: "outline",
18041
18103
  size: "sm",
18042
- "aria-label": isVideo ? "Replace video" : "Replace image",
18104
+ "aria-label": replaceLabel,
18043
18105
  className: "gap-1.5 cursor-pointer hover:bg-background",
18044
18106
  style: {
18045
18107
  ...OVERLAY_BUTTON_STYLE,
@@ -18062,7 +18124,7 @@ function MediaOverlay({
18062
18124
  },
18063
18125
  children: [
18064
18126
  isVideo ? /* @__PURE__ */ jsx17(Film, { size: 14 }) : /* @__PURE__ */ jsx17(ImageIcon, { size: 14 }),
18065
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
18127
+ replaceMode === "full" ? replaceLabel : null
18066
18128
  ]
18067
18129
  }
18068
18130
  )
@@ -18655,8 +18717,24 @@ function AiSectionOverlay({
18655
18717
  }
18656
18718
 
18657
18719
  // src/ui/ai-section/AiMediaStripOverlay.tsx
18658
- import { useCallback as useCallback3, useEffect as useEffect6, useRef as useRef5, useState as useState8 } from "react";
18659
- import { Star } from "lucide-react";
18720
+ import { useCallback as useCallback3, useEffect as useEffect6, useLayoutEffect as useLayoutEffect4, useRef as useRef5, useState as useState8 } from "react";
18721
+ import {
18722
+ AlignCenter,
18723
+ AlignLeft,
18724
+ AlignRight,
18725
+ ArrowLeft as ArrowLeft2,
18726
+ ArrowRight as ArrowRight2,
18727
+ Check as Check2,
18728
+ ChevronDown as ChevronDown3,
18729
+ Image as ImageIcon2,
18730
+ ImageMinus,
18731
+ ImagePlus,
18732
+ Link as LinkIcon,
18733
+ Minus,
18734
+ Palette,
18735
+ Plus as Plus2,
18736
+ Star
18737
+ } from "lucide-react";
18660
18738
 
18661
18739
  // src/lib/ai-media-strip-tree.ts
18662
18740
  var MEDIA_STRIP_ITEM_MIN = 1;
@@ -18770,14 +18848,6 @@ function setMediaStripItemAsset(tree, index, assetId) {
18770
18848
  return true;
18771
18849
  });
18772
18850
  }
18773
- function setMediaStripItemAlt(tree, index, alt) {
18774
- return withMediaStrip(tree, (block) => {
18775
- const items = block.items;
18776
- if (index < 0 || index >= items.length) return false;
18777
- items[index] = { ...items[index], alt };
18778
- return true;
18779
- });
18780
- }
18781
18851
  function setMediaStripItemLink(tree, index, link) {
18782
18852
  return withMediaStrip(tree, (block) => {
18783
18853
  const items = block.items;
@@ -18795,11 +18865,117 @@ function setMediaStripItemLink(tree, index, link) {
18795
18865
  });
18796
18866
  }
18797
18867
 
18868
+ // src/ui/dialog.tsx
18869
+ import * as React9 from "react";
18870
+ import { Dialog as DialogPrimitive } from "radix-ui";
18871
+ import { X as X2 } from "lucide-react";
18872
+ import { jsx as jsx20, jsxs as jsxs11 } from "react/jsx-runtime";
18873
+ function Dialog2({
18874
+ ...props
18875
+ }) {
18876
+ return /* @__PURE__ */ jsx20(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
18877
+ }
18878
+ function DialogPortal({
18879
+ ...props
18880
+ }) {
18881
+ return /* @__PURE__ */ jsx20(DialogPrimitive.Portal, { ...props });
18882
+ }
18883
+ function DialogOverlay({
18884
+ className,
18885
+ ...props
18886
+ }) {
18887
+ return /* @__PURE__ */ jsx20(
18888
+ DialogPrimitive.Overlay,
18889
+ {
18890
+ "data-slot": "dialog-overlay",
18891
+ "data-ohw-link-modal-root": "",
18892
+ className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
18893
+ ...props
18894
+ }
18895
+ );
18896
+ }
18897
+ var DialogContent = React9.forwardRef(
18898
+ ({ className, children, showCloseButton = true, container, ...props }, ref) => {
18899
+ const positionMode = container ? "absolute" : "fixed";
18900
+ return /* @__PURE__ */ jsxs11(DialogPortal, { container: container ?? void 0, children: [
18901
+ /* @__PURE__ */ jsx20(DialogOverlay, { className: cn(positionMode, "inset-0") }),
18902
+ /* @__PURE__ */ jsxs11(
18903
+ DialogPrimitive.Content,
18904
+ {
18905
+ ref,
18906
+ "data-slot": "dialog-content",
18907
+ className: cn(
18908
+ positionMode,
18909
+ "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",
18910
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
18911
+ container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
18912
+ className
18913
+ ),
18914
+ onMouseDown: (e) => e.stopPropagation(),
18915
+ ...props,
18916
+ children: [
18917
+ children,
18918
+ showCloseButton ? /* @__PURE__ */ jsx20(
18919
+ DialogPrimitive.Close,
18920
+ {
18921
+ type: "button",
18922
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
18923
+ "aria-label": "Close",
18924
+ children: /* @__PURE__ */ jsx20(X2, { size: 16, "aria-hidden": true })
18925
+ }
18926
+ ) : null
18927
+ ]
18928
+ }
18929
+ )
18930
+ ] });
18931
+ }
18932
+ );
18933
+ DialogContent.displayName = DialogPrimitive.Content.displayName;
18934
+ function DialogHeader({
18935
+ className,
18936
+ ...props
18937
+ }) {
18938
+ return /* @__PURE__ */ jsx20("div", { className: cn("flex flex-col gap-1.5", className), ...props });
18939
+ }
18940
+ function DialogFooter({
18941
+ className,
18942
+ ...props
18943
+ }) {
18944
+ return /* @__PURE__ */ jsx20(
18945
+ "div",
18946
+ {
18947
+ className: cn("flex items-center justify-end gap-2", className),
18948
+ ...props
18949
+ }
18950
+ );
18951
+ }
18952
+ var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx20(
18953
+ DialogPrimitive.Title,
18954
+ {
18955
+ ref,
18956
+ className: cn(
18957
+ "text-lg font-semibold leading-none tracking-tight text-card-foreground",
18958
+ className
18959
+ ),
18960
+ ...props
18961
+ }
18962
+ ));
18963
+ DialogTitle.displayName = DialogPrimitive.Title.displayName;
18964
+ var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx20(
18965
+ DialogPrimitive.Description,
18966
+ {
18967
+ ref,
18968
+ className: cn("text-sm text-muted-foreground", className),
18969
+ ...props
18970
+ }
18971
+ ));
18972
+ DialogDescription.displayName = DialogPrimitive.Description.displayName;
18973
+ var DialogClose = DialogPrimitive.Close;
18974
+
18798
18975
  // src/ui/ai-section/AiMediaStripOverlay.tsx
18799
- import { Fragment as Fragment7, jsx as jsx20, jsxs as jsxs11 } from "react/jsx-runtime";
18800
- var PRIMARY3 = "#0885FE";
18801
- var OHW_BLUE = `var(--ohw-primary, ${PRIMARY3})`;
18802
- var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY3}) 60%, #000000)`;
18976
+ import { Fragment as Fragment7, jsx as jsx21, jsxs as jsxs12 } from "react/jsx-runtime";
18977
+ var PRIMARY3 = "var(--ohw-primary, #0885FE)";
18978
+ 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]";
18803
18979
  function findMediaStripItemElement(sectionId, index) {
18804
18980
  const sectionEl = findByInstanceId(sectionId);
18805
18981
  if (!sectionEl) return null;
@@ -18809,14 +18985,35 @@ function findMediaStripItemElement(sectionId, index) {
18809
18985
  }
18810
18986
  return null;
18811
18987
  }
18812
- function readRect2(ref) {
18813
- const el = ref && findMediaStripItemElement(ref.sectionId, ref.index);
18988
+ function itemRefFromTarget(target) {
18989
+ if (!(target instanceof Element)) return null;
18990
+ if (target.closest(CHROME_SELECTOR)) return null;
18991
+ const itemEl = target.closest("[data-ohw-media-strip-item]");
18992
+ if (!itemEl) return null;
18993
+ const sectionEl = itemEl.closest("[data-ohw-section]");
18994
+ const unit = sectionEl ? movableUnit(sectionEl) : null;
18995
+ const sectionId = unit ? instanceIdOf(unit) || null : null;
18996
+ const index = Number(itemEl.dataset.ohwMediaStripIndex);
18997
+ if (!sectionId || Number.isNaN(index)) return null;
18998
+ return { sectionId, index };
18999
+ }
19000
+ function sameRef(a, b) {
19001
+ return a?.sectionId === b?.sectionId && a?.index === b?.index;
19002
+ }
19003
+ function rectOf(el) {
18814
19004
  if (!el) return null;
18815
19005
  const r2 = el.getBoundingClientRect();
18816
19006
  if (r2.width <= 0 || r2.height <= 0) return null;
18817
19007
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
18818
19008
  }
18819
- function useLiveItemRect(ref) {
19009
+ function readRect2(ref) {
19010
+ return rectOf(ref && findMediaStripItemElement(ref.sectionId, ref.index));
19011
+ }
19012
+ function readStripRect(ref) {
19013
+ const item = ref && findMediaStripItemElement(ref.sectionId, ref.index);
19014
+ return rectOf(item?.closest("[data-ohw-media-strip]") ?? item);
19015
+ }
19016
+ function useLiveItemRect(ref, strip = false) {
18820
19017
  const [rect, setRect] = useState8(null);
18821
19018
  useEffect6(() => {
18822
19019
  if (!ref) {
@@ -18824,7 +19021,7 @@ function useLiveItemRect(ref) {
18824
19021
  return;
18825
19022
  }
18826
19023
  const update = () => {
18827
- const next = readRect2(ref);
19024
+ const next = strip ? readStripRect(ref) : readRect2(ref);
18828
19025
  setRect(
18829
19026
  (prev) => prev && next && prev.top === next.top && prev.left === next.left && prev.width === next.width && prev.height === next.height ? prev : next
18830
19027
  );
@@ -18839,9 +19036,49 @@ function useLiveItemRect(ref) {
18839
19036
  window.removeEventListener("resize", update);
18840
19037
  clearInterval(interval);
18841
19038
  };
18842
- }, [ref?.sectionId, ref?.index]);
19039
+ }, [ref?.sectionId, ref?.index, strip]);
18843
19040
  return rect;
18844
19041
  }
19042
+ var TOOLBAR_STRIP_GAP = 52;
19043
+ var TOOLBAR_STRIP_GAP_MOBILE = 42;
19044
+ var TOOLBAR_EDGE_MARGIN2 = 4;
19045
+ function StripToolbarSlot({ stripRect, children }) {
19046
+ const slotRef = useRef5(null);
19047
+ const [shift, setShift] = useState8(0);
19048
+ const centerX = stripRect.left + stripRect.width / 2;
19049
+ useLayoutEffect4(() => {
19050
+ const slot = slotRef.current;
19051
+ if (!slot) return;
19052
+ const clamp = () => {
19053
+ const w = slot.offsetWidth;
19054
+ if (w <= 0) return;
19055
+ const half = w / 2;
19056
+ const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
19057
+ setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
19058
+ };
19059
+ clamp();
19060
+ const ro = new ResizeObserver(clamp);
19061
+ ro.observe(slot);
19062
+ return () => ro.disconnect();
19063
+ }, [centerX, children]);
19064
+ const mobile = window.innerWidth <= 480;
19065
+ return /* @__PURE__ */ jsx21(
19066
+ "div",
19067
+ {
19068
+ ref: slotRef,
19069
+ "data-ohw-bridge": "",
19070
+ "data-ohw-item-interaction": "",
19071
+ className: "pointer-events-auto fixed",
19072
+ style: {
19073
+ top: stripRect.top - (mobile ? TOOLBAR_STRIP_GAP_MOBILE : TOOLBAR_STRIP_GAP),
19074
+ left: centerX + shift,
19075
+ transform: "translate(-50%, -100%)",
19076
+ zIndex: 2147483645
19077
+ },
19078
+ children
19079
+ }
19080
+ );
19081
+ }
18845
19082
  function requestValidateTree(postToParent2, tree) {
18846
19083
  return new Promise((resolve) => {
18847
19084
  const id = `ai-validate-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -18878,221 +19115,177 @@ function requestAssetPick(postToParent2) {
18878
19115
  postToParent2({ type: "ow:asset-pick", id });
18879
19116
  });
18880
19117
  }
18881
- function Pill({
19118
+ function ToolbarAction({
18882
19119
  label,
18883
19120
  onClick,
18884
- disabled,
18885
- active
19121
+ disabled = false,
19122
+ active = false,
19123
+ children
18886
19124
  }) {
18887
- return /* @__PURE__ */ jsx20(
18888
- "button",
18889
- {
18890
- type: "button",
18891
- onClick,
18892
- disabled,
18893
- style: {
18894
- height: 28,
18895
- padding: "0 10px",
18896
- display: "inline-flex",
18897
- alignItems: "center",
18898
- gap: 4,
18899
- borderRadius: 999,
18900
- border: "none",
18901
- cursor: disabled ? "default" : "pointer",
18902
- background: active ? OHW_BLUE_DARK : "#ffffff",
18903
- color: active ? "#ffffff" : OHW_BLUE_DARK,
18904
- opacity: disabled ? 0.4 : 1,
18905
- fontSize: 12,
18906
- lineHeight: "16px",
18907
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18908
- whiteSpace: "nowrap",
18909
- flex: "0 0 auto"
18910
- },
18911
- children: label
18912
- }
18913
- );
19125
+ const button = /* @__PURE__ */ jsx21(CustomToolbarButton, { type: "button", "aria-label": label, disabled, active, onClick, className: "size-8", children });
19126
+ return /* @__PURE__ */ jsxs12(Tooltip, { children: [
19127
+ /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ jsx21("span", { className: "inline-flex", children: button }) : button }),
19128
+ /* @__PURE__ */ jsx21(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19129
+ ] });
18914
19130
  }
18915
- function AltInput({ value, onCommit }) {
18916
- const [draft, setDraft] = useState8(value);
18917
- useEffect6(() => setDraft(value), [value]);
18918
- return /* @__PURE__ */ jsxs11(
18919
- "div",
18920
- {
18921
- style: {
18922
- display: "flex",
18923
- alignItems: "center",
18924
- gap: 6,
18925
- height: 28,
18926
- padding: "0 12px",
18927
- borderRadius: 999,
18928
- background: "#ffffff",
18929
- width: "100%",
18930
- maxWidth: 260,
18931
- boxSizing: "border-box"
18932
- },
18933
- children: [
18934
- /* @__PURE__ */ jsx20(
18935
- "span",
18936
- {
18937
- style: {
18938
- flex: "0 0 auto",
18939
- color: OHW_BLUE_DARK,
18940
- fontSize: 10,
18941
- fontWeight: 700,
18942
- textTransform: "uppercase",
18943
- letterSpacing: 0.4
18944
- },
18945
- children: "Alt"
18946
- }
18947
- ),
18948
- /* @__PURE__ */ jsx20(
18949
- "input",
18950
- {
18951
- type: "text",
18952
- value: draft,
18953
- onChange: (e) => setDraft(e.target.value),
18954
- onBlur: () => {
18955
- if (draft !== value) onCommit(draft);
18956
- },
18957
- onKeyDown: (e) => {
18958
- if (e.key === "Enter") e.currentTarget.blur();
18959
- if (e.key === "Escape") {
18960
- setDraft(value);
18961
- e.currentTarget.blur();
18962
- }
18963
- },
18964
- placeholder: "Describe this image",
18965
- "aria-label": "Image alt text",
18966
- style: {
18967
- flex: 1,
18968
- minWidth: 0,
18969
- border: "none",
18970
- outline: "none",
18971
- background: "transparent",
18972
- fontSize: 12,
18973
- lineHeight: "16px",
18974
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18975
- color: "#1f2937"
18976
- }
18977
- }
18978
- )
18979
- ]
18980
- }
18981
- );
19131
+ function ToolbarSeparator() {
19132
+ return /* @__PURE__ */ jsx21("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
18982
19133
  }
18983
- var VARIANTS = [
18984
- { value: "logos", label: "Logos" },
18985
- { value: "reel", label: "Reel" }
18986
- ];
18987
- var ALIGNS = [
18988
- { value: "left", label: "Left" },
18989
- { value: "center", label: "Center" },
18990
- { value: "right", label: "Right" }
18991
- ];
18992
- var HOVERS = [
18993
- { value: "none", label: "None" },
18994
- { value: "lift", label: "Lift" },
18995
- { value: "zoom", label: "Zoom" },
18996
- { value: "fade", label: "Fade" }
18997
- ];
18998
- var TREATMENTS = [
18999
- { value: "color", label: "Color" },
19000
- { value: "grayscale", label: "Grayscale" }
19001
- ];
19002
- var HTTP_URL_RE = /^https?:\/\/\S+$/i;
19003
- function LinkInput({ value, onCommit }) {
19004
- const [draft, setDraft] = useState8(value);
19005
- const [invalid, setInvalid] = useState8(false);
19006
- useEffect6(() => {
19007
- setDraft(value);
19008
- setInvalid(false);
19009
- }, [value]);
19010
- const commit = () => {
19011
- const next = draft.trim();
19012
- if (next === value) return setInvalid(false);
19013
- if (next && !HTTP_URL_RE.test(next)) return setInvalid(true);
19014
- setInvalid(false);
19015
- onCommit(next);
19016
- };
19017
- return /* @__PURE__ */ jsxs11(
19018
- "div",
19019
- {
19020
- style: {
19021
- display: "flex",
19022
- alignItems: "center",
19023
- gap: 6,
19024
- height: 28,
19025
- padding: "0 12px",
19026
- borderRadius: 999,
19027
- background: "#ffffff",
19028
- width: "100%",
19029
- maxWidth: 260,
19030
- boxSizing: "border-box",
19031
- boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
19032
- },
19033
- children: [
19034
- /* @__PURE__ */ jsx20(
19035
- "span",
19036
- {
19037
- style: {
19038
- flex: "0 0 auto",
19039
- color: OHW_BLUE_DARK,
19040
- fontSize: 10,
19041
- fontWeight: 700,
19042
- textTransform: "uppercase",
19043
- letterSpacing: 0.4
19044
- },
19045
- children: "Link"
19046
- }
19047
- ),
19048
- /* @__PURE__ */ jsx20(
19049
- "input",
19134
+ function OptionMenu({
19135
+ label,
19136
+ value,
19137
+ options,
19138
+ onChange
19139
+ }) {
19140
+ const current = options.find((o) => o.value === value) ?? options[0];
19141
+ return /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19142
+ /* @__PURE__ */ jsx21("span", { className: "text-xs text-muted-foreground", children: label }),
19143
+ /* @__PURE__ */ jsxs12(DropdownMenu, { modal: false, children: [
19144
+ /* @__PURE__ */ jsx21(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs12(
19145
+ "button",
19146
+ {
19147
+ type: "button",
19148
+ "aria-label": label,
19149
+ 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",
19150
+ children: [
19151
+ /* @__PURE__ */ jsx21("span", { className: "flex-1 text-left", children: current.label }),
19152
+ /* @__PURE__ */ jsx21(ChevronDown3, { size: 16, style: { pointerEvents: "none" } })
19153
+ ]
19154
+ }
19155
+ ) }),
19156
+ /* @__PURE__ */ jsx21(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[132px] min-w-[132px] p-1", children: options.map((option) => {
19157
+ const selected = option.value === value;
19158
+ return /* @__PURE__ */ jsxs12(
19159
+ DropdownMenuItem,
19050
19160
  {
19051
- type: "text",
19052
- inputMode: "url",
19053
- value: draft,
19054
- onChange: (e) => setDraft(e.target.value),
19055
- onBlur: commit,
19056
- onKeyDown: (e) => {
19057
- if (e.key === "Enter") e.currentTarget.blur();
19058
- if (e.key === "Escape") {
19059
- setDraft(value);
19060
- setInvalid(false);
19061
- e.currentTarget.blur();
19062
- }
19063
- },
19064
- placeholder: "https://example.com",
19065
- "aria-label": "Image link URL",
19066
- "aria-invalid": invalid,
19067
- style: {
19068
- flex: 1,
19069
- minWidth: 0,
19070
- border: "none",
19071
- outline: "none",
19072
- background: "transparent",
19073
- fontSize: 12,
19074
- lineHeight: "16px",
19075
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
19076
- color: "#1f2937"
19077
- }
19078
- }
19079
- )
19080
- ]
19161
+ role: "menuitemradio",
19162
+ "aria-checked": selected,
19163
+ onSelect: () => onChange(option.value),
19164
+ className: cn("grid grid-cols-[1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
19165
+ children: [
19166
+ /* @__PURE__ */ jsx21("span", { children: option.label }),
19167
+ selected && /* @__PURE__ */ jsx21(Check2, { size: 16, className: "text-bridge-primary" })
19168
+ ]
19169
+ },
19170
+ option.value
19171
+ );
19172
+ }) })
19173
+ ] })
19174
+ ] });
19175
+ }
19176
+ var VARIANTS = [
19177
+ { value: "logos", label: "Logos" },
19178
+ { value: "reel", label: "Reel" }
19179
+ ];
19180
+ var ALIGNS = [
19181
+ { value: "left", label: "Align left", Icon: AlignLeft },
19182
+ { value: "center", label: "Align center", Icon: AlignCenter },
19183
+ { value: "right", label: "Align right", Icon: AlignRight }
19184
+ ];
19185
+ var HOVERS = [
19186
+ { value: "none", label: "None" },
19187
+ { value: "lift", label: "Lift" },
19188
+ { value: "zoom", label: "Zoom" },
19189
+ { value: "fade", label: "Fade" }
19190
+ ];
19191
+ var HTTP_URL_RE = /^https?:\/\/\S+$/i;
19192
+ function LinkModal({
19193
+ href,
19194
+ linkTarget,
19195
+ onClose,
19196
+ onSave
19197
+ }) {
19198
+ const [draft, setDraft] = useState8(href);
19199
+ const [newTab, setNewTab] = useState8(linkTarget !== "same-tab");
19200
+ const [invalid, setInvalid] = useState8(false);
19201
+ const save = () => {
19202
+ const next = draft.trim();
19203
+ if (next && !HTTP_URL_RE.test(next)) {
19204
+ setInvalid(true);
19205
+ return;
19081
19206
  }
19082
- );
19207
+ onSave(next, newTab ? "new-tab" : "same-tab");
19208
+ onClose();
19209
+ };
19210
+ return /* @__PURE__ */ jsx21(Dialog2, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs12(DialogContent, { "data-ohw-link-modal-root": "", className: "max-w-[400px] gap-4 p-5", children: [
19211
+ /* @__PURE__ */ jsx21(DialogHeader, { children: /* @__PURE__ */ jsx21(DialogTitle, { className: "text-base", children: "Image link" }) }),
19212
+ /* @__PURE__ */ jsxs12("div", { className: "flex flex-col gap-3", children: [
19213
+ /* @__PURE__ */ jsx21(
19214
+ "input",
19215
+ {
19216
+ autoFocus: true,
19217
+ type: "text",
19218
+ inputMode: "url",
19219
+ value: draft,
19220
+ onChange: (e) => {
19221
+ setDraft(e.target.value);
19222
+ setInvalid(false);
19223
+ },
19224
+ onKeyDown: (e) => {
19225
+ if (e.key === "Enter") save();
19226
+ },
19227
+ placeholder: "https://example.com",
19228
+ "aria-label": "Image link URL",
19229
+ "aria-invalid": invalid,
19230
+ className: cn(
19231
+ "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",
19232
+ invalid && "border-destructive focus:border-destructive"
19233
+ )
19234
+ }
19235
+ ),
19236
+ invalid && /* @__PURE__ */ jsx21("p", { className: "text-xs text-destructive", children: "Enter a full URL starting with http:// or https://" }),
19237
+ /* @__PURE__ */ jsxs12("label", { className: "flex items-center gap-2 text-[13px] text-foreground", children: [
19238
+ /* @__PURE__ */ jsx21("input", { type: "checkbox", checked: newTab, onChange: (e) => setNewTab(e.target.checked) }),
19239
+ "Open in new tab"
19240
+ ] })
19241
+ ] }),
19242
+ /* @__PURE__ */ jsxs12(DialogFooter, { children: [
19243
+ href && /* @__PURE__ */ jsx21(
19244
+ "button",
19245
+ {
19246
+ type: "button",
19247
+ onClick: () => {
19248
+ onSave("", "new-tab");
19249
+ onClose();
19250
+ },
19251
+ className: "mr-auto rounded-[6px] px-3 py-2 text-[13px] font-medium text-destructive transition-colors hover:bg-muted",
19252
+ children: "Remove link"
19253
+ }
19254
+ ),
19255
+ /* @__PURE__ */ jsx21(
19256
+ "button",
19257
+ {
19258
+ type: "button",
19259
+ onClick: onClose,
19260
+ className: "rounded-[6px] px-3 py-2 text-[13px] font-medium text-foreground transition-colors hover:bg-muted",
19261
+ children: "Cancel"
19262
+ }
19263
+ ),
19264
+ /* @__PURE__ */ jsx21(
19265
+ "button",
19266
+ {
19267
+ type: "button",
19268
+ onClick: save,
19269
+ className: "rounded-[6px] bg-bridge-primary px-3 py-2 text-[13px] font-medium text-primary-foreground transition-opacity hover:opacity-90",
19270
+ children: "Save"
19271
+ }
19272
+ )
19273
+ ] })
19274
+ ] }) });
19083
19275
  }
19084
19276
  function MediaStripToolbar({
19085
19277
  entry,
19086
19278
  index,
19087
19279
  pending,
19088
19280
  error,
19281
+ itemEditOn,
19282
+ onToggleItemEdit,
19089
19283
  onSetVariant,
19090
19284
  onSetAlign,
19091
19285
  onSetHover,
19092
19286
  onSetTreatment,
19093
19287
  onResize,
19094
- onSetAlt,
19095
- onSetLink,
19288
+ onOpenLink,
19096
19289
  onAddItem,
19097
19290
  onRemoveItem,
19098
19291
  onChangeImage,
@@ -19101,81 +19294,102 @@ function MediaStripToolbar({
19101
19294
  }) {
19102
19295
  const items = mediaStripItems(entry.tree);
19103
19296
  const itemCount = items.length;
19104
- const currentAlt = items[index]?.alt ?? "";
19105
- const currentHref = items[index]?.href ?? "";
19106
- const currentTarget = items[index]?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
19107
19297
  const slots = findMediaStripSlots(entry.tree);
19108
19298
  const variant = slots?.variant === "reel" ? "reel" : "logos";
19109
19299
  const align = slots?.align === "left" || slots?.align === "right" ? slots.align : "center";
19110
19300
  const hover = slots?.hover === "none" || slots?.hover === "lift" || slots?.hover === "zoom" || slots?.hover === "fade" ? slots.hover : "lift";
19111
19301
  const treatment = slots?.treatment === "color" || slots?.treatment === "grayscale" ? slots.treatment : variant === "logos" ? "grayscale" : "color";
19112
19302
  const sizeValue = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19113
- const isMobile = window.innerWidth <= 480;
19114
- const rowStyle = isMobile ? {
19115
- display: "flex",
19116
- gap: 6,
19117
- flexWrap: "nowrap",
19118
- justifyContent: "center",
19119
- overflowX: "auto",
19120
- WebkitOverflowScrolling: "touch",
19121
- width: "100%",
19122
- scrollbarWidth: "none"
19123
- } : { display: "flex", gap: 6, flexWrap: "nowrap", justifyContent: "center" };
19124
- return /* @__PURE__ */ jsxs11(
19303
+ const mobile = window.innerWidth <= 480;
19304
+ const gatedLabel = (label) => itemEditOn ? label : "Turn on the star to edit this image";
19305
+ return /* @__PURE__ */ jsx21(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsxs12(
19125
19306
  "div",
19126
19307
  {
19127
19308
  "data-ohw-bridge": "",
19128
19309
  "data-ohw-toolbar": "",
19129
19310
  onMouseDown: (e) => e.stopPropagation(),
19130
- style: {
19131
- display: "flex",
19132
- flexDirection: "column",
19133
- gap: 6,
19134
- alignItems: "center",
19135
- background: OHW_BLUE,
19136
- padding: 8,
19137
- borderRadius: 12,
19138
- boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
19139
- pointerEvents: pending ? "none" : "auto",
19140
- opacity: pending ? 0.6 : 1,
19141
- maxWidth: isMobile ? "calc(100vw - 24px)" : "none"
19142
- },
19311
+ className: cn("flex flex-col items-center gap-1.5 font-sans", pending && "pointer-events-none opacity-60"),
19312
+ style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
19143
19313
  children: [
19144
- /* @__PURE__ */ jsxs11("div", { style: rowStyle, children: [
19145
- VARIANTS.map((v) => /* @__PURE__ */ jsx20(Pill, { label: v.label, active: variant === v.value, onClick: () => onSetVariant(v.value) }, v.value)),
19146
- ALIGNS.map((a) => /* @__PURE__ */ jsx20(Pill, { label: a.label, active: align === a.value, onClick: () => onSetAlign(a.value) }, a.value))
19147
- ] }),
19148
- /* @__PURE__ */ jsxs11("div", { style: rowStyle, children: [
19149
- HOVERS.map((h) => /* @__PURE__ */ jsx20(Pill, { label: h.label, active: hover === h.value, onClick: () => onSetHover(h.value) }, h.value)),
19150
- TREATMENTS.map((t) => /* @__PURE__ */ jsx20(Pill, { label: t.label, active: treatment === t.value, onClick: () => onSetTreatment(t.value) }, t.value))
19151
- ] }),
19152
- /* @__PURE__ */ jsxs11("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
19153
- /* @__PURE__ */ jsx20(Pill, { label: "\u2212", onClick: () => onResize(-8) }),
19154
- /* @__PURE__ */ jsxs11("span", { style: { color: "#ffffff", fontSize: 12, minWidth: 34, textAlign: "center" }, children: [
19155
- sizeValue,
19156
- "px"
19157
- ] }),
19158
- /* @__PURE__ */ jsx20(Pill, { label: "+", onClick: () => onResize(8) })
19159
- ] }),
19160
- /* @__PURE__ */ jsx20("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ jsx20(AltInput, { value: currentAlt, onCommit: onSetAlt }) }),
19161
- /* @__PURE__ */ jsx20("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ jsx20(LinkInput, { value: currentHref, onCommit: (href) => onSetLink(href, currentTarget) }) }),
19162
- currentHref && /* @__PURE__ */ jsxs11("div", { style: rowStyle, children: [
19163
- /* @__PURE__ */ jsx20(Pill, { label: "New tab", active: currentTarget === "new-tab", onClick: () => onSetLink(currentHref, "new-tab") }),
19164
- /* @__PURE__ */ jsx20(Pill, { label: "Same tab", active: currentTarget === "same-tab", onClick: () => onSetLink(currentHref, "same-tab") })
19165
- ] }),
19166
- /* @__PURE__ */ jsxs11("div", { style: rowStyle, children: [
19167
- /* @__PURE__ */ jsx20(Pill, { label: "Change Image", onClick: onChangeImage }),
19168
- /* @__PURE__ */ jsx20(Pill, { label: "+ Item", onClick: onAddItem }),
19169
- /* @__PURE__ */ jsx20(Pill, { label: "\u2212 Item", onClick: onRemoveItem, disabled: itemCount <= 1 })
19170
- ] }),
19171
- /* @__PURE__ */ jsxs11("div", { style: rowStyle, children: [
19172
- /* @__PURE__ */ jsx20(Pill, { label: "\u2190 Move", onClick: onMoveLeft, disabled: index <= 0 }),
19173
- /* @__PURE__ */ jsx20(Pill, { label: "\u2192 Move", onClick: onMoveRight, disabled: index >= itemCount - 1 })
19174
- ] }),
19175
- error && /* @__PURE__ */ jsx20("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
19314
+ /* @__PURE__ */ jsxs12(
19315
+ "div",
19316
+ {
19317
+ className: cn(
19318
+ "flex items-center gap-1 rounded-[10px] border border-border bg-background p-1 whitespace-nowrap",
19319
+ "shadow-[0px_10px_15px_-3px_rgba(0,0,0,0.1),0px_4px_6px_-4px_rgba(0,0,0,0.1)]",
19320
+ mobile ? "flex-wrap justify-center" : "flex-nowrap"
19321
+ ),
19322
+ children: [
19323
+ /* @__PURE__ */ jsxs12(Tooltip, { children: [
19324
+ /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx21(
19325
+ CustomToolbarButton,
19326
+ {
19327
+ type: "button",
19328
+ "aria-label": itemEditOn ? "Done editing this image" : "Edit this image",
19329
+ "aria-pressed": itemEditOn,
19330
+ onClick: onToggleItemEdit,
19331
+ className: "group size-8",
19332
+ children: /* @__PURE__ */ jsx21(
19333
+ Star,
19334
+ {
19335
+ size: 16,
19336
+ fill: "currentColor",
19337
+ strokeWidth: 0,
19338
+ className: cn(
19339
+ "transition-colors",
19340
+ itemEditOn ? "text-[#f59e0b] group-hover:text-[#d97706]" : "text-[#d6d3d1] group-hover:text-[#fbbf24]"
19341
+ ),
19342
+ style: { pointerEvents: "none" }
19343
+ }
19344
+ )
19345
+ }
19346
+ ) }),
19347
+ /* @__PURE__ */ jsx21(TooltipContent, { side: "bottom", sideOffset: 8, children: itemEditOn ? "Done editing this image" : "Edit this image" })
19348
+ ] }),
19349
+ /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19350
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: gatedLabel("Change image"), onClick: onChangeImage, disabled: !itemEditOn, children: /* @__PURE__ */ jsx21(ImageIcon2, { size: 16, style: { pointerEvents: "none" } }) }),
19351
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: gatedLabel("Link"), onClick: onOpenLink, disabled: !itemEditOn, children: /* @__PURE__ */ jsx21(LinkIcon, { size: 16, style: { pointerEvents: "none" } }) }),
19352
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: gatedLabel("Move left"), onClick: onMoveLeft, disabled: !itemEditOn || index <= 0, children: /* @__PURE__ */ jsx21(ArrowLeft2, { size: 16, style: { pointerEvents: "none" } }) }),
19353
+ /* @__PURE__ */ jsx21(
19354
+ ToolbarAction,
19355
+ {
19356
+ label: gatedLabel("Move right"),
19357
+ onClick: onMoveRight,
19358
+ disabled: !itemEditOn || index >= itemCount - 1,
19359
+ children: /* @__PURE__ */ jsx21(ArrowRight2, { size: 16, style: { pointerEvents: "none" } })
19360
+ }
19361
+ ),
19362
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: gatedLabel("Remove image"), onClick: onRemoveItem, disabled: !itemEditOn || itemCount <= 1, children: /* @__PURE__ */ jsx21(ImageMinus, { size: 16, style: { pointerEvents: "none" } }) }),
19363
+ /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19364
+ /* @__PURE__ */ jsx21(OptionMenu, { label: "Layout", value: variant, options: VARIANTS, onChange: onSetVariant }),
19365
+ /* @__PURE__ */ jsx21(OptionMenu, { label: "Hover", value: hover, options: HOVERS, onChange: onSetHover }),
19366
+ /* @__PURE__ */ jsx21(
19367
+ ToolbarAction,
19368
+ {
19369
+ label: treatment === "grayscale" ? "Show in color" : "Show grayscale",
19370
+ active: treatment === "grayscale",
19371
+ onClick: () => onSetTreatment(treatment === "grayscale" ? "color" : "grayscale"),
19372
+ children: /* @__PURE__ */ jsx21(Palette, { size: 16, style: { pointerEvents: "none" } })
19373
+ }
19374
+ ),
19375
+ /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19376
+ ALIGNS.map(({ value, label, Icon }) => /* @__PURE__ */ jsx21(ToolbarAction, { label, active: align === value, onClick: () => onSetAlign(value), children: /* @__PURE__ */ jsx21(Icon, { size: 16, style: { pointerEvents: "none" } }) }, value)),
19377
+ /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19378
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: "Smaller", onClick: () => onResize(-8), children: /* @__PURE__ */ jsx21(Minus, { size: 16, style: { pointerEvents: "none" } }) }),
19379
+ /* @__PURE__ */ jsxs12("span", { className: "min-w-[40px] text-center text-xs tabular-nums text-muted-foreground", children: [
19380
+ sizeValue,
19381
+ "px"
19382
+ ] }),
19383
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: "Larger", onClick: () => onResize(8), children: /* @__PURE__ */ jsx21(Plus2, { size: 16, style: { pointerEvents: "none" } }) }),
19384
+ /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19385
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: "Add image", onClick: onAddItem, children: /* @__PURE__ */ jsx21(ImagePlus, { size: 16, style: { pointerEvents: "none" } }) })
19386
+ ]
19387
+ }
19388
+ ),
19389
+ error && /* @__PURE__ */ jsx21("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19176
19390
  ]
19177
19391
  }
19178
- );
19392
+ ) });
19179
19393
  }
19180
19394
  function findMediaStripSlots(tree) {
19181
19395
  const stack = tree.rows.flatMap((row) => row.blocks);
@@ -19194,31 +19408,23 @@ function AiMediaStripOverlay({
19194
19408
  }) {
19195
19409
  const [hover, setHover] = useState8(null);
19196
19410
  const [active, setActive] = useState8(null);
19411
+ const [itemEditOn, setItemEditOn] = useState8(false);
19412
+ const [linkOpen, setLinkOpen] = useState8(false);
19197
19413
  const [pending, setPending] = useState8(false);
19198
19414
  const [error, setError] = useState8(null);
19199
19415
  const activeRef = useRef5(null);
19200
19416
  activeRef.current = active;
19417
+ const linkOpenRef = useRef5(false);
19418
+ linkOpenRef.current = linkOpen;
19201
19419
  useEffect6(() => {
19202
19420
  const onMove = (e) => {
19421
+ const ref = itemRefFromTarget(e.target);
19203
19422
  const t = e.target;
19204
- if (!(t instanceof HTMLElement)) return;
19205
- 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]")) {
19206
- return;
19207
- }
19208
- const itemEl = t.closest("[data-ohw-media-strip-item]");
19209
- if (!itemEl) {
19210
- setHover(null);
19211
- return;
19212
- }
19213
- const sectionEl = itemEl.closest("[data-ohw-section]");
19214
- const unit = sectionEl ? movableUnit(sectionEl) : null;
19215
- const sectionId = unit ? instanceIdOf(unit) || null : null;
19216
- const index = Number(itemEl.dataset.ohwMediaStripIndex);
19217
- if (!sectionId || Number.isNaN(index)) {
19218
- setHover(null);
19219
- return;
19220
- }
19221
- setHover({ sectionId, index });
19423
+ const overEditable = Boolean(t?.closest?.("[data-ohw-editable]"));
19424
+ setHover((prev) => {
19425
+ const next = ref && !overEditable ? ref : null;
19426
+ return sameRef(prev, next) ? prev : next;
19427
+ });
19222
19428
  };
19223
19429
  const onLeave = () => setHover(null);
19224
19430
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -19230,23 +19436,25 @@ function AiMediaStripOverlay({
19230
19436
  }, []);
19231
19437
  useEffect6(() => {
19232
19438
  const onClick = (e) => {
19233
- const current = activeRef.current;
19234
- if (!current) return;
19235
19439
  const t = e.target;
19236
- if (!(t instanceof HTMLElement)) return;
19237
- if (t.closest("[data-ohw-item-interaction]")) return;
19238
- const itemEl = findMediaStripItemElement(current.sectionId, current.index);
19239
- if (itemEl && itemEl.contains(t)) return;
19240
- setActive(null);
19440
+ if (!(t instanceof Element)) return;
19441
+ if (t.closest(CHROME_SELECTOR)) return;
19442
+ const ref = itemRefFromTarget(t);
19443
+ if (sameRef(activeRef.current, ref)) return;
19444
+ if (ref) clearInteractionChromeRef.current();
19445
+ setItemEditOn(false);
19241
19446
  setError(null);
19447
+ setActive(ref);
19242
19448
  };
19243
19449
  document.addEventListener("click", onClick, true);
19244
19450
  return () => document.removeEventListener("click", onClick, true);
19245
- }, []);
19451
+ }, [clearInteractionChromeRef]);
19246
19452
  useEffect6(() => {
19247
19453
  const onBlur = () => {
19248
19454
  if (!activeRef.current) return;
19249
19455
  setActive(null);
19456
+ setItemEditOn(false);
19457
+ setLinkOpen(false);
19250
19458
  setError(null);
19251
19459
  };
19252
19460
  window.addEventListener("blur", onBlur);
@@ -19256,7 +19464,10 @@ function AiMediaStripOverlay({
19256
19464
  const onKeyDown = (e) => {
19257
19465
  if (e.key !== "Escape") return;
19258
19466
  if (!activeRef.current) return;
19467
+ if (linkOpenRef.current) return;
19468
+ if (document.querySelector('[data-ohw-more-menu][data-state="open"]')) return;
19259
19469
  setActive(null);
19470
+ setItemEditOn(false);
19260
19471
  setError(null);
19261
19472
  };
19262
19473
  document.addEventListener("keydown", onKeyDown);
@@ -19290,137 +19501,133 @@ function AiMediaStripOverlay({
19290
19501
  tree: nextTree,
19291
19502
  label: entry.label,
19292
19503
  media: extraMedia ? { ...entry.media, ...extraMedia } : entry.media,
19293
- ...entry.brand ? { brand: entry.brand } : {}
19504
+ ...entry.brand ? { brand: entry.brand } : {},
19505
+ // The user is already looking at the strip they just edited — recentering the viewport
19506
+ // on every edit reads as the section jumping around.
19507
+ skipScrollIntoView: true
19294
19508
  });
19295
19509
  return true;
19296
19510
  },
19297
19511
  [findEntry, postToParent2, applyTreeLocalRef]
19298
19512
  );
19299
19513
  const activeEntry = active ? findEntry(active.sectionId) : null;
19300
- const hoverHandleRect = useLiveItemRect(!active && hover ? hover : null);
19301
19514
  const activeItemRect = useLiveItemRect(active);
19302
- const TOOLBAR_APPROX_H = 170;
19303
- const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
19304
- return /* @__PURE__ */ jsxs11(Fragment7, { children: [
19305
- hoverHandleRect && hover && /* @__PURE__ */ jsx20(
19306
- "button",
19307
- {
19308
- type: "button",
19309
- "data-ohw-toolbar": "",
19310
- onClick: () => {
19311
- clearInteractionChromeRef.current();
19312
- setActive(hover);
19313
- },
19314
- "aria-label": "Configure media item",
19315
- style: {
19316
- position: "fixed",
19317
- top: hoverHandleRect.top + 8,
19318
- left: hoverHandleRect.left + hoverHandleRect.width - 36,
19319
- width: 28,
19320
- height: 28,
19321
- borderRadius: 8,
19322
- border: `1.5px dashed ${PRIMARY3}`,
19323
- background: "#ffffff",
19324
- color: PRIMARY3,
19325
- display: "flex",
19326
- alignItems: "center",
19327
- justifyContent: "center",
19328
- cursor: "pointer",
19329
- zIndex: 2147483643,
19330
- fontSize: 14,
19331
- fontWeight: 700
19332
- },
19333
- children: /* @__PURE__ */ jsx20(Star, { size: 14, style: { pointerEvents: "none" } })
19334
- }
19335
- ),
19336
- activeItemRect && active && activeEntry && /* @__PURE__ */ jsx20(
19337
- ItemInteractionLayer,
19338
- {
19339
- rect: activeItemRect,
19340
- state: toolbarState,
19341
- toolbar: /* @__PURE__ */ jsx20(
19342
- MediaStripToolbar,
19343
- {
19344
- entry: activeEntry,
19345
- index: active.index,
19346
- pending,
19347
- error,
19348
- onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
19349
- onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
19350
- onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
19351
- onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
19352
- onResize: (delta) => {
19353
- const slots = findMediaStripSlots(activeEntry.tree);
19354
- const variant = slots?.variant === "reel" ? "reel" : "logos";
19355
- const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19356
- void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
19357
- },
19358
- onSetAlt: (alt) => {
19359
- void runMutation(active.sectionId, (tree) => setMediaStripItemAlt(tree, active.index, alt));
19360
- },
19361
- onSetLink: (href, linkTarget) => {
19515
+ const activeStripRect = useLiveItemRect(active, true);
19516
+ const hoverRect = useLiveItemRect(hover && !sameRef(hover, active) ? hover : null);
19517
+ const activeItems = activeEntry ? mediaStripItems(activeEntry.tree) : [];
19518
+ const activeItem = active ? activeItems[active.index] : void 0;
19519
+ const activeHref = activeItem?.href ?? "";
19520
+ const activeTarget = activeItem?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
19521
+ return /* @__PURE__ */ jsxs12(Fragment7, { children: [
19522
+ hoverRect && /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19523
+ activeItemRect && activeStripRect && active && activeEntry && /* @__PURE__ */ jsxs12(Fragment7, { children: [
19524
+ /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: activeItemRect, state: "active-top" }),
19525
+ /* @__PURE__ */ jsx21(StripToolbarSlot, { stripRect: activeStripRect, children: /* @__PURE__ */ jsx21(
19526
+ MediaStripToolbar,
19527
+ {
19528
+ entry: activeEntry,
19529
+ index: active.index,
19530
+ pending,
19531
+ error,
19532
+ itemEditOn,
19533
+ onToggleItemEdit: () => setItemEditOn((on) => !on),
19534
+ onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
19535
+ onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
19536
+ onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
19537
+ onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
19538
+ onResize: (delta) => {
19539
+ const slots = findMediaStripSlots(activeEntry.tree);
19540
+ const variant = slots?.variant === "reel" ? "reel" : "logos";
19541
+ const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19542
+ void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
19543
+ },
19544
+ onOpenLink: () => setLinkOpen(true),
19545
+ onAddItem: () => {
19546
+ void requestAssetPick(postToParent2).then((picked) => {
19547
+ if (!picked) return;
19548
+ const item = { assetId: picked.assetId, alt: "Logo" };
19362
19549
  void runMutation(
19363
19550
  active.sectionId,
19364
- (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
19365
- );
19366
- },
19367
- onAddItem: () => {
19368
- void requestAssetPick(postToParent2).then((picked) => {
19369
- if (!picked) return;
19370
- const item = { assetId: picked.assetId, alt: "Logo" };
19371
- void runMutation(
19372
- active.sectionId,
19373
- (tree) => addMediaStripItem(tree, item, active.index),
19374
- { [picked.assetId]: picked.url }
19375
- ).then((applied) => {
19376
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19377
- });
19551
+ (tree) => addMediaStripItem(tree, item, active.index),
19552
+ { [picked.assetId]: picked.url }
19553
+ ).then((applied) => {
19554
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19378
19555
  });
19379
- },
19380
- onChangeImage: () => {
19381
- void requestAssetPick(postToParent2).then((picked) => {
19382
- if (!picked) return;
19383
- void runMutation(
19384
- active.sectionId,
19385
- (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
19386
- { [picked.assetId]: picked.url }
19387
- );
19388
- });
19389
- },
19390
- onRemoveItem: () => {
19391
- void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
19392
- (applied) => {
19393
- if (applied) setActive(null);
19394
- }
19395
- );
19396
- },
19397
- onMoveLeft: () => {
19398
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
19399
- (applied) => {
19400
- if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
19401
- }
19402
- );
19403
- },
19404
- onMoveRight: () => {
19405
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
19406
- (applied) => {
19407
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19408
- }
19556
+ });
19557
+ },
19558
+ onChangeImage: () => {
19559
+ void requestAssetPick(postToParent2).then((picked) => {
19560
+ if (!picked) return;
19561
+ void runMutation(
19562
+ active.sectionId,
19563
+ (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
19564
+ { [picked.assetId]: picked.url }
19409
19565
  );
19410
- }
19566
+ });
19567
+ },
19568
+ onRemoveItem: () => {
19569
+ void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
19570
+ (applied) => {
19571
+ if (applied) setActive(null);
19572
+ }
19573
+ );
19574
+ },
19575
+ onMoveLeft: () => {
19576
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
19577
+ (applied) => {
19578
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
19579
+ }
19580
+ );
19581
+ },
19582
+ onMoveRight: () => {
19583
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
19584
+ (applied) => {
19585
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19586
+ }
19587
+ );
19411
19588
  }
19412
- )
19413
- }
19414
- )
19589
+ }
19590
+ ) }),
19591
+ /* @__PURE__ */ jsx21(
19592
+ "div",
19593
+ {
19594
+ "data-ohw-bridge": "",
19595
+ "data-ohw-item-interaction": "",
19596
+ "aria-hidden": true,
19597
+ className: "pointer-events-none fixed rounded-[4px] px-[7px] py-[3px] font-sans text-[11px] font-medium leading-none text-primary-foreground whitespace-nowrap",
19598
+ style: {
19599
+ top: activeItemRect.top + activeItemRect.height + 10,
19600
+ left: activeItemRect.left - 5,
19601
+ background: PRIMARY3,
19602
+ zIndex: 2147483644
19603
+ },
19604
+ children: "Media item"
19605
+ }
19606
+ ),
19607
+ linkOpen && /* @__PURE__ */ jsx21(
19608
+ LinkModal,
19609
+ {
19610
+ href: activeHref,
19611
+ linkTarget: activeTarget,
19612
+ onClose: () => setLinkOpen(false),
19613
+ onSave: (href, linkTarget) => {
19614
+ void runMutation(
19615
+ active.sectionId,
19616
+ (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
19617
+ );
19618
+ }
19619
+ }
19620
+ )
19621
+ ] })
19415
19622
  ] });
19416
19623
  }
19417
19624
 
19418
19625
  // src/ui/ai-section/AiPricingCardOverlay.tsx
19419
- import { useCallback as useCallback4, useEffect as useEffect7, useLayoutEffect as useLayoutEffect4, useRef as useRef6, useState as useState9 } from "react";
19420
- import { Check as Check2, ChevronDown as ChevronDown3, Minus, Plus as Plus2, Star as Star2 } from "lucide-react";
19421
- import { Fragment as Fragment8, jsx as jsx21, jsxs as jsxs12 } from "react/jsx-runtime";
19626
+ import { useCallback as useCallback4, useEffect as useEffect7, useLayoutEffect as useLayoutEffect5, useRef as useRef6, useState as useState9 } from "react";
19627
+ import { Check as Check3, ChevronDown as ChevronDown4, Minus as Minus2, Plus as Plus3, Star as Star2 } from "lucide-react";
19628
+ import { Fragment as Fragment8, jsx as jsx22, jsxs as jsxs13 } from "react/jsx-runtime";
19422
19629
  var PRIMARY4 = "var(--ohw-primary, #0885FE)";
19423
- 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]";
19630
+ 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]";
19424
19631
  function findPricingCardElement(sectionId, index) {
19425
19632
  const sectionEl = findByInstanceId(sectionId);
19426
19633
  if (!sectionEl) return null;
@@ -19432,7 +19639,7 @@ function findPricingCardElement(sectionId, index) {
19432
19639
  }
19433
19640
  function cardRefFromTarget(target) {
19434
19641
  if (!(target instanceof Element)) return null;
19435
- if (target.closest(CHROME_SELECTOR)) return null;
19642
+ if (target.closest(CHROME_SELECTOR2)) return null;
19436
19643
  const cardEl = target.closest("[data-ohw-pricing-card]");
19437
19644
  if (!cardEl) return null;
19438
19645
  const sectionEl = cardEl.closest("[data-ohw-section]");
@@ -19442,21 +19649,21 @@ function cardRefFromTarget(target) {
19442
19649
  if (!sectionId || Number.isNaN(index)) return null;
19443
19650
  return { sectionId, index };
19444
19651
  }
19445
- function sameRef(a, b) {
19652
+ function sameRef2(a, b) {
19446
19653
  return a?.sectionId === b?.sectionId && a?.index === b?.index;
19447
19654
  }
19448
- function rectOf(el) {
19655
+ function rectOf2(el) {
19449
19656
  if (!el) return null;
19450
19657
  const r2 = el.getBoundingClientRect();
19451
19658
  if (r2.width <= 0 || r2.height <= 0) return null;
19452
19659
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
19453
19660
  }
19454
19661
  function readRect3(ref) {
19455
- return rectOf(ref && findPricingCardElement(ref.sectionId, ref.index));
19662
+ return rectOf2(ref && findPricingCardElement(ref.sectionId, ref.index));
19456
19663
  }
19457
19664
  function readGridRect(ref) {
19458
19665
  const card = ref && findPricingCardElement(ref.sectionId, ref.index);
19459
- return rectOf(card?.closest("[data-ohw-pricing-collection]") ?? card);
19666
+ return rectOf2(card?.closest("[data-ohw-pricing-collection]") ?? card);
19460
19667
  }
19461
19668
  function useLiveCardRect(ref, grid = false) {
19462
19669
  const [rect, setRect] = useState9(null);
@@ -19486,19 +19693,19 @@ function useLiveCardRect(ref, grid = false) {
19486
19693
  }
19487
19694
  var TOOLBAR_GRID_GAP = 52;
19488
19695
  var TOOLBAR_GRID_GAP_MOBILE = 42;
19489
- var TOOLBAR_EDGE_MARGIN2 = 4;
19696
+ var TOOLBAR_EDGE_MARGIN3 = 4;
19490
19697
  function GridToolbarSlot({ gridRect, children }) {
19491
19698
  const slotRef = useRef6(null);
19492
19699
  const [shift, setShift] = useState9(0);
19493
19700
  const centerX = gridRect.left + gridRect.width / 2;
19494
- useLayoutEffect4(() => {
19701
+ useLayoutEffect5(() => {
19495
19702
  const slot = slotRef.current;
19496
19703
  if (!slot) return;
19497
19704
  const clamp = () => {
19498
19705
  const w = slot.offsetWidth;
19499
19706
  if (w <= 0) return;
19500
19707
  const half = w / 2;
19501
- const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
19708
+ const clamped = Math.max(TOOLBAR_EDGE_MARGIN3 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half));
19502
19709
  setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
19503
19710
  };
19504
19711
  clamp();
@@ -19507,7 +19714,7 @@ function GridToolbarSlot({ gridRect, children }) {
19507
19714
  return () => ro.disconnect();
19508
19715
  }, [centerX, children]);
19509
19716
  const mobile = window.innerWidth <= 480;
19510
- return /* @__PURE__ */ jsx21(
19717
+ return /* @__PURE__ */ jsx22(
19511
19718
  "div",
19512
19719
  {
19513
19720
  ref: slotRef,
@@ -19542,7 +19749,7 @@ function requestValidateTree2(postToParent2, tree) {
19542
19749
  });
19543
19750
  }
19544
19751
  function StyleSwatch({ style }) {
19545
- return /* @__PURE__ */ jsx21(
19752
+ return /* @__PURE__ */ jsx22(
19546
19753
  "span",
19547
19754
  {
19548
19755
  "aria-hidden": true,
@@ -19556,35 +19763,35 @@ function StyleSwatch({ style }) {
19556
19763
  );
19557
19764
  }
19558
19765
  function ButtonGlyph() {
19559
- return /* @__PURE__ */ jsxs12("svg", { width: "18", height: "12", viewBox: "0 0 18 12", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19560
- /* @__PURE__ */ jsx21("rect", { x: "0.75", y: "0.75", width: "16.5", height: "10.5", rx: "3", stroke: "currentColor", strokeWidth: "1.5" }),
19561
- /* @__PURE__ */ jsx21("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
19766
+ return /* @__PURE__ */ jsxs13("svg", { width: "18", height: "12", viewBox: "0 0 18 12", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19767
+ /* @__PURE__ */ jsx22("rect", { x: "0.75", y: "0.75", width: "16.5", height: "10.5", rx: "3", stroke: "currentColor", strokeWidth: "1.5" }),
19768
+ /* @__PURE__ */ jsx22("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
19562
19769
  ] });
19563
19770
  }
19564
19771
  function FeatureGlyph({ mode }) {
19565
- return /* @__PURE__ */ jsxs12("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19566
- /* @__PURE__ */ jsx21("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
19567
- /* @__PURE__ */ jsx21("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
19568
- /* @__PURE__ */ jsx21("circle", { cx: "13.5", cy: "13.5", r: "4.25", fill: "var(--ohw-background, #ffffff)", stroke: "var(--ohw-background, #ffffff)", strokeWidth: "1" }),
19569
- /* @__PURE__ */ jsx21("circle", { cx: "13.5", cy: "13.5", r: "3.75", fill: "var(--ohw-background, #ffffff)", stroke: PRIMARY4, strokeWidth: "1" }),
19570
- /* @__PURE__ */ jsx21("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
19571
- mode === "add" && /* @__PURE__ */ jsx21("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
19772
+ return /* @__PURE__ */ jsxs13("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19773
+ /* @__PURE__ */ jsx22("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
19774
+ /* @__PURE__ */ jsx22("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
19775
+ /* @__PURE__ */ jsx22("circle", { cx: "13.5", cy: "13.5", r: "4.25", fill: "var(--ohw-background, #ffffff)", stroke: "var(--ohw-background, #ffffff)", strokeWidth: "1" }),
19776
+ /* @__PURE__ */ jsx22("circle", { cx: "13.5", cy: "13.5", r: "3.75", fill: "var(--ohw-background, #ffffff)", stroke: PRIMARY4, strokeWidth: "1" }),
19777
+ /* @__PURE__ */ jsx22("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
19778
+ mode === "add" && /* @__PURE__ */ jsx22("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
19572
19779
  ] });
19573
19780
  }
19574
- function ToolbarAction({
19781
+ function ToolbarAction2({
19575
19782
  label,
19576
19783
  onClick,
19577
19784
  disabled = false,
19578
19785
  children
19579
19786
  }) {
19580
- const button = /* @__PURE__ */ jsx21(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
19581
- return /* @__PURE__ */ jsxs12(Tooltip, { children: [
19582
- /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ jsx21("span", { className: "inline-flex", children: button }) : button }),
19583
- /* @__PURE__ */ jsx21(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19787
+ const button = /* @__PURE__ */ jsx22(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
19788
+ return /* @__PURE__ */ jsxs13(Tooltip, { children: [
19789
+ /* @__PURE__ */ jsx22(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ jsx22("span", { className: "inline-flex", children: button }) : button }),
19790
+ /* @__PURE__ */ jsx22(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19584
19791
  ] });
19585
19792
  }
19586
- function ToolbarSeparator() {
19587
- return /* @__PURE__ */ jsx21("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19793
+ function ToolbarSeparator2() {
19794
+ return /* @__PURE__ */ jsx22("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19588
19795
  }
19589
19796
  function CardStyleMenu({
19590
19797
  value,
@@ -19592,7 +19799,7 @@ function CardStyleMenu({
19592
19799
  disabled = false
19593
19800
  }) {
19594
19801
  const current = PRICING_CARD_STYLES.find((s) => s.value === value) ?? PRICING_CARD_STYLES[0];
19595
- const trigger = /* @__PURE__ */ jsxs12(
19802
+ const trigger = /* @__PURE__ */ jsxs13(
19596
19803
  "button",
19597
19804
  {
19598
19805
  type: "button",
@@ -19603,28 +19810,28 @@ function CardStyleMenu({
19603
19810
  disabled && "cursor-not-allowed opacity-40 hover:bg-transparent"
19604
19811
  ),
19605
19812
  children: [
19606
- /* @__PURE__ */ jsx21(StyleSwatch, { style: current.value }),
19607
- /* @__PURE__ */ jsx21("span", { className: "flex-1 text-left", children: current.label }),
19608
- /* @__PURE__ */ jsx21(ChevronDown3, { size: 16, style: { pointerEvents: "none" } })
19813
+ /* @__PURE__ */ jsx22(StyleSwatch, { style: current.value }),
19814
+ /* @__PURE__ */ jsx22("span", { className: "flex-1 text-left", children: current.label }),
19815
+ /* @__PURE__ */ jsx22(ChevronDown4, { size: 16, style: { pointerEvents: "none" } })
19609
19816
  ]
19610
19817
  }
19611
19818
  );
19612
19819
  if (disabled) {
19613
- return /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19614
- /* @__PURE__ */ jsx21("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
19615
- /* @__PURE__ */ jsxs12(Tooltip, { children: [
19616
- /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx21("span", { className: "inline-flex", children: trigger }) }),
19617
- /* @__PURE__ */ jsx21(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
19820
+ return /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19821
+ /* @__PURE__ */ jsx22("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
19822
+ /* @__PURE__ */ jsxs13(Tooltip, { children: [
19823
+ /* @__PURE__ */ jsx22(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx22("span", { className: "inline-flex", children: trigger }) }),
19824
+ /* @__PURE__ */ jsx22(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
19618
19825
  ] })
19619
19826
  ] });
19620
19827
  }
19621
- return /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19622
- /* @__PURE__ */ jsx21("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
19623
- /* @__PURE__ */ jsxs12(DropdownMenu, { modal: false, children: [
19624
- /* @__PURE__ */ jsx21(DropdownMenuTrigger, { asChild: true, children: trigger }),
19625
- /* @__PURE__ */ jsx21(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[152px] min-w-[152px] p-1", children: PRICING_CARD_STYLES.map((style) => {
19828
+ return /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19829
+ /* @__PURE__ */ jsx22("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
19830
+ /* @__PURE__ */ jsxs13(DropdownMenu, { modal: false, children: [
19831
+ /* @__PURE__ */ jsx22(DropdownMenuTrigger, { asChild: true, children: trigger }),
19832
+ /* @__PURE__ */ jsx22(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[152px] min-w-[152px] p-1", children: PRICING_CARD_STYLES.map((style) => {
19626
19833
  const selected = style.value === value;
19627
- return /* @__PURE__ */ jsxs12(
19834
+ return /* @__PURE__ */ jsxs13(
19628
19835
  DropdownMenuItem,
19629
19836
  {
19630
19837
  role: "menuitemradio",
@@ -19632,9 +19839,9 @@ function CardStyleMenu({
19632
19839
  onSelect: () => onChange(style.value),
19633
19840
  className: cn("grid grid-cols-[18px_1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
19634
19841
  children: [
19635
- /* @__PURE__ */ jsx21(StyleSwatch, { style: style.value }),
19636
- /* @__PURE__ */ jsx21("span", { children: style.label }),
19637
- selected && /* @__PURE__ */ jsx21(Check2, { size: 16, className: "text-bridge-primary" })
19842
+ /* @__PURE__ */ jsx22(StyleSwatch, { style: style.value }),
19843
+ /* @__PURE__ */ jsx22("span", { children: style.label }),
19844
+ selected && /* @__PURE__ */ jsx22(Check3, { size: 16, className: "text-bridge-primary" })
19638
19845
  ]
19639
19846
  },
19640
19847
  style.value
@@ -19664,7 +19871,7 @@ function PricingCardToolbar({
19664
19871
  const style = pricingCardStyle(entry.tree, index);
19665
19872
  const featuredTag = pricingCardFeaturedTag(entry.tree, index);
19666
19873
  const mobile = window.innerWidth <= 480;
19667
- return /* @__PURE__ */ jsx21(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsxs12(
19874
+ return /* @__PURE__ */ jsx22(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsxs13(
19668
19875
  "div",
19669
19876
  {
19670
19877
  "data-ohw-bridge": "",
@@ -19676,7 +19883,7 @@ function PricingCardToolbar({
19676
19883
  ),
19677
19884
  style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
19678
19885
  children: [
19679
- /* @__PURE__ */ jsxs12(
19886
+ /* @__PURE__ */ jsxs13(
19680
19887
  "div",
19681
19888
  {
19682
19889
  className: cn(
@@ -19684,9 +19891,9 @@ function PricingCardToolbar({
19684
19891
  "shadow-[0px_10px_15px_-3px_rgba(0,0,0,0.1),0px_4px_6px_-4px_rgba(0,0,0,0.1)]",
19685
19892
  mobile ? "flex-wrap justify-center" : "flex-nowrap"
19686
19893
  ),
19687
- children: [
19688
- /* @__PURE__ */ jsxs12(Tooltip, { children: [
19689
- /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx21(
19894
+ children: [
19895
+ /* @__PURE__ */ jsxs13(Tooltip, { children: [
19896
+ /* @__PURE__ */ jsx22(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx22(
19690
19897
  CustomToolbarButton,
19691
19898
  {
19692
19899
  type: "button",
@@ -19694,7 +19901,7 @@ function PricingCardToolbar({
19694
19901
  "aria-pressed": featuredTag,
19695
19902
  onClick: () => onSetFeaturedTag(!featuredTag),
19696
19903
  className: "group size-8",
19697
- children: /* @__PURE__ */ jsx21(
19904
+ children: /* @__PURE__ */ jsx22(
19698
19905
  Star2,
19699
19906
  {
19700
19907
  size: 16,
@@ -19709,25 +19916,25 @@ function PricingCardToolbar({
19709
19916
  )
19710
19917
  }
19711
19918
  ) }),
19712
- /* @__PURE__ */ jsx21(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
19919
+ /* @__PURE__ */ jsx22(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
19713
19920
  ] }),
19714
- /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19715
- /* @__PURE__ */ jsx21(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
19716
- /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19717
- /* @__PURE__ */ jsx21(ToolbarAction, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ jsx21(ButtonGlyph, {}) }),
19718
- /* @__PURE__ */ jsx21(ToolbarAction, { label: "Remove button", onClick: onRemoveButton, disabled: buttonCount === 0, children: /* @__PURE__ */ jsxs12("span", { className: "relative inline-flex", children: [
19719
- /* @__PURE__ */ jsx21(ButtonGlyph, {}),
19720
- /* @__PURE__ */ jsx21(Minus, { size: 10, strokeWidth: 2.5, className: "absolute -right-1.5 -top-1.5 rounded-full bg-background text-bridge-primary" })
19921
+ /* @__PURE__ */ jsx22(ToolbarSeparator2, {}),
19922
+ /* @__PURE__ */ jsx22(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
19923
+ /* @__PURE__ */ jsx22(ToolbarSeparator2, {}),
19924
+ /* @__PURE__ */ jsx22(ToolbarAction2, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ jsx22(ButtonGlyph, {}) }),
19925
+ /* @__PURE__ */ jsx22(ToolbarAction2, { label: "Remove button", onClick: onRemoveButton, disabled: buttonCount === 0, children: /* @__PURE__ */ jsxs13("span", { className: "relative inline-flex", children: [
19926
+ /* @__PURE__ */ jsx22(ButtonGlyph, {}),
19927
+ /* @__PURE__ */ jsx22(Minus2, { size: 10, strokeWidth: 2.5, className: "absolute -right-1.5 -top-1.5 rounded-full bg-background text-bridge-primary" })
19721
19928
  ] }) }),
19722
- /* @__PURE__ */ jsx21(ToolbarAction, { label: "Add feature", onClick: onAddFeature, disabled: featureCount >= 10, children: /* @__PURE__ */ jsx21(FeatureGlyph, { mode: "add" }) }),
19723
- /* @__PURE__ */ jsx21(ToolbarAction, { label: "Remove feature", onClick: onRemoveFeature, disabled: featureCount <= 1, children: /* @__PURE__ */ jsx21(FeatureGlyph, { mode: "remove" }) }),
19724
- /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19725
- /* @__PURE__ */ jsx21(ToolbarAction, { label: "Add tier", onClick: onAddTier, disabled: collectionCount >= 4, children: /* @__PURE__ */ jsx21(Plus2, { size: 16, style: { pointerEvents: "none" } }) }),
19726
- /* @__PURE__ */ jsx21(ToolbarAction, { label: "Remove tier", onClick: onRemoveTier, disabled: collectionCount <= 1, children: /* @__PURE__ */ jsx21(Minus, { size: 16, style: { pointerEvents: "none" } }) })
19929
+ /* @__PURE__ */ jsx22(ToolbarAction2, { label: "Add feature", onClick: onAddFeature, disabled: featureCount >= 10, children: /* @__PURE__ */ jsx22(FeatureGlyph, { mode: "add" }) }),
19930
+ /* @__PURE__ */ jsx22(ToolbarAction2, { label: "Remove feature", onClick: onRemoveFeature, disabled: featureCount <= 1, children: /* @__PURE__ */ jsx22(FeatureGlyph, { mode: "remove" }) }),
19931
+ /* @__PURE__ */ jsx22(ToolbarSeparator2, {}),
19932
+ /* @__PURE__ */ jsx22(ToolbarAction2, { label: "Add tier", onClick: onAddTier, disabled: collectionCount >= 4, children: /* @__PURE__ */ jsx22(Plus3, { size: 16, style: { pointerEvents: "none" } }) }),
19933
+ /* @__PURE__ */ jsx22(ToolbarAction2, { label: "Remove tier", onClick: onRemoveTier, disabled: collectionCount <= 1, children: /* @__PURE__ */ jsx22(Minus2, { size: 16, style: { pointerEvents: "none" } }) })
19727
19934
  ]
19728
19935
  }
19729
19936
  ),
19730
- error && /* @__PURE__ */ jsx21("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19937
+ error && /* @__PURE__ */ jsx22("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19731
19938
  ]
19732
19939
  }
19733
19940
  ) });
@@ -19761,7 +19968,7 @@ function AiPricingCardOverlay({
19761
19968
  const overEditable = Boolean(t?.closest?.("[data-ohw-editable], [data-ohw-role], a, button"));
19762
19969
  setHover((prev) => {
19763
19970
  const next = ref && !overEditable ? ref : null;
19764
- return sameRef(prev, next) ? prev : next;
19971
+ return sameRef2(prev, next) ? prev : next;
19765
19972
  });
19766
19973
  };
19767
19974
  const onLeave = () => setHover(null);
@@ -19776,10 +19983,10 @@ function AiPricingCardOverlay({
19776
19983
  const onClick = (e) => {
19777
19984
  const t = e.target;
19778
19985
  if (!(t instanceof Element)) return;
19779
- if (t.closest(CHROME_SELECTOR)) return;
19986
+ if (t.closest(CHROME_SELECTOR2)) return;
19780
19987
  const ref = cardRefFromTarget(t);
19781
- setActive((prev) => sameRef(prev, ref) ? prev : ref);
19782
- if (!ref || !sameRef(activeRef.current, ref)) setError(null);
19988
+ setActive((prev) => sameRef2(prev, ref) ? prev : ref);
19989
+ if (!ref || !sameRef2(activeRef.current, ref)) setError(null);
19783
19990
  };
19784
19991
  document.addEventListener("click", onClick, true);
19785
19992
  return () => document.removeEventListener("click", onClick, true);
@@ -19844,12 +20051,12 @@ function AiPricingCardOverlay({
19844
20051
  const activeEntry = active ? findEntry(active.sectionId) : null;
19845
20052
  const activeCardRect = useLiveCardRect(active);
19846
20053
  const activeGridRect = useLiveCardRect(active, true);
19847
- const hoverRect = useLiveCardRect(hover && !sameRef(hover, active) ? hover : null);
19848
- return /* @__PURE__ */ jsxs12(Fragment8, { children: [
19849
- hoverRect && /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19850
- activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ jsxs12(Fragment8, { children: [
19851
- /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
19852
- /* @__PURE__ */ jsx21(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ jsx21(
20054
+ const hoverRect = useLiveCardRect(hover && !sameRef2(hover, active) ? hover : null);
20055
+ return /* @__PURE__ */ jsxs13(Fragment8, { children: [
20056
+ hoverRect && /* @__PURE__ */ jsx22(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
20057
+ activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ jsxs13(Fragment8, { children: [
20058
+ /* @__PURE__ */ jsx22(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
20059
+ /* @__PURE__ */ jsx22(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ jsx22(
19853
20060
  PricingCardToolbar,
19854
20061
  {
19855
20062
  entry: activeEntry,
@@ -19877,7 +20084,7 @@ function AiPricingCardOverlay({
19877
20084
  onRemoveFeature: () => runMutation(active.sectionId, (tree) => removePricingCardFeature(tree, active.index))
19878
20085
  }
19879
20086
  ) }),
19880
- /* @__PURE__ */ jsx21(
20087
+ /* @__PURE__ */ jsx22(
19881
20088
  "div",
19882
20089
  {
19883
20090
  "data-ohw-bridge": "",
@@ -19970,10 +20177,10 @@ function extractYouTubeId(input) {
19970
20177
  }
19971
20178
 
19972
20179
  // src/ui/ai-section/AiVideoOverlay.tsx
19973
- import { Fragment as Fragment9, jsx as jsx22, jsxs as jsxs13 } from "react/jsx-runtime";
20180
+ import { Fragment as Fragment9, jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
19974
20181
  var PRIMARY5 = "#0885FE";
19975
- var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY5})`;
19976
- var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
20182
+ var OHW_BLUE = `var(--ohw-primary, ${PRIMARY5})`;
20183
+ var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
19977
20184
  function findVideoBlockElement(sectionId, path) {
19978
20185
  const sectionEl = findByInstanceId(sectionId);
19979
20186
  if (!sectionEl) return null;
@@ -20033,12 +20240,12 @@ function requestValidateTree3(postToParent2, tree) {
20033
20240
  postToParent2({ type: "ow:ai-validate-tree-request", id, tree });
20034
20241
  });
20035
20242
  }
20036
- function Pill2({
20243
+ function Pill({
20037
20244
  label,
20038
20245
  onClick,
20039
20246
  disabled
20040
20247
  }) {
20041
- return /* @__PURE__ */ jsx22(
20248
+ return /* @__PURE__ */ jsx23(
20042
20249
  "button",
20043
20250
  {
20044
20251
  type: "button",
@@ -20054,7 +20261,7 @@ function Pill2({
20054
20261
  border: "none",
20055
20262
  cursor: disabled ? "default" : "pointer",
20056
20263
  background: "#ffffff",
20057
- color: OHW_BLUE_DARK2,
20264
+ color: OHW_BLUE_DARK,
20058
20265
  opacity: disabled ? 0.4 : 1,
20059
20266
  fontSize: 12,
20060
20267
  lineHeight: "16px",
@@ -20076,7 +20283,7 @@ function VideoUrlInput({ value, onCommit }) {
20076
20283
  setDraft(value ? watchUrl(value) : "");
20077
20284
  setInvalid(false);
20078
20285
  }, [value]);
20079
- const commit = () => {
20286
+ const commit2 = () => {
20080
20287
  const next = draft.trim();
20081
20288
  if (!next) {
20082
20289
  setInvalid(false);
@@ -20097,8 +20304,8 @@ function VideoUrlInput({ value, onCommit }) {
20097
20304
  onCommit(id);
20098
20305
  }
20099
20306
  };
20100
- return /* @__PURE__ */ jsxs13("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
20101
- /* @__PURE__ */ jsxs13(
20307
+ return /* @__PURE__ */ jsxs14("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
20308
+ /* @__PURE__ */ jsxs14(
20102
20309
  "div",
20103
20310
  {
20104
20311
  style: {
@@ -20114,12 +20321,12 @@ function VideoUrlInput({ value, onCommit }) {
20114
20321
  boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
20115
20322
  },
20116
20323
  children: [
20117
- /* @__PURE__ */ jsx22(
20324
+ /* @__PURE__ */ jsx23(
20118
20325
  "span",
20119
20326
  {
20120
20327
  style: {
20121
20328
  flex: "0 0 auto",
20122
- color: OHW_BLUE_DARK2,
20329
+ color: OHW_BLUE_DARK,
20123
20330
  fontSize: 10,
20124
20331
  lineHeight: "16px",
20125
20332
  fontWeight: 700,
@@ -20129,14 +20336,14 @@ function VideoUrlInput({ value, onCommit }) {
20129
20336
  children: "Video"
20130
20337
  }
20131
20338
  ),
20132
- /* @__PURE__ */ jsx22(
20339
+ /* @__PURE__ */ jsx23(
20133
20340
  "input",
20134
20341
  {
20135
20342
  type: "text",
20136
20343
  inputMode: "url",
20137
20344
  value: draft,
20138
20345
  onChange: (e) => handleChange(e.target.value),
20139
- onBlur: commit,
20346
+ onBlur: commit2,
20140
20347
  onKeyDown: (e) => {
20141
20348
  if (e.key === "Enter") e.currentTarget.blur();
20142
20349
  if (e.key === "Escape") {
@@ -20166,7 +20373,7 @@ function VideoUrlInput({ value, onCommit }) {
20166
20373
  ]
20167
20374
  }
20168
20375
  ),
20169
- invalid && /* @__PURE__ */ jsx22("div", { style: { color: "#FCA5A5", fontSize: 11, textAlign: "center" }, children: "That doesn't look like a YouTube link \u2014 try pasting the video URL again." })
20376
+ invalid && /* @__PURE__ */ jsx23("div", { style: { color: "#FCA5A5", fontSize: 11, textAlign: "center" }, children: "That doesn't look like a YouTube link \u2014 try pasting the video URL again." })
20170
20377
  ] });
20171
20378
  }
20172
20379
  function VideoToolbar({
@@ -20176,7 +20383,7 @@ function VideoToolbar({
20176
20383
  onSetVideoId,
20177
20384
  onRemove
20178
20385
  }) {
20179
- return /* @__PURE__ */ jsxs13(
20386
+ return /* @__PURE__ */ jsxs14(
20180
20387
  "div",
20181
20388
  {
20182
20389
  "data-ohw-bridge": "",
@@ -20188,7 +20395,7 @@ function VideoToolbar({
20188
20395
  gap: 6,
20189
20396
  alignItems: "center",
20190
20397
  width: 280,
20191
- background: OHW_BLUE2,
20398
+ background: OHW_BLUE,
20192
20399
  padding: 8,
20193
20400
  borderRadius: 12,
20194
20401
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -20196,9 +20403,9 @@ function VideoToolbar({
20196
20403
  opacity: pending ? 0.6 : 1
20197
20404
  },
20198
20405
  children: [
20199
- /* @__PURE__ */ jsx22(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
20200
- /* @__PURE__ */ jsx22(Pill2, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
20201
- error && /* @__PURE__ */ jsx22("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20406
+ /* @__PURE__ */ jsx23(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
20407
+ /* @__PURE__ */ jsx23(Pill, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
20408
+ error && /* @__PURE__ */ jsx23("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20202
20409
  ]
20203
20410
  }
20204
20411
  );
@@ -20318,8 +20525,8 @@ function AiVideoOverlay({
20318
20525
  const activeBlockRect = useLiveBlockRect(active);
20319
20526
  const TOOLBAR_APPROX_H = 90;
20320
20527
  const toolbarState = activeBlockRect && activeBlockRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
20321
- return /* @__PURE__ */ jsxs13(Fragment9, { children: [
20322
- hoverHandleRect && hover && /* @__PURE__ */ jsx22(
20528
+ return /* @__PURE__ */ jsxs14(Fragment9, { children: [
20529
+ hoverHandleRect && hover && /* @__PURE__ */ jsx23(
20323
20530
  "button",
20324
20531
  {
20325
20532
  type: "button",
@@ -20347,15 +20554,15 @@ function AiVideoOverlay({
20347
20554
  fontSize: 14,
20348
20555
  fontWeight: 700
20349
20556
  },
20350
- children: /* @__PURE__ */ jsx22(Star3, { size: 14, style: { pointerEvents: "none" } })
20557
+ children: /* @__PURE__ */ jsx23(Star3, { size: 14, style: { pointerEvents: "none" } })
20351
20558
  }
20352
20559
  ),
20353
- activeBlockRect && active && activeEntry && /* @__PURE__ */ jsx22(
20560
+ activeBlockRect && active && activeEntry && /* @__PURE__ */ jsx23(
20354
20561
  ItemInteractionLayer,
20355
20562
  {
20356
20563
  rect: activeBlockRect,
20357
20564
  state: toolbarState,
20358
- toolbar: /* @__PURE__ */ jsx22(
20565
+ toolbar: /* @__PURE__ */ jsx23(
20359
20566
  VideoToolbar,
20360
20567
  {
20361
20568
  videoId: videoIdAt(activeEntry.tree, active.path),
@@ -20430,10 +20637,10 @@ function setCarouselAutoplay(tree, on) {
20430
20637
  }
20431
20638
 
20432
20639
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
20433
- import { Fragment as Fragment10, jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
20640
+ import { Fragment as Fragment10, jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
20434
20641
  var PRIMARY6 = "#0885FE";
20435
- var OHW_BLUE3 = `var(--ohw-primary, ${PRIMARY6})`;
20436
- var OHW_BLUE_DARK3 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
20642
+ var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY6})`;
20643
+ var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
20437
20644
  function findCarouselItemElement(sectionId, index) {
20438
20645
  const sectionEl = findByInstanceId(sectionId);
20439
20646
  if (!sectionEl) return null;
@@ -20512,8 +20719,8 @@ function requestAssetPick2(postToParent2) {
20512
20719
  postToParent2({ type: "ow:asset-pick", id });
20513
20720
  });
20514
20721
  }
20515
- function Pill3({ label, onClick, disabled }) {
20516
- return /* @__PURE__ */ jsx23(
20722
+ function Pill2({ label, onClick, disabled }) {
20723
+ return /* @__PURE__ */ jsx24(
20517
20724
  "button",
20518
20725
  {
20519
20726
  type: "button",
@@ -20529,7 +20736,7 @@ function Pill3({ label, onClick, disabled }) {
20529
20736
  border: "none",
20530
20737
  cursor: disabled ? "default" : "pointer",
20531
20738
  background: "#ffffff",
20532
- color: OHW_BLUE_DARK3,
20739
+ color: OHW_BLUE_DARK2,
20533
20740
  opacity: disabled ? 0.4 : 1,
20534
20741
  fontSize: 12,
20535
20742
  lineHeight: "16px",
@@ -20547,7 +20754,7 @@ function TogglePill({
20547
20754
  onToggle,
20548
20755
  disabled
20549
20756
  }) {
20550
- return /* @__PURE__ */ jsxs14(
20757
+ return /* @__PURE__ */ jsxs15(
20551
20758
  "button",
20552
20759
  {
20553
20760
  type: "button",
@@ -20566,7 +20773,7 @@ function TogglePill({
20566
20773
  border: "none",
20567
20774
  cursor: disabled ? "default" : "pointer",
20568
20775
  background: "#ffffff",
20569
- color: OHW_BLUE_DARK3,
20776
+ color: OHW_BLUE_DARK2,
20570
20777
  opacity: disabled ? 0.4 : 1,
20571
20778
  fontSize: 12,
20572
20779
  lineHeight: "16px",
@@ -20576,7 +20783,7 @@ function TogglePill({
20576
20783
  },
20577
20784
  children: [
20578
20785
  label,
20579
- /* @__PURE__ */ jsx23(
20786
+ /* @__PURE__ */ jsx24(
20580
20787
  "span",
20581
20788
  {
20582
20789
  "aria-hidden": true,
@@ -20589,10 +20796,10 @@ function TogglePill({
20589
20796
  // Off is a grey track, not a washed-out blue: the pill's own white background already
20590
20797
  // carries the brand colour in the text, and a pale blue track next to it read as
20591
20798
  // "disabled control" rather than "switch that is off".
20592
- background: on ? OHW_BLUE_DARK3 : "#CBD5E1",
20799
+ background: on ? OHW_BLUE_DARK2 : "#CBD5E1",
20593
20800
  transition: "background 120ms ease"
20594
20801
  },
20595
- children: /* @__PURE__ */ jsx23(
20802
+ children: /* @__PURE__ */ jsx24(
20596
20803
  "span",
20597
20804
  {
20598
20805
  style: {
@@ -20621,7 +20828,7 @@ function CarouselSlideToolbar({
20621
20828
  onChangeImage,
20622
20829
  onToggleAutoplay
20623
20830
  }) {
20624
- return /* @__PURE__ */ jsxs14(
20831
+ return /* @__PURE__ */ jsxs15(
20625
20832
  "div",
20626
20833
  {
20627
20834
  "data-ohw-bridge": "",
@@ -20632,7 +20839,7 @@ function CarouselSlideToolbar({
20632
20839
  flexDirection: "column",
20633
20840
  gap: 6,
20634
20841
  alignItems: "center",
20635
- background: OHW_BLUE3,
20842
+ background: OHW_BLUE2,
20636
20843
  padding: 8,
20637
20844
  borderRadius: 12,
20638
20845
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -20644,11 +20851,11 @@ function CarouselSlideToolbar({
20644
20851
  pointerEvents: pending ? "none" : "auto"
20645
20852
  },
20646
20853
  children: [
20647
- /* @__PURE__ */ jsxs14("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20648
- /* @__PURE__ */ jsx23(Pill3, { label: "Change Image", onClick: onChangeImage }),
20649
- /* @__PURE__ */ jsx23(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
20854
+ /* @__PURE__ */ jsxs15("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20855
+ /* @__PURE__ */ jsx24(Pill2, { label: "Change Image", onClick: onChangeImage }),
20856
+ /* @__PURE__ */ jsx24(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
20650
20857
  ] }),
20651
- error && /* @__PURE__ */ jsx23("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20858
+ error && /* @__PURE__ */ jsx24("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20652
20859
  ]
20653
20860
  }
20654
20861
  );
@@ -20788,8 +20995,8 @@ function AiFadeCarouselOverlay({
20788
20995
  const activeItemRect = useLiveItemRect2(active);
20789
20996
  const TOOLBAR_APPROX_H = 90;
20790
20997
  const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
20791
- return /* @__PURE__ */ jsxs14(Fragment10, { children: [
20792
- hoverHandleRect && hover && /* @__PURE__ */ jsx23(
20998
+ return /* @__PURE__ */ jsxs15(Fragment10, { children: [
20999
+ hoverHandleRect && hover && /* @__PURE__ */ jsx24(
20793
21000
  "button",
20794
21001
  {
20795
21002
  type: "button",
@@ -20818,10 +21025,10 @@ function AiFadeCarouselOverlay({
20818
21025
  fontSize: 14,
20819
21026
  fontWeight: 700
20820
21027
  },
20821
- children: /* @__PURE__ */ jsx23(Star4, { size: 14, style: { pointerEvents: "none" } })
21028
+ children: /* @__PURE__ */ jsx24(Star4, { size: 14, style: { pointerEvents: "none" } })
20822
21029
  }
20823
21030
  ),
20824
- swapping && /* @__PURE__ */ jsx23(
21031
+ swapping && /* @__PURE__ */ jsx24(
20825
21032
  "div",
20826
21033
  {
20827
21034
  ref: skeletonRef,
@@ -20839,15 +21046,15 @@ function AiFadeCarouselOverlay({
20839
21046
  zIndex: 2147483646,
20840
21047
  pointerEvents: "none"
20841
21048
  },
20842
- children: /* @__PURE__ */ jsx23("style", { children: MEDIA_SKELETON_CSS })
21049
+ children: /* @__PURE__ */ jsx24("style", { children: MEDIA_SKELETON_CSS })
20843
21050
  }
20844
21051
  ),
20845
- activeItemRect && active && activeEntry && /* @__PURE__ */ jsx23(
21052
+ activeItemRect && active && activeEntry && /* @__PURE__ */ jsx24(
20846
21053
  ItemInteractionLayer,
20847
21054
  {
20848
21055
  rect: activeItemRect,
20849
21056
  state: toolbarState,
20850
- toolbar: /* @__PURE__ */ jsx23(
21057
+ toolbar: /* @__PURE__ */ jsx24(
20851
21058
  CarouselSlideToolbar,
20852
21059
  {
20853
21060
  pending,
@@ -21065,118 +21272,11 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
21065
21272
  // src/ui/link-modal/LinkPopover.tsx
21066
21273
  import { useEffect as useEffect13 } from "react";
21067
21274
 
21068
- // src/ui/dialog.tsx
21069
- import * as React9 from "react";
21070
- import { Dialog as DialogPrimitive } from "radix-ui";
21071
- import { X as X2 } from "lucide-react";
21072
- import { jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
21073
- function Dialog2({
21074
- ...props
21075
- }) {
21076
- return /* @__PURE__ */ jsx24(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
21077
- }
21078
- function DialogPortal({
21079
- ...props
21080
- }) {
21081
- return /* @__PURE__ */ jsx24(DialogPrimitive.Portal, { ...props });
21082
- }
21083
- function DialogOverlay({
21084
- className,
21085
- ...props
21086
- }) {
21087
- return /* @__PURE__ */ jsx24(
21088
- DialogPrimitive.Overlay,
21089
- {
21090
- "data-slot": "dialog-overlay",
21091
- "data-ohw-link-modal-root": "",
21092
- className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
21093
- ...props
21094
- }
21095
- );
21096
- }
21097
- var DialogContent = React9.forwardRef(
21098
- ({ className, children, showCloseButton = true, container, ...props }, ref) => {
21099
- const positionMode = container ? "absolute" : "fixed";
21100
- return /* @__PURE__ */ jsxs15(DialogPortal, { container: container ?? void 0, children: [
21101
- /* @__PURE__ */ jsx24(DialogOverlay, { className: cn(positionMode, "inset-0") }),
21102
- /* @__PURE__ */ jsxs15(
21103
- DialogPrimitive.Content,
21104
- {
21105
- ref,
21106
- "data-slot": "dialog-content",
21107
- className: cn(
21108
- positionMode,
21109
- "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",
21110
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
21111
- container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
21112
- className
21113
- ),
21114
- onMouseDown: (e) => e.stopPropagation(),
21115
- ...props,
21116
- children: [
21117
- children,
21118
- showCloseButton ? /* @__PURE__ */ jsx24(
21119
- DialogPrimitive.Close,
21120
- {
21121
- type: "button",
21122
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
21123
- "aria-label": "Close",
21124
- children: /* @__PURE__ */ jsx24(X2, { size: 16, "aria-hidden": true })
21125
- }
21126
- ) : null
21127
- ]
21128
- }
21129
- )
21130
- ] });
21131
- }
21132
- );
21133
- DialogContent.displayName = DialogPrimitive.Content.displayName;
21134
- function DialogHeader({
21135
- className,
21136
- ...props
21137
- }) {
21138
- return /* @__PURE__ */ jsx24("div", { className: cn("flex flex-col gap-1.5", className), ...props });
21139
- }
21140
- function DialogFooter({
21141
- className,
21142
- ...props
21143
- }) {
21144
- return /* @__PURE__ */ jsx24(
21145
- "div",
21146
- {
21147
- className: cn("flex items-center justify-end gap-2", className),
21148
- ...props
21149
- }
21150
- );
21151
- }
21152
- var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx24(
21153
- DialogPrimitive.Title,
21154
- {
21155
- ref,
21156
- className: cn(
21157
- "text-lg font-semibold leading-none tracking-tight text-card-foreground",
21158
- className
21159
- ),
21160
- ...props
21161
- }
21162
- ));
21163
- DialogTitle.displayName = DialogPrimitive.Title.displayName;
21164
- var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx24(
21165
- DialogPrimitive.Description,
21166
- {
21167
- ref,
21168
- className: cn("text-sm text-muted-foreground", className),
21169
- ...props
21170
- }
21171
- ));
21172
- DialogDescription.displayName = DialogPrimitive.Description.displayName;
21173
- var DialogClose = DialogPrimitive.Close;
21174
-
21175
21275
  // src/ui/link-modal/LinkEditorPanel.tsx
21176
21276
  import { Info, X as X4 } from "lucide-react";
21177
21277
 
21178
21278
  // src/ui/link-modal/DestinationBreadcrumb.tsx
21179
- import { ArrowRight as ArrowRight2, File, GalleryVertical } from "lucide-react";
21279
+ import { ArrowRight as ArrowRight3, File, GalleryVertical } from "lucide-react";
21180
21280
  import { jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
21181
21281
  function DestinationBreadcrumb({
21182
21282
  pageTitle,
@@ -21190,7 +21290,7 @@ function DestinationBreadcrumb({
21190
21290
  /* @__PURE__ */ jsx25("span", { className: "text-sm font-medium leading-none text-foreground!", children: pageTitle })
21191
21291
  ] }),
21192
21292
  /* @__PURE__ */ jsx25(
21193
- ArrowRight2,
21293
+ ArrowRight3,
21194
21294
  {
21195
21295
  size: 16,
21196
21296
  className: "shrink-0 text-muted-foreground",
@@ -21302,7 +21402,7 @@ function Label({ className, ...props }) {
21302
21402
  }
21303
21403
 
21304
21404
  // src/ui/link-modal/UrlOrPageInput.tsx
21305
- import { ChevronDown as ChevronDown4, File as File2, X as X3 } from "lucide-react";
21405
+ import { ChevronDown as ChevronDown5, File as File2, X as X3 } from "lucide-react";
21306
21406
  import { jsx as jsx29, jsxs as jsxs18 } from "react/jsx-runtime";
21307
21407
  function FieldChevron({
21308
21408
  onClick
@@ -21315,7 +21415,7 @@ function FieldChevron({
21315
21415
  onClick,
21316
21416
  "aria-label": "Open page list",
21317
21417
  tabIndex: -1,
21318
- children: /* @__PURE__ */ jsx29(ChevronDown4, { size: 16 })
21418
+ children: /* @__PURE__ */ jsx29(ChevronDown5, { size: 16 })
21319
21419
  }
21320
21420
  );
21321
21421
  }
@@ -21545,7 +21645,7 @@ function LinkEditorPanel({ state, onClose }) {
21545
21645
  // src/ui/link-modal/SectionPickerOverlay.tsx
21546
21646
  import { useCallback as useCallback7, useEffect as useEffect11, useMemo as useMemo2, useRef as useRef10, useState as useState13 } from "react";
21547
21647
  import { createPortal as createPortal2 } from "react-dom";
21548
- import { ArrowLeft as ArrowLeft2, Check as Check3 } from "lucide-react";
21648
+ import { ArrowLeft as ArrowLeft3, Check as Check4 } from "lucide-react";
21549
21649
  import { usePathname, useRouter as useRouter2 } from "next/navigation";
21550
21650
 
21551
21651
  // src/lib/parent-scroll.ts
@@ -21822,7 +21922,7 @@ function SectionPickerOverlay({
21822
21922
  className: "h-8 min-w-0 gap-1 border-border bg-background px-2 py-1.5 shadow-sm hover:bg-muted cursor-pointer",
21823
21923
  onClick: onBack,
21824
21924
  children: [
21825
- /* @__PURE__ */ jsx31(ArrowLeft2, { className: "size-4 shrink-0", "aria-hidden": true }),
21925
+ /* @__PURE__ */ jsx31(ArrowLeft3, { className: "size-4 shrink-0", "aria-hidden": true }),
21826
21926
  "Back"
21827
21927
  ]
21828
21928
  }
@@ -21891,7 +21991,7 @@ function SectionPickerOverlay({
21891
21991
  className: "absolute right-3 top-3 flex size-8 items-center justify-center rounded-full text-white",
21892
21992
  style: { backgroundColor: "var(--ohw-primary, #0885fe)" },
21893
21993
  "aria-hidden": true,
21894
- children: /* @__PURE__ */ jsx31(Check3, { className: "size-5" })
21994
+ children: /* @__PURE__ */ jsx31(Check4, { className: "size-5" })
21895
21995
  }
21896
21996
  ) : null
21897
21997
  ]
@@ -22275,6 +22375,7 @@ function shouldUseDevFixtures() {
22275
22375
  function getLogoElement(el) {
22276
22376
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
22277
22377
  if (marked) return marked;
22378
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
22278
22379
  const root = el.closest("nav, [data-ohw-nav-root], footer");
22279
22380
  if (!root) return null;
22280
22381
  const anchor = el.closest("a");
@@ -22344,7 +22445,7 @@ function addFooterColumnWithPersist({
22344
22445
  import {
22345
22446
  useCallback as useCallback9,
22346
22447
  useEffect as useEffect14,
22347
- useLayoutEffect as useLayoutEffect5,
22448
+ useLayoutEffect as useLayoutEffect6,
22348
22449
  useRef as useRef11,
22349
22450
  useState as useState15
22350
22451
  } from "react";
@@ -22441,7 +22542,7 @@ function FloatingPanel({
22441
22542
  return () => window.removeEventListener("message", onParentScroll);
22442
22543
  }, [open]);
22443
22544
  const positionRef = useRef11(position);
22444
- useLayoutEffect5(() => {
22545
+ useLayoutEffect6(() => {
22445
22546
  if (!open) {
22446
22547
  positionRef.current = position;
22447
22548
  return;
@@ -22451,7 +22552,7 @@ function FloatingPanel({
22451
22552
  }
22452
22553
  positionRef.current = position;
22453
22554
  }, [open, position, liveScroll, parentScroll]);
22454
- useLayoutEffect5(() => {
22555
+ useLayoutEffect6(() => {
22455
22556
  if (!open || !panelRef.current) return;
22456
22557
  const el = panelRef.current;
22457
22558
  const header = el.querySelector("[data-ohw-floating-panel-header]");
@@ -22660,7 +22761,7 @@ function SocialsDisplayPanel({ display, onChange, className }) {
22660
22761
  }
22661
22762
 
22662
22763
  // src/ui/button-style-panel.tsx
22663
- import { Check as Check4 } from "lucide-react";
22764
+ import { Check as Check5 } from "lucide-react";
22664
22765
  import { jsx as jsx35, jsxs as jsxs24 } from "react/jsx-runtime";
22665
22766
  var SIZE_LABEL = { s: "S", m: "M", l: "L" };
22666
22767
  function ButtonStylePanel({
@@ -22731,7 +22832,7 @@ function ButtonStylePanel({
22731
22832
  {
22732
22833
  className: "absolute right-0.5 top-0.5 flex size-5 items-center justify-center rounded-full bg-primary text-primary-foreground",
22733
22834
  "aria-hidden": true,
22734
- children: /* @__PURE__ */ jsx35(Check4, { className: "size-3", strokeWidth: 3 })
22835
+ children: /* @__PURE__ */ jsx35(Check5, { className: "size-3", strokeWidth: 3 })
22735
22836
  }
22736
22837
  ) : null
22737
22838
  ]
@@ -23756,7 +23857,7 @@ function useSectionDrag({
23756
23857
  }
23757
23858
 
23758
23859
  // src/ui/footer-container-chrome.tsx
23759
- import { Plus as Plus3 } from "lucide-react";
23860
+ import { Plus as Plus4 } from "lucide-react";
23760
23861
  import { jsx as jsx36, jsxs as jsxs25 } from "react/jsx-runtime";
23761
23862
  function FooterContainerChrome({
23762
23863
  rect,
@@ -23797,7 +23898,7 @@ function FooterContainerChrome({
23797
23898
  if (addDisabled) return;
23798
23899
  onAdd();
23799
23900
  },
23800
- children: /* @__PURE__ */ jsx36(Plus3, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
23901
+ children: /* @__PURE__ */ jsx36(Plus4, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
23801
23902
  }
23802
23903
  ) }),
23803
23904
  /* @__PURE__ */ jsx36(TooltipContent, { side: "bottom", sideOffset: 9, children: addDisabled ? "Maximum columns reached" : "Add item" })
@@ -23975,8 +24076,104 @@ function deleteSelectedNavFooterItem(deps) {
23975
24076
  return false;
23976
24077
  }
23977
24078
 
24079
+ // src/lib/nav-page-sync.ts
24080
+ function labelKeyFor(hrefKey) {
24081
+ return hrefKey.replace(/-href$/, "-label");
24082
+ }
24083
+ function isPageLinkKey(key) {
24084
+ return isNavbarHrefKey(key) || isFooterHrefKey(key);
24085
+ }
24086
+ function currentHref(key, anchor, content) {
24087
+ const stored = content[key];
24088
+ if (typeof stored === "string" && stored.length > 0) return stored;
24089
+ return anchor.getAttribute("href") ?? "";
24090
+ }
24091
+ function normalizePath2(value) {
24092
+ if (value.length > 1 && value.endsWith("/")) return value.slice(0, -1);
24093
+ return value;
24094
+ }
24095
+ function pointsAt(href, path) {
24096
+ const normalized = normalizePath2(href);
24097
+ const target = normalizePath2(path);
24098
+ if (normalized === target) return true;
24099
+ return (normalized.startsWith(`${target}#`) || normalized.startsWith(`${target}?`)) && target !== "/";
24100
+ }
24101
+ function retargetHref(href, oldPath, newPath) {
24102
+ const normalized = normalizePath2(href);
24103
+ const target = normalizePath2(oldPath);
24104
+ if (normalized === target) return newPath;
24105
+ return `${newPath}${normalized.slice(target.length)}`;
24106
+ }
24107
+ function setLabelText(key, text) {
24108
+ document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`).forEach((el) => {
24109
+ el.textContent = text;
24110
+ });
24111
+ }
24112
+ function commit(delta, deps) {
24113
+ if (Object.keys(delta).length === 0) return;
24114
+ deps.setEditContent({ ...deps.getEditContent(), ...delta });
24115
+ deps.postToParent({
24116
+ type: "ow:change",
24117
+ nodes: Object.entries(delta).map(([key, text]) => ({ key, text }))
24118
+ });
24119
+ }
24120
+ function navLinksTo(path, content) {
24121
+ return Array.from(document.querySelectorAll("[data-ohw-href-key]")).filter((anchor) => {
24122
+ const key = anchor.getAttribute("data-ohw-href-key");
24123
+ return isNavbarHrefKey(key) && pointsAt(currentHref(key, anchor, content), path);
24124
+ }).map((anchor) => anchor.getAttribute("data-ohw-href-key"));
24125
+ }
24126
+ function addNavLinkForPage(page, deps) {
24127
+ const content = deps.getEditContent();
24128
+ if (navLinksTo(page.path, content).length > 0) return false;
24129
+ const roots = listNavbarRootItems();
24130
+ const afterAnchor = roots[roots.length - 1] ?? null;
24131
+ let inserted;
24132
+ try {
24133
+ inserted = insertNavbarItem(page.path, page.title, afterAnchor);
24134
+ } catch {
24135
+ return false;
24136
+ }
24137
+ const { hrefKey, labelKey, index } = inserted;
24138
+ deps.applyLinkByKey(hrefKey, page.path);
24139
+ setLabelText(labelKey, page.title);
24140
+ commit(
24141
+ {
24142
+ [hrefKey]: page.path,
24143
+ [labelKey]: page.title,
24144
+ [NAV_COUNT_KEY]: String(index + 1),
24145
+ [NAV_ORDER_KEY]: getNavbarOrderJson()
24146
+ },
24147
+ deps
24148
+ );
24149
+ return true;
24150
+ }
24151
+ function retargetPageLinks(change, deps) {
24152
+ const { oldPath, newPath, oldTitle, newTitle } = change;
24153
+ if (!oldPath || !newPath || normalizePath2(oldPath) === normalizePath2(newPath)) return 0;
24154
+ const content = deps.getEditContent();
24155
+ const delta = {};
24156
+ for (const anchor of Array.from(document.querySelectorAll("[data-ohw-href-key]"))) {
24157
+ const hrefKey = anchor.getAttribute("data-ohw-href-key");
24158
+ if (!isPageLinkKey(hrefKey)) continue;
24159
+ const href = currentHref(hrefKey, anchor, content);
24160
+ if (!pointsAt(href, oldPath)) continue;
24161
+ const nextHref = retargetHref(href, oldPath, newPath);
24162
+ deps.applyLinkByKey(hrefKey, nextHref);
24163
+ delta[hrefKey] = nextHref;
24164
+ if (!oldTitle || !newTitle) continue;
24165
+ const labelKey = labelKeyFor(hrefKey);
24166
+ const label = content[labelKey] ?? anchor.querySelector('[data-ohw-editable="text"]')?.textContent ?? "";
24167
+ if (label.trim() !== oldTitle) continue;
24168
+ setLabelText(labelKey, newTitle);
24169
+ delta[labelKey] = newTitle;
24170
+ }
24171
+ commit(delta, deps);
24172
+ return Object.keys(delta).filter((key) => key.endsWith("-href")).length;
24173
+ }
24174
+
23978
24175
  // src/ui/navbar-container-chrome.tsx
23979
- import { Plus as Plus4 } from "lucide-react";
24176
+ import { Plus as Plus5 } from "lucide-react";
23980
24177
  import { jsx as jsx37 } from "react/jsx-runtime";
23981
24178
  function NavbarContainerChrome({
23982
24179
  rect,
@@ -24012,7 +24209,7 @@ function NavbarContainerChrome({
24012
24209
  e.stopPropagation();
24013
24210
  onAdd();
24014
24211
  },
24015
- children: /* @__PURE__ */ jsx37(Plus4, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
24212
+ children: /* @__PURE__ */ jsx37(Plus5, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
24016
24213
  }
24017
24214
  )
24018
24215
  }
@@ -24084,7 +24281,7 @@ function Badge({ className, variant, ...props }) {
24084
24281
  }
24085
24282
 
24086
24283
  // src/OhhwellsBridge.tsx
24087
- import { Brush as Brush2, Link as Link2, Plus as Plus5, Settings } from "lucide-react";
24284
+ import { Brush as Brush2, Link as Link2, Plus as Plus6, Settings } from "lucide-react";
24088
24285
  import { Fragment as Fragment13, jsx as jsx40, jsxs as jsxs26 } from "react/jsx-runtime";
24089
24286
  var PRIMARY7 = "#0885FE";
24090
24287
  var IMAGE_FADE_MS = 300;
@@ -24862,7 +25059,7 @@ function getNavigationSelectionParent(el) {
24862
25059
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
24863
25060
  return resolveFooterLinksContainerFrom(el);
24864
25061
  }
24865
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
25062
+ 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)) {
24866
25063
  return getNavigationRoot(el);
24867
25064
  }
24868
25065
  return null;
@@ -25084,7 +25281,6 @@ var ICONS = {
25084
25281
  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"/>',
25085
25282
  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"/>'
25086
25283
  };
25087
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
25088
25284
  var SELECTION_CHROME_GAP2 = 4;
25089
25285
  var TOOLBAR_STROKE_GAP2 = 4;
25090
25286
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -25182,7 +25378,7 @@ function applyVisibleViewport(el, parentScroll) {
25182
25378
  el.style.top = `${top}px`;
25183
25379
  el.style.height = `${height}px`;
25184
25380
  }
25185
- var TOOLBAR_EDGE_MARGIN3 = 4;
25381
+ var TOOLBAR_EDGE_MARGIN4 = 4;
25186
25382
  function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
25187
25383
  const isAbove = transform.includes("translateY(-100%)");
25188
25384
  let visualTop = isAbove ? top - approxH : top;
@@ -25209,8 +25405,8 @@ function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
25209
25405
  function clampToolbarLeft(centerX, toolbarWidth) {
25210
25406
  const half = toolbarWidth / 2;
25211
25407
  return Math.max(
25212
- TOOLBAR_EDGE_MARGIN3 + half,
25213
- Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half)
25408
+ TOOLBAR_EDGE_MARGIN4 + half,
25409
+ Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN4 - half)
25214
25410
  );
25215
25411
  }
25216
25412
  function calcToolbarPos(rect, parentScroll, toolbarW = 306) {
@@ -25611,7 +25807,7 @@ function OhhwellsBridge() {
25611
25807
  }, []);
25612
25808
  const subdomainFromQuery = searchParams.get("subdomain");
25613
25809
  const [subdomain, setSubdomain] = useState18(() => resolveSubdomain(subdomainFromQuery));
25614
- useLayoutEffect6(() => {
25810
+ useLayoutEffect7(() => {
25615
25811
  const next = resolveSubdomain(subdomainFromQuery);
25616
25812
  if (next !== subdomain) setSubdomain(next);
25617
25813
  if (next) return;
@@ -26090,21 +26286,6 @@ function OhhwellsBridge() {
26090
26286
  const [isItemDragging, setIsItemDragging] = useState18(false);
26091
26287
  const [isFooterFrameSelection, setIsFooterFrameSelection] = useState18(false);
26092
26288
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
26093
- const [floatingPanel, setFloatingPanel] = useState18(null);
26094
- const floatingPanelOpenRef = useRef14(false);
26095
- floatingPanelOpenRef.current = floatingPanel !== null;
26096
- const floatingPanelRef = useRef14(null);
26097
- floatingPanelRef.current = floatingPanel;
26098
- const [floatingPanelPos, setFloatingPanelPos] = useState18(null);
26099
- const buttonCollectionDragRef = useRef14(null);
26100
- const buttonDropInsertIndexRef = useRef14(null);
26101
- const [buttonDropSlots, setButtonDropSlots] = useState18([]);
26102
- const [activeButtonDropIndex, setActiveButtonDropIndex] = useState18(null);
26103
- const buttonPointerDragRef = useRef14(null);
26104
- const [buttonStyleTick, setButtonStyleTick] = useState18(0);
26105
- const [logoSizeDraft, setLogoSizeDraft] = useState18(null);
26106
- const [editorViewport, setEditorViewport] = useState18("desktop");
26107
- const [parentScrollSnap, setParentScrollSnap] = useState18(null);
26108
26289
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = useState18(null);
26109
26290
  const [footerHeadingVisible, setFooterHeadingVisible] = useState18(null);
26110
26291
  const footerDragRef = useRef14(null);
@@ -26130,6 +26311,21 @@ function OhhwellsBridge() {
26130
26311
  const stylesRef = useRef14("");
26131
26312
  const appliedDrawerRef = useRef14(null);
26132
26313
  const pendingDeleteUndoRef = useRef14(null);
26314
+ const [floatingPanel, setFloatingPanel] = useState18(null);
26315
+ const floatingPanelOpenRef = useRef14(false);
26316
+ const floatingPanelRef = useRef14(null);
26317
+ floatingPanelRef.current = floatingPanel;
26318
+ const setFloatingPanelRef = useRef14(setFloatingPanel);
26319
+ const [floatingPanelPos, setFloatingPanelPos] = useState18(null);
26320
+ const buttonCollectionDragRef = useRef14(null);
26321
+ const buttonDropInsertIndexRef = useRef14(null);
26322
+ const [buttonDropSlots, setButtonDropSlots] = useState18([]);
26323
+ const [activeButtonDropIndex, setActiveButtonDropIndex] = useState18(null);
26324
+ const buttonPointerDragRef = useRef14(null);
26325
+ const [buttonStyleTick, setButtonStyleTick] = useState18(0);
26326
+ const [logoSizeDraft, setLogoSizeDraft] = useState18(null);
26327
+ const [editorViewport, setEditorViewport] = useState18("desktop");
26328
+ const [parentScrollSnap, setParentScrollSnap] = useState18(null);
26133
26329
  const [sitePages, setSitePages] = useState18([]);
26134
26330
  const collectionItemsRef = useRef14(/* @__PURE__ */ new Map());
26135
26331
  const [sectionsByPath, setSectionsByPath] = useState18({});
@@ -26139,7 +26335,18 @@ function OhhwellsBridge() {
26139
26335
  const linkPopoverOpenRef = useRef14(false);
26140
26336
  const linkPopoverGraceUntilRef = useRef14(0);
26141
26337
  setLinkPopoverRef.current = setLinkPopover;
26338
+ setFloatingPanelRef.current = setFloatingPanel;
26142
26339
  linkPopoverSessionRef.current = linkPopover;
26340
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
26341
+ useEffect17(() => {
26342
+ const syncViewport = () => {
26343
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
26344
+ setEditorViewport((prev) => prev === next ? prev : next);
26345
+ };
26346
+ syncViewport();
26347
+ window.addEventListener("resize", syncViewport);
26348
+ return () => window.removeEventListener("resize", syncViewport);
26349
+ }, []);
26143
26350
  useEffect17(() => {
26144
26351
  setSectionOrderPersistHandler((entries) => {
26145
26352
  const currentPath = window.location.pathname;
@@ -26288,6 +26495,43 @@ function OhhwellsBridge() {
26288
26495
  if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
26289
26496
  return () => window.removeEventListener("message", onSitePages);
26290
26497
  }, [isEditMode, postToParent2]);
26498
+ useEffect17(() => {
26499
+ if (!isEditMode) return;
26500
+ const deps = {
26501
+ getEditContent: () => editContentRef.current,
26502
+ setEditContent: (next) => {
26503
+ editContentRef.current = next;
26504
+ },
26505
+ applyLinkByKey,
26506
+ postToParent: postToParent2
26507
+ };
26508
+ const onPageMessage = (e) => {
26509
+ if (e.data?.type === "ow:page-created") {
26510
+ const { path, title } = e.data;
26511
+ if (typeof path !== "string" || !path) return;
26512
+ if (addNavLinkForPage({ path, title: typeof title === "string" && title ? title : path }, deps)) {
26513
+ enforceLinkHrefs();
26514
+ }
26515
+ return;
26516
+ }
26517
+ if (e.data?.type === "ow:page-renamed") {
26518
+ const { oldPath, newPath, oldTitle, newTitle } = e.data;
26519
+ if (typeof oldPath !== "string" || typeof newPath !== "string") return;
26520
+ const moved = retargetPageLinks(
26521
+ {
26522
+ oldPath,
26523
+ newPath,
26524
+ oldTitle: typeof oldTitle === "string" ? oldTitle : void 0,
26525
+ newTitle: typeof newTitle === "string" ? newTitle : void 0
26526
+ },
26527
+ deps
26528
+ );
26529
+ if (moved > 0) enforceLinkHrefs();
26530
+ }
26531
+ };
26532
+ window.addEventListener("message", onPageMessage);
26533
+ return () => window.removeEventListener("message", onPageMessage);
26534
+ }, [isEditMode, postToParent2]);
26291
26535
  useEffect17(() => {
26292
26536
  if (!isEditMode || shouldUseDevFixtures()) return;
26293
26537
  void loadAllSectionsManifest().then((manifest) => {
@@ -27809,7 +28053,7 @@ function OhhwellsBridge() {
27809
28053
  isFooterFrameSelection,
27810
28054
  postToParent2
27811
28055
  ]);
27812
- useLayoutEffect6(() => {
28056
+ useLayoutEffect7(() => {
27813
28057
  if (!subdomain || isEditMode) {
27814
28058
  setFetchState("done");
27815
28059
  return;
@@ -27836,6 +28080,7 @@ function OhhwellsBridge() {
27836
28080
  applyAiFooterFromContent(content);
27837
28081
  if (typeof content[STYLE_STORE_KEY] === "string") {
27838
28082
  stylesRef.current = content[STYLE_STORE_KEY];
28083
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
27839
28084
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
27840
28085
  }
27841
28086
  applyBrandChrome(content);
@@ -27846,12 +28091,12 @@ function OhhwellsBridge() {
27846
28091
  if (key === "__ohw_sections") continue;
27847
28092
  if (key === AI_SECTIONS_KEY) continue;
27848
28093
  if (key === AI_FOOTER_KEY) continue;
28094
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
28095
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
27849
28096
  if (key === BRAND_KIT_KEY) continue;
27850
28097
  if (key === STYLE_STORE_KEY) continue;
27851
28098
  if (BRAND_CHROME_KEYS.has(key)) continue;
27852
- if (key === LOGO_PLACEHOLDER_KEY) continue;
27853
28099
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
27854
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
27855
28100
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
27856
28101
  continue;
27857
28102
  }
@@ -28082,17 +28327,18 @@ function OhhwellsBridge() {
28082
28327
  }
28083
28328
  applyAiFooterFromContent(content);
28084
28329
  if (typeof content[STYLE_STORE_KEY] === "string") {
28330
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
28085
28331
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
28086
28332
  }
28087
28333
  for (const [key, val] of Object.entries(content)) {
28088
28334
  if (key === "__ohw_sections") continue;
28089
28335
  if (key === AI_SECTIONS_KEY) continue;
28090
28336
  if (key === AI_FOOTER_KEY) continue;
28337
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
28338
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
28091
28339
  if (key === BRAND_KIT_KEY) continue;
28092
28340
  if (key === STYLE_STORE_KEY) continue;
28093
28341
  if (BRAND_CHROME_KEYS.has(key)) continue;
28094
- if (key === LOGO_PLACEHOLDER_KEY) continue;
28095
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
28096
28342
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
28097
28343
  continue;
28098
28344
  }
@@ -28175,7 +28421,7 @@ function OhhwellsBridge() {
28175
28421
  if (debounceTimer) clearTimeout(debounceTimer);
28176
28422
  };
28177
28423
  }, [subdomain, isEditMode, pathname]);
28178
- useLayoutEffect6(() => {
28424
+ useLayoutEffect7(() => {
28179
28425
  const el = document.getElementById("ohw-loader");
28180
28426
  if (!el) return;
28181
28427
  const visible = Boolean(subdomain) && fetchState !== "done";
@@ -28359,26 +28605,11 @@ function OhhwellsBridge() {
28359
28605
  const t2 = setTimeout(measure, 500);
28360
28606
  const ro = new ResizeObserver(schedule);
28361
28607
  ro.observe(document.body);
28362
- let lastWidth = window.innerWidth;
28363
- let resizeTimers = [];
28364
- const clearResizeTimers = () => {
28365
- resizeTimers.forEach(clearTimeout);
28366
- resizeTimers = [];
28367
- };
28368
- const handleResize = () => {
28369
- if (window.innerWidth === lastWidth) return;
28370
- lastWidth = window.innerWidth;
28371
- clearResizeTimers();
28372
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
28373
- };
28374
- window.addEventListener("resize", handleResize);
28375
28608
  return () => {
28376
28609
  clearTimeout(t1);
28377
28610
  clearTimeout(t2);
28378
28611
  if (raf != null) cancelAnimationFrame(raf);
28379
28612
  ro.disconnect();
28380
- clearResizeTimers();
28381
- window.removeEventListener("resize", handleResize);
28382
28613
  };
28383
28614
  }, [pathname, isEditMode, postToParent2]);
28384
28615
  useEffect17(() => {
@@ -28675,6 +28906,13 @@ function OhhwellsBridge() {
28675
28906
  if (target.closest("[data-ohw-state-toggle]")) return;
28676
28907
  if (target.closest("[data-ohw-max-badge]")) return;
28677
28908
  if (isInsideLinkEditor(target)) return;
28909
+ if (isInsideFloatingPanel(target)) return;
28910
+ if (target.closest("[data-ohw-form-toolbar]")) return;
28911
+ if (target.closest(
28912
+ '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
28913
+ )) {
28914
+ return;
28915
+ }
28678
28916
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
28679
28917
  clearMediaSelectionRef.current();
28680
28918
  }
@@ -28698,8 +28936,6 @@ function OhhwellsBridge() {
28698
28936
  return;
28699
28937
  }
28700
28938
  }
28701
- if (isInsideFloatingPanel(target)) return;
28702
- if (target.closest("[data-ohw-form-toolbar]")) return;
28703
28939
  const collectionItem = matchCollectionItemClick(target, collectionItemsRef.current);
28704
28940
  if (collectionItem) {
28705
28941
  e.preventDefault();
@@ -28711,11 +28947,6 @@ function OhhwellsBridge() {
28711
28947
  });
28712
28948
  return;
28713
28949
  }
28714
- if (target.closest(
28715
- '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
28716
- )) {
28717
- return;
28718
- }
28719
28950
  {
28720
28951
  const formEl = getFormElement(target);
28721
28952
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -28869,9 +29100,13 @@ function OhhwellsBridge() {
28869
29100
  if (isIconEditable(editable) && !getSocialItem(editable)) {
28870
29101
  e.preventDefault();
28871
29102
  e.stopPropagation();
28872
- selectIconElementRef.current(editable);
28873
29103
  const aiIcon = editable.hasAttribute("data-ai-icon");
28874
29104
  const featureIcon = aiIcon && /\.features\.\d+\.icon$/.test(editable.dataset.ohwKey ?? "");
29105
+ if (aiIcon) {
29106
+ aiSectionApiRef.current?.selectFromElement(editable);
29107
+ } else {
29108
+ selectIconElementRef.current(editable);
29109
+ }
28875
29110
  postToParentRef.current({
28876
29111
  type: "ow:icon-pick",
28877
29112
  key: editable.dataset.ohwKey ?? "",
@@ -29099,6 +29334,9 @@ function OhhwellsBridge() {
29099
29334
  setHoveredItemRect(null);
29100
29335
  hoveredNavContainerRef.current = null;
29101
29336
  setHoveredNavContainerRect(null);
29337
+ siblingHintElRef.current = null;
29338
+ setSiblingHintRect(null);
29339
+ setSiblingHintRects([]);
29102
29340
  return;
29103
29341
  }
29104
29342
  {
@@ -29219,7 +29457,6 @@ function OhhwellsBridge() {
29219
29457
  hoveredNavContainerRef.current = null;
29220
29458
  setHoveredNavContainerRect(null);
29221
29459
  hoveredItemElRef.current = editable;
29222
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
29223
29460
  }
29224
29461
  }
29225
29462
  }
@@ -29929,7 +30166,11 @@ function OhhwellsBridge() {
29929
30166
  };
29930
30167
  const handleMouseMove = (e) => {
29931
30168
  const { clientX, clientY } = e;
29932
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
30169
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
30170
+ dismissImageHover();
30171
+ clearImageHover();
30172
+ return;
30173
+ }
29933
30174
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) {
29934
30175
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
29935
30176
  formHoverElRef.current = null;
@@ -29958,7 +30199,12 @@ function OhhwellsBridge() {
29958
30199
  setHoveredItemRect(null);
29959
30200
  hoveredNavContainerRef.current = null;
29960
30201
  setHoveredNavContainerRect(null);
30202
+ siblingHintElRef.current = null;
30203
+ setSiblingHintRect(null);
30204
+ setSiblingHintRects([]);
29961
30205
  dismissImageHover();
30206
+ clearImageHover();
30207
+ setSectionGap(null);
29962
30208
  return;
29963
30209
  }
29964
30210
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -29970,7 +30216,11 @@ function OhhwellsBridge() {
29970
30216
  if (e.data?.type !== "ow:pointer-sync") return;
29971
30217
  const { clientX, clientY } = e.data;
29972
30218
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
29973
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
30219
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
30220
+ dismissImageHover();
30221
+ clearImageHover();
30222
+ return;
30223
+ }
29974
30224
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) return;
29975
30225
  if (probeSocialsRowAt(clientX, clientY)) return;
29976
30226
  probeSectionGapAt(clientX, clientY);
@@ -30350,6 +30600,7 @@ function OhhwellsBridge() {
30350
30600
  applyAiFooterFromContent(content);
30351
30601
  if (typeof content[STYLE_STORE_KEY] === "string") {
30352
30602
  stylesRef.current = content[STYLE_STORE_KEY];
30603
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
30353
30604
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
30354
30605
  }
30355
30606
  applyBrandChrome(content);
@@ -30364,12 +30615,12 @@ function OhhwellsBridge() {
30364
30615
  }
30365
30616
  if (key === AI_SECTIONS_KEY) continue;
30366
30617
  if (key === AI_FOOTER_KEY) continue;
30618
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
30619
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
30367
30620
  if (key === BRAND_KIT_KEY) continue;
30368
30621
  if (key === STYLE_STORE_KEY) continue;
30369
30622
  if (BRAND_CHROME_KEYS.has(key)) continue;
30370
- if (key === LOGO_PLACEHOLDER_KEY) continue;
30371
30623
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
30372
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
30373
30624
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
30374
30625
  continue;
30375
30626
  }
@@ -31005,6 +31256,12 @@ function OhhwellsBridge() {
31005
31256
  closeLinkPopoverRef.current();
31006
31257
  return;
31007
31258
  }
31259
+ if (floatingPanelOpenRef.current) {
31260
+ setFloatingPanelRef.current(null);
31261
+ deselectRef.current();
31262
+ deactivateRef.current();
31263
+ return;
31264
+ }
31008
31265
  deselectRef.current();
31009
31266
  deactivateRef.current();
31010
31267
  clearMediaSelectionRef.current();
@@ -31393,7 +31650,7 @@ function OhhwellsBridge() {
31393
31650
  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;
31394
31651
  const nodes = sectionEl ? collectEditableNodes(void 0, sectionEl).filter(
31395
31652
  (n) => n.key && !n.key.startsWith("__ohw_") && !["meta", "sections", "video-setting"].includes(n.type)
31396
- ).map((n) => ({ ...n, text: n.text ?? "", type: normalizeAiNodeType(n.type) })) : [];
31653
+ ).map((n) => ({ ...n, text: n.text ?? "", type: normalizeAiNodeType(n.type) })).map((n) => withNodeFontSize(n, sectionEl)) : [];
31397
31654
  const items = sectionEl ? collectItemGroups(sectionEl) : [];
31398
31655
  const box = sectionEl?.getBoundingClientRect();
31399
31656
  const rect = box ? {
@@ -31840,10 +32097,10 @@ function OhhwellsBridge() {
31840
32097
  window.removeEventListener("message", handleDuplicateSection);
31841
32098
  window.removeEventListener("message", handleDeactivate);
31842
32099
  window.removeEventListener("message", handleSelectNav);
31843
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
31844
32100
  window.removeEventListener("message", handleToastAction);
31845
32101
  window.removeEventListener("message", handleFormCount);
31846
32102
  window.removeEventListener("message", handleUiEscape);
32103
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
31847
32104
  autoSaveTimers.current.forEach(clearTimeout);
31848
32105
  autoSaveTimers.current.clear();
31849
32106
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -32137,7 +32394,7 @@ function OhhwellsBridge() {
32137
32394
  postToParent2({
32138
32395
  type: "ow:ready",
32139
32396
  version: "1",
32140
- bridgeVersion: "0.1.159",
32397
+ bridgeVersion: "0.1.160-next.427",
32141
32398
  path: pathname,
32142
32399
  nodes: collectEditableNodes(editContentRef.current),
32143
32400
  sections
@@ -32872,7 +33129,7 @@ function OhhwellsBridge() {
32872
33129
  className: "flex h-7 w-7 items-center justify-center rounded-md text-foreground transition-colors hover:bg-muted/80",
32873
33130
  onClick: () => setFieldTypePickerOpen((open) => !open),
32874
33131
  "data-ohw-add-field": "",
32875
- children: /* @__PURE__ */ jsx40(Plus5, { size: 15, "aria-hidden": true })
33132
+ children: /* @__PURE__ */ jsx40(Plus6, { size: 15, "aria-hidden": true })
32876
33133
  }
32877
33134
  ),
32878
33135
  /* @__PURE__ */ jsx40("div", { className: "mx-0.5 h-5 w-px bg-border" }),