@ohhwells/bridge 0.1.157 → 0.1.158-next.423

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -64,6 +64,7 @@ __export(index_exports, {
64
64
  OhwLoaderSurface: () => OhwLoaderSurface,
65
65
  SITE_OG_IMAGE_KEY: () => SITE_OG_IMAGE_KEY,
66
66
  SchedulingWidget: () => SchedulingWidget,
67
+ TEMPLATE_LOGO_ATTR: () => TEMPLATE_LOGO_ATTR,
67
68
  Toggle: () => Toggle,
68
69
  ToggleGroup: () => ToggleGroup,
69
70
  ToggleGroupItem: () => ToggleGroupItem,
@@ -97,6 +98,7 @@ __export(index_exports, {
97
98
  parseAiSectionsState: () => parseAiSectionsState,
98
99
  parseTarget: () => parseTarget,
99
100
  recordOverriddenPaths: () => recordOverriddenPaths,
101
+ releaseTemplateLogoIfReplaced: () => releaseTemplateLogoIfReplaced,
100
102
  removeFooterBlock: () => removeFooterBlock,
101
103
  resetOverriddenPathsCache: () => resetOverriddenPathsCache,
102
104
  resolveFooterCopyright: () => resolveFooterCopyright,
@@ -518,11 +520,37 @@ function logoTextElement(root) {
518
520
  );
519
521
  return hasOwnText ? root : null;
520
522
  }
523
+ var TEMPLATE_LOGO_ATTR = "data-ohw-template-logo";
524
+ function releaseTemplateLogoIfReplaced(img, url) {
525
+ if (!img.hasAttribute(TEMPLATE_LOGO_ATTR)) return;
526
+ let incoming = url;
527
+ try {
528
+ incoming = new URL(url, document.baseURI).href;
529
+ } catch {
530
+ }
531
+ if (incoming !== img.src) img.removeAttribute(TEMPLATE_LOGO_ATTR);
532
+ }
533
+ function logoImage(root, selector) {
534
+ if (root instanceof HTMLImageElement && root.matches(selector)) return root;
535
+ return root.querySelector(selector);
536
+ }
521
537
  function logoRootImage(root) {
522
- const img = root.querySelector("img");
538
+ const img = logoImage(root, `img:not([${TEMPLATE_LOGO_ATTR}])`);
523
539
  const src = img?.getAttribute("src")?.trim() ?? "";
524
540
  return img && src && img.style.display !== "none" ? img : null;
525
541
  }
542
+ function templateLogoImage(root) {
543
+ const img = logoImage(root, `img[${TEMPLATE_LOGO_ATTR}]`);
544
+ return img && img.style.display !== "none" ? img : null;
545
+ }
546
+ function createWordmark(root) {
547
+ const el = document.createElement("span");
548
+ el.setAttribute("data-ohw-editable", "plain");
549
+ el.setAttribute("data-ohw-key", textKeyForRoot(root));
550
+ if (root instanceof HTMLImageElement) root.parentNode?.insertBefore(el, root.nextSibling);
551
+ else root.appendChild(el);
552
+ return el;
553
+ }
526
554
  function applyLogoIdentity(text, isPlaceholder) {
527
555
  const display = resolveLogoDisplayText(text);
528
556
  const placeholder = isPlaceholder || !text.trim() || display === PLACEHOLDER_BUSINESS_NAME;
@@ -541,7 +569,9 @@ function applyLogoIdentity(text, isPlaceholder) {
541
569
  if (placeholder) el.setAttribute("data-ohw-placeholder", "");
542
570
  else el.removeAttribute("data-ohw-placeholder");
543
571
  if (logoRootImage(el)) return;
544
- const textEl = logoTextElement(el);
572
+ const templateMark = placeholder ? null : templateLogoImage(el);
573
+ if (templateMark) templateMark.style.display = "none";
574
+ const textEl = logoTextElement(el) ?? (templateMark ? createWordmark(el) : null);
545
575
  if (textEl && textEl.textContent !== display) textEl.textContent = display;
546
576
  });
547
577
  return display;
@@ -573,6 +603,7 @@ function applyLogoImage(url, alt, placement) {
573
603
  }
574
604
  img.removeAttribute("srcset");
575
605
  img.removeAttribute("sizes");
606
+ img.removeAttribute(TEMPLATE_LOGO_ATTR);
576
607
  img.src = url;
577
608
  img.alt = displayAlt;
578
609
  img.style.display = "block";
@@ -592,10 +623,7 @@ function applyLogoImage(url, alt, placement) {
592
623
  img.style.display = "none";
593
624
  }
594
625
  if (!textEl) {
595
- textEl = document.createElement("span");
596
- textEl.setAttribute("data-ohw-editable", "plain");
597
- textEl.setAttribute("data-ohw-key", textKey);
598
- root.appendChild(textEl);
626
+ textEl = createWordmark(root);
599
627
  }
600
628
  textEl.style.display = "";
601
629
  if (textEl.textContent !== displayAlt) textEl.textContent = displayAlt;
@@ -6220,6 +6248,20 @@ function applyStylesToDom(store) {
6220
6248
  }
6221
6249
  }
6222
6250
  }
6251
+ for (const [sectionId, override] of Object.entries(store.sections)) {
6252
+ if (override.iconColor === void 0) continue;
6253
+ const escaped = CSS.escape(sectionId);
6254
+ const byInstance = document.querySelectorAll(`[data-ohw-instance="${escaped}"]`);
6255
+ const sections = byInstance.length ? byInstance : document.querySelectorAll(`[data-ohw-section="${escaped}"]`);
6256
+ for (const marker of Array.from(sections)) {
6257
+ const icons = marker.querySelectorAll('[data-ohw-editable="icon"]');
6258
+ for (const el of Array.from(icons)) {
6259
+ saveInline(el, "color");
6260
+ el.style.setProperty("color", override.iconColor, "important");
6261
+ el.setAttribute(NODE_WROTE_ATTR, "");
6262
+ }
6263
+ }
6264
+ }
6223
6265
  }
6224
6266
 
6225
6267
  // src/lib/ai-sections-store.ts
@@ -6654,6 +6696,21 @@ function pricingCardFeatureCount(tree, index) {
6654
6696
  const target = findPricingCollection(tree)?.children?.[index];
6655
6697
  return target ? pricingFeatureList(target).length : 0;
6656
6698
  }
6699
+ function prevailingFeatureIcon(features) {
6700
+ const counts = /* @__PURE__ */ new Map();
6701
+ for (const row of features) {
6702
+ const key = `${row.icon ?? ""}|${row.iconColor ?? ""}`;
6703
+ const entry = counts.get(key) ?? { count: 0, ...row.icon ? { icon: row.icon } : {}, ...row.iconColor ? { iconColor: row.iconColor } : {} };
6704
+ entry.count += 1;
6705
+ counts.set(key, entry);
6706
+ }
6707
+ let best = null;
6708
+ for (const entry of counts.values()) {
6709
+ if (!best || entry.count > best.count) best = entry;
6710
+ }
6711
+ if (!best) return {};
6712
+ return { ...best.icon ? { icon: best.icon } : {}, ...best.iconColor ? { iconColor: best.iconColor } : {} };
6713
+ }
6657
6714
  function addPricingCardFeature(tree, index, text) {
6658
6715
  return withPricingCollection(tree, (collection) => {
6659
6716
  const children = collection.children;
@@ -6662,7 +6719,7 @@ function addPricingCardFeature(tree, index, text) {
6662
6719
  const features = pricingFeatureList(target);
6663
6720
  if (features.length >= PRICING_FEATURES_MAX) return false;
6664
6721
  target.slots = target.slots ?? {};
6665
- target.slots.features = [...features, { text }];
6722
+ target.slots.features = [...features, { text, ...prevailingFeatureIcon(features) }];
6666
6723
  return true;
6667
6724
  });
6668
6725
  }
@@ -6849,6 +6906,7 @@ function applyBrandChrome(content) {
6849
6906
  if (el.textContent !== name) el.textContent = name;
6850
6907
  if (el.getAttribute("title") !== name) el.setAttribute("title", name);
6851
6908
  });
6909
+ applyLogoIdentity(name, false);
6852
6910
  }
6853
6911
  const title = content[BRAND_TITLE_KEY];
6854
6912
  if (typeof title === "string" && title.length > 0) {
@@ -7861,7 +7919,7 @@ var AI_MOBILE_CSS = [
7861
7919
  // page's first section. Top only (bottom needs no nav clearance); ~96px approximates the
7862
7920
  // template nav's own height (measured ~94px) — there's no shared `--ohw-header-h` var to read
7863
7921
  // an exact figure from (no live measurement exists), so this is a safe approximation, not a
7864
- // pixel-exact fit. Horizontal spacing already comes from `data-ai-container`.
7922
+ // pixel-exact fit. Horizontal spacing already comes from `data-ai-section-inner`.
7865
7923
  '[data-ai-overlay-clearance] [data-ai-overlap-layer="overlay"]{padding-top:96px}',
7866
7924
  // A full-bleed carousel's height is pure arithmetic: it spans the whole window, so 16/9 turns
7867
7925
  // window width straight into height. Measured at 16/9 it came out ~90% of the viewport at EVERY
@@ -7877,8 +7935,10 @@ var AI_MOBILE_CSS = [
7877
7935
  "[data-ai-section]{overflow-x:hidden}",
7878
7936
  // `:not([data-ai-full-bleed])`: this `!important` gutter beat the inline `padding: 0` that a
7879
7937
  // full-bleed section's container sets, so a carousel/media hero that is edge-to-edge on desktop
7880
- // still got a 20px band of section background down both sides on a phone.
7881
- "[data-ai-container]:not([data-ai-full-bleed]){padding:0 20px !important}",
7938
+ // still got a 20px band of section background down both sides on a phone. The footer's own
7939
+ // container keeps the unguarded rule below — `data-ai-full-bleed` can never appear on it.
7940
+ "[data-ai-section-inner]:not([data-ai-full-bleed]){padding:0 20px !important}",
7941
+ "[data-ai-container]{padding:0 20px !important}",
7882
7942
  "[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
7883
7943
  "[data-ai-cell]{width:100%;min-width:0}",
7884
7944
  // The rule above turns every row into a COLUMN flex container on phones, which silently breaks
@@ -8083,6 +8143,31 @@ function pricingCardEmphasis(featured, ctx, hasBg) {
8083
8143
  function textAttrs(ctx, path) {
8084
8144
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
8085
8145
  }
8146
+ var AI_RESPONSIVE_CSS = [
8147
+ "@media (max-width: 960px) {",
8148
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
8149
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
8150
+ "}",
8151
+ "@media (max-width: 640px) {",
8152
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
8153
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
8154
+ // Group containers flatten to a column on phones; span placements come along for free.
8155
+ // `:not([data-ai-group="overlap"])` for the same reason as the twin pair in AI_MOBILE_CSS — see
8156
+ // the comment there. This sheet's copy is the narrower selector, so without the exemption it
8157
+ // flattens an `overlap` hero below 640px even though the other rule stands down: measured a 390px
8158
+ // viewport with the heading/CTA in flow *under* the image, 219px of section background where the
8159
+ // layered text used to be.
8160
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) { display: flex !important; flex-direction: column !important; }',
8161
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) > * { grid-column: auto !important; }',
8162
+ // `:not([data-ai-full-bleed])` for the same reason as the twin rule in AI_MOBILE_CSS: this one
8163
+ // is narrower (0,2,0 vs 0,1,0) and also `!important`, so without the guard it wins outright and
8164
+ // a full-bleed hero gets its 20px side band back below 640px only — the one width where the
8165
+ // other rule had already been taught to stand down.
8166
+ " [data-ai-responsive] [data-ai-section-inner]:not([data-ai-full-bleed]) { padding-left: 20px !important; padding-right: 20px !important; }",
8167
+ " [data-ai-responsive] { overflow-x: hidden; }",
8168
+ " [data-ai-responsive] img { max-width: 100%; }",
8169
+ "}"
8170
+ ].join("\n");
8086
8171
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
8087
8172
  function MediaBox({
8088
8173
  refValue,
@@ -8137,7 +8222,7 @@ function MediaBox({
8137
8222
  position: fill ? "absolute" : void 0,
8138
8223
  inset: fill ? 0 : void 0,
8139
8224
  width: fill ? "100%" : fixedSize ? "auto" : "100%",
8140
- height: fill ? "100%" : void 0,
8225
+ height: fill ? "100%" : fixedSize ? height : void 0,
8141
8226
  aspectRatio: fill ? void 0 : aspectRatio,
8142
8227
  minHeight: fill || fixedSize ? void 0 : height ?? (aspectRatio ? void 0 : 180),
8143
8228
  borderRadius: radius,
@@ -9633,7 +9718,7 @@ function CollectionBlock({ node, ctx, path }) {
9633
9718
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
9634
9719
  "div",
9635
9720
  {
9636
- "data-ai-grid": "",
9721
+ "data-ai-grid": String(itemsPerRow),
9637
9722
  "data-ohw-pricing-collection": isPricing ? "" : void 0,
9638
9723
  style: {
9639
9724
  display: "grid",
@@ -9935,7 +10020,7 @@ function renderNode(node, ctx, path, ambientAlign) {
9935
10020
  },
9936
10021
  children: [
9937
10022
  i > 0 && ctx.heroOverlapFullBleed ? (
9938
- // The section's `data-ai-container` gave up its max-width/gutter so the base
10023
+ // The section's `data-ai-section-inner` gave up its max-width/gutter so the base
9939
10024
  // layer (this group's i===0 sibling) can span edge-to-edge — reintroduced here,
9940
10025
  // scoped to just the overlay text, so a heading/copy/CTA stack doesn't stretch
9941
10026
  // the full section width instead of reading at the usual content width.
@@ -10354,6 +10439,7 @@ function AiTreeRenderer({
10354
10439
  ...fillHeight ? { "data-ai-fill-height": "" } : {},
10355
10440
  ...needsOverlayClearance ? { "data-ai-overlay-clearance": "" } : {},
10356
10441
  ...bgAttrs,
10442
+ "data-ai-responsive": "",
10357
10443
  style: {
10358
10444
  position: "relative",
10359
10445
  padding: `${padCss} 0`,
@@ -10367,12 +10453,13 @@ function AiTreeRenderer({
10367
10453
  ...fillHeight ? { display: "grid" } : {}
10368
10454
  },
10369
10455
  children: [
10370
- isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10456
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
10371
10457
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
10458
+ isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10372
10459
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
10373
10460
  "div",
10374
10461
  {
10375
- "data-ai-container": "",
10462
+ "data-ai-section-inner": "",
10376
10463
  ...fullBleedMedia ? { "data-ai-full-bleed": "" } : {},
10377
10464
  style: {
10378
10465
  position: "relative",
@@ -10389,7 +10476,7 @@ function AiTreeRenderer({
10389
10476
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
10390
10477
  "div",
10391
10478
  {
10392
- "data-ai-row": "",
10479
+ "data-ai-columns": "",
10393
10480
  style: {
10394
10481
  display: "grid",
10395
10482
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -11051,9 +11138,10 @@ function applyAiSectionsToDom(state, options) {
11051
11138
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
11052
11139
  continue;
11053
11140
  }
11054
- const isNewMount = !existing || !existing.container.isConnected;
11055
11141
  let container = existing?.container;
11142
+ let isNewMount = false;
11056
11143
  if (!container || !container.isConnected) {
11144
+ isNewMount = true;
11057
11145
  existing?.root.unmount();
11058
11146
  container?.remove();
11059
11147
  container = document.createElement("div");
@@ -17212,7 +17300,7 @@ function apiBase() {
17212
17300
  function bridgeVersion() {
17213
17301
  try {
17214
17302
  if (true) {
17215
- return "0.1.157";
17303
+ return "0.1.158-next.423";
17216
17304
  }
17217
17305
  } catch {
17218
17306
  }
@@ -17226,7 +17314,7 @@ function stampBridgeVersionOnDocument() {
17226
17314
  return;
17227
17315
  }
17228
17316
  try {
17229
- const v = true ? "0.1.157" : "";
17317
+ const v = true ? "0.1.158-next.423" : "";
17230
17318
  if (v) {
17231
17319
  document.documentElement.setAttribute("data-ohw-bridge-version", v);
17232
17320
  window.__OHW_BRIDGE_VERSION__ = v;
@@ -17904,6 +17992,7 @@ function MediaOverlay({
17904
17992
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
17905
17993
  );
17906
17994
  }, [isVideo]);
17995
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
17907
17996
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
17908
17997
  const box = {
17909
17998
  position: "fixed",
@@ -18002,11 +18091,10 @@ function MediaOverlay({
18002
18091
  className: "flex items-center justify-center cursor-pointer",
18003
18092
  style: {
18004
18093
  ...box,
18005
- // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
18006
- // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
18007
- // in-document, pointer-events does it natively. The button below opts back in, so
18008
- // Replace still works.
18009
- pointerEvents: hover.hasTextOverlap || cueOnly ? "none" : "auto",
18094
+ // Visual only. `pointer-events: auto` on this full-bleed rect covered the navbar
18095
+ // (hero bg includes the absolute TopNav) and swallowed CTA press-drag. Text overlay
18096
+ // and cueOnly also need clicks to fall through — Replace / video controls opt back in.
18097
+ pointerEvents: "none",
18010
18098
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
18011
18099
  // than hovered. Hover keeps the existing tinted preview.
18012
18100
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -18033,17 +18121,17 @@ function MediaOverlay({
18033
18121
  },
18034
18122
  children: [
18035
18123
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
18036
- isVideo ? "Replace video" : "Replace image"
18124
+ replaceLabel
18037
18125
  ]
18038
18126
  }
18039
18127
  ),
18040
- replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
18128
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
18041
18129
  Button,
18042
18130
  {
18043
18131
  "data-ohw-media-overlay": "",
18044
18132
  variant: "outline",
18045
18133
  size: "sm",
18046
- "aria-label": isVideo ? "Replace video" : "Replace image",
18134
+ "aria-label": replaceLabel,
18047
18135
  className: "gap-1.5 cursor-pointer hover:bg-background",
18048
18136
  style: {
18049
18137
  ...OVERLAY_BUTTON_STYLE,
@@ -18066,7 +18154,7 @@ function MediaOverlay({
18066
18154
  },
18067
18155
  children: [
18068
18156
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
18069
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
18157
+ replaceMode === "full" ? replaceLabel : null
18070
18158
  ]
18071
18159
  }
18072
18160
  )
@@ -18135,6 +18223,89 @@ function CarouselOverlay({
18135
18223
  // src/ui/ai-section/AiSectionOverlay.tsx
18136
18224
  var import_react9 = require("react");
18137
18225
  var import_lucide_react7 = require("lucide-react");
18226
+
18227
+ // src/lib/ai-change-highlight.ts
18228
+ var STYLE_ID = "ohw-ai-changed-style";
18229
+ var MARK_ATTR = "data-ohw-ai-changed";
18230
+ var HIGHLIGHT_MS = 2200;
18231
+ var HIGHLIGHT_CSS = `
18232
+ @keyframes ohw-ai-changed-pulse {
18233
+ 0% { outline-color: rgba(0, 120, 229, 0.95); box-shadow: 0 0 0 5px rgba(0, 120, 229, 0.20); }
18234
+ 72% { outline-color: rgba(0, 120, 229, 0.95); box-shadow: 0 0 0 5px rgba(0, 120, 229, 0.12); }
18235
+ 100% { outline-color: rgba(0, 120, 229, 0); box-shadow: 0 0 0 5px rgba(0, 120, 229, 0); }
18236
+ }
18237
+ [${MARK_ATTR}] {
18238
+ outline: 2px solid rgba(0, 120, 229, 0.95) !important;
18239
+ outline-offset: 3px !important;
18240
+ animation: ohw-ai-changed-pulse ${HIGHLIGHT_MS}ms ease-out forwards !important;
18241
+ }
18242
+ @media (prefers-reduced-motion: reduce) {
18243
+ [${MARK_ATTR}] { animation: none !important; }
18244
+ }
18245
+ `;
18246
+ function ensureStyleSheet2() {
18247
+ if (document.getElementById(STYLE_ID)) return;
18248
+ const el = document.createElement("style");
18249
+ el.id = STYLE_ID;
18250
+ el.textContent = HIGHLIGHT_CSS;
18251
+ document.head.appendChild(el);
18252
+ }
18253
+ function targetsFor(keys, item) {
18254
+ const out = /* @__PURE__ */ new Set();
18255
+ for (const key of keys) {
18256
+ if (!key) continue;
18257
+ for (const el of Array.from(
18258
+ document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`)
18259
+ )) {
18260
+ out.add(el);
18261
+ }
18262
+ }
18263
+ if (item) {
18264
+ const section = document.querySelector(`[data-ohw-instance="${CSS.escape(item.sectionId)}"]`) ?? document.querySelector(`[data-ohw-section="${CSS.escape(item.sectionId)}"]`);
18265
+ const stamp = `${item.group}:${item.index}`;
18266
+ const el = section?.querySelector(`[data-ohw-item="${CSS.escape(stamp)}"]`);
18267
+ if (el) out.add(el);
18268
+ }
18269
+ return [...out];
18270
+ }
18271
+ var clearTimer = null;
18272
+ function clearChangeHighlight() {
18273
+ if (clearTimer !== null) {
18274
+ clearTimeout(clearTimer);
18275
+ clearTimer = null;
18276
+ }
18277
+ for (const el of Array.from(document.querySelectorAll(`[${MARK_ATTR}]`))) {
18278
+ el.removeAttribute(MARK_ATTR);
18279
+ }
18280
+ }
18281
+ function highlightChangedElements(keys, item) {
18282
+ clearChangeHighlight();
18283
+ const targets = targetsFor(keys, item);
18284
+ if (targets.length === 0) return 0;
18285
+ ensureStyleSheet2();
18286
+ for (const el of targets) el.setAttribute(MARK_ATTR, "");
18287
+ const rect = targets[0].getBoundingClientRect();
18288
+ if (rect.bottom < 0 || rect.top > window.innerHeight) {
18289
+ targets[0].scrollIntoView({ behavior: "smooth", block: "center" });
18290
+ }
18291
+ clearTimer = setTimeout(clearChangeHighlight, HIGHLIGHT_MS);
18292
+ return targets.length;
18293
+ }
18294
+ function installAiChangeHighlight() {
18295
+ const onMessage = (e) => {
18296
+ const data = e.data;
18297
+ if (data?.type !== "ow:ai-highlight") return;
18298
+ const keys = Array.isArray(data.keys) ? data.keys.filter((k) => typeof k === "string") : [];
18299
+ const raw = data.item;
18300
+ const item = raw && typeof raw.sectionId === "string" && typeof raw.group === "string" && typeof raw.index === "number" ? { sectionId: raw.sectionId, group: raw.group, index: raw.index } : void 0;
18301
+ const found = highlightChangedElements(keys, item);
18302
+ window.parent?.postMessage({ type: "ow:ai-highlight-done", found }, "*");
18303
+ };
18304
+ window.addEventListener("message", onMessage);
18305
+ return () => window.removeEventListener("message", onMessage);
18306
+ }
18307
+
18308
+ // src/ui/ai-section/AiSectionOverlay.tsx
18138
18309
  var import_jsx_runtime19 = require("react/jsx-runtime");
18139
18310
  var findSectionElement = findByInstanceId;
18140
18311
  function readRect(instanceId) {
@@ -18386,6 +18557,7 @@ function AiSectionOverlay({
18386
18557
  window.addEventListener("message", onMessage);
18387
18558
  return () => window.removeEventListener("message", onMessage);
18388
18559
  }, [postToParent2]);
18560
+ (0, import_react9.useEffect)(() => installAiChangeHighlight(), []);
18389
18561
  const [hoveredId, setHoveredId] = (0, import_react9.useState)(null);
18390
18562
  (0, import_react9.useEffect)(() => {
18391
18563
  const onMove = (e) => {
@@ -18576,7 +18748,7 @@ function AiSectionOverlay({
18576
18748
 
18577
18749
  // src/ui/ai-section/AiMediaStripOverlay.tsx
18578
18750
  var import_react10 = require("react");
18579
- var import_lucide_react8 = require("lucide-react");
18751
+ var import_lucide_react9 = require("lucide-react");
18580
18752
 
18581
18753
  // src/lib/ai-media-strip-tree.ts
18582
18754
  var MEDIA_STRIP_ITEM_MIN = 1;
@@ -18690,14 +18862,6 @@ function setMediaStripItemAsset(tree, index, assetId) {
18690
18862
  return true;
18691
18863
  });
18692
18864
  }
18693
- function setMediaStripItemAlt(tree, index, alt) {
18694
- return withMediaStrip(tree, (block) => {
18695
- const items = block.items;
18696
- if (index < 0 || index >= items.length) return false;
18697
- items[index] = { ...items[index], alt };
18698
- return true;
18699
- });
18700
- }
18701
18865
  function setMediaStripItemLink(tree, index, link) {
18702
18866
  return withMediaStrip(tree, (block) => {
18703
18867
  const items = block.items;
@@ -18715,11 +18879,117 @@ function setMediaStripItemLink(tree, index, link) {
18715
18879
  });
18716
18880
  }
18717
18881
 
18718
- // src/ui/ai-section/AiMediaStripOverlay.tsx
18882
+ // src/ui/dialog.tsx
18883
+ var React9 = __toESM(require("react"), 1);
18884
+ var import_radix_ui6 = require("radix-ui");
18885
+ var import_lucide_react8 = require("lucide-react");
18719
18886
  var import_jsx_runtime20 = require("react/jsx-runtime");
18720
- var PRIMARY3 = "#0885FE";
18721
- var OHW_BLUE = `var(--ohw-primary, ${PRIMARY3})`;
18722
- var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY3}) 60%, #000000)`;
18887
+ function Dialog2({
18888
+ ...props
18889
+ }) {
18890
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_radix_ui6.Dialog.Root, { "data-slot": "dialog", ...props });
18891
+ }
18892
+ function DialogPortal({
18893
+ ...props
18894
+ }) {
18895
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_radix_ui6.Dialog.Portal, { ...props });
18896
+ }
18897
+ function DialogOverlay({
18898
+ className,
18899
+ ...props
18900
+ }) {
18901
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18902
+ import_radix_ui6.Dialog.Overlay,
18903
+ {
18904
+ "data-slot": "dialog-overlay",
18905
+ "data-ohw-link-modal-root": "",
18906
+ className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
18907
+ ...props
18908
+ }
18909
+ );
18910
+ }
18911
+ var DialogContent = React9.forwardRef(
18912
+ ({ className, children, showCloseButton = true, container, ...props }, ref) => {
18913
+ const positionMode = container ? "absolute" : "fixed";
18914
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(DialogPortal, { container: container ?? void 0, children: [
18915
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
18916
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
18917
+ import_radix_ui6.Dialog.Content,
18918
+ {
18919
+ ref,
18920
+ "data-slot": "dialog-content",
18921
+ className: cn(
18922
+ positionMode,
18923
+ "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",
18924
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
18925
+ container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
18926
+ className
18927
+ ),
18928
+ onMouseDown: (e) => e.stopPropagation(),
18929
+ ...props,
18930
+ children: [
18931
+ children,
18932
+ showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18933
+ import_radix_ui6.Dialog.Close,
18934
+ {
18935
+ type: "button",
18936
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
18937
+ "aria-label": "Close",
18938
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react8.X, { size: 16, "aria-hidden": true })
18939
+ }
18940
+ ) : null
18941
+ ]
18942
+ }
18943
+ )
18944
+ ] });
18945
+ }
18946
+ );
18947
+ DialogContent.displayName = import_radix_ui6.Dialog.Content.displayName;
18948
+ function DialogHeader({
18949
+ className,
18950
+ ...props
18951
+ }) {
18952
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
18953
+ }
18954
+ function DialogFooter({
18955
+ className,
18956
+ ...props
18957
+ }) {
18958
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18959
+ "div",
18960
+ {
18961
+ className: cn("flex items-center justify-end gap-2", className),
18962
+ ...props
18963
+ }
18964
+ );
18965
+ }
18966
+ var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18967
+ import_radix_ui6.Dialog.Title,
18968
+ {
18969
+ ref,
18970
+ className: cn(
18971
+ "text-lg font-semibold leading-none tracking-tight text-card-foreground",
18972
+ className
18973
+ ),
18974
+ ...props
18975
+ }
18976
+ ));
18977
+ DialogTitle.displayName = import_radix_ui6.Dialog.Title.displayName;
18978
+ var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18979
+ import_radix_ui6.Dialog.Description,
18980
+ {
18981
+ ref,
18982
+ className: cn("text-sm text-muted-foreground", className),
18983
+ ...props
18984
+ }
18985
+ ));
18986
+ DialogDescription.displayName = import_radix_ui6.Dialog.Description.displayName;
18987
+ var DialogClose = import_radix_ui6.Dialog.Close;
18988
+
18989
+ // src/ui/ai-section/AiMediaStripOverlay.tsx
18990
+ var import_jsx_runtime21 = require("react/jsx-runtime");
18991
+ var PRIMARY3 = "var(--ohw-primary, #0885FE)";
18992
+ 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]";
18723
18993
  function findMediaStripItemElement(sectionId, index) {
18724
18994
  const sectionEl = findByInstanceId(sectionId);
18725
18995
  if (!sectionEl) return null;
@@ -18729,14 +18999,35 @@ function findMediaStripItemElement(sectionId, index) {
18729
18999
  }
18730
19000
  return null;
18731
19001
  }
18732
- function readRect2(ref) {
18733
- const el = ref && findMediaStripItemElement(ref.sectionId, ref.index);
19002
+ function itemRefFromTarget(target) {
19003
+ if (!(target instanceof Element)) return null;
19004
+ if (target.closest(CHROME_SELECTOR)) return null;
19005
+ const itemEl = target.closest("[data-ohw-media-strip-item]");
19006
+ if (!itemEl) return null;
19007
+ const sectionEl = itemEl.closest("[data-ohw-section]");
19008
+ const unit = sectionEl ? movableUnit(sectionEl) : null;
19009
+ const sectionId = unit ? instanceIdOf(unit) || null : null;
19010
+ const index = Number(itemEl.dataset.ohwMediaStripIndex);
19011
+ if (!sectionId || Number.isNaN(index)) return null;
19012
+ return { sectionId, index };
19013
+ }
19014
+ function sameRef(a, b) {
19015
+ return a?.sectionId === b?.sectionId && a?.index === b?.index;
19016
+ }
19017
+ function rectOf(el) {
18734
19018
  if (!el) return null;
18735
19019
  const r2 = el.getBoundingClientRect();
18736
19020
  if (r2.width <= 0 || r2.height <= 0) return null;
18737
19021
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
18738
19022
  }
18739
- function useLiveItemRect(ref) {
19023
+ function readRect2(ref) {
19024
+ return rectOf(ref && findMediaStripItemElement(ref.sectionId, ref.index));
19025
+ }
19026
+ function readStripRect(ref) {
19027
+ const item = ref && findMediaStripItemElement(ref.sectionId, ref.index);
19028
+ return rectOf(item?.closest("[data-ohw-media-strip]") ?? item);
19029
+ }
19030
+ function useLiveItemRect(ref, strip = false) {
18740
19031
  const [rect, setRect] = (0, import_react10.useState)(null);
18741
19032
  (0, import_react10.useEffect)(() => {
18742
19033
  if (!ref) {
@@ -18744,7 +19035,7 @@ function useLiveItemRect(ref) {
18744
19035
  return;
18745
19036
  }
18746
19037
  const update = () => {
18747
- const next = readRect2(ref);
19038
+ const next = strip ? readStripRect(ref) : readRect2(ref);
18748
19039
  setRect(
18749
19040
  (prev) => prev && next && prev.top === next.top && prev.left === next.left && prev.width === next.width && prev.height === next.height ? prev : next
18750
19041
  );
@@ -18759,9 +19050,49 @@ function useLiveItemRect(ref) {
18759
19050
  window.removeEventListener("resize", update);
18760
19051
  clearInterval(interval);
18761
19052
  };
18762
- }, [ref?.sectionId, ref?.index]);
19053
+ }, [ref?.sectionId, ref?.index, strip]);
18763
19054
  return rect;
18764
19055
  }
19056
+ var TOOLBAR_STRIP_GAP = 52;
19057
+ var TOOLBAR_STRIP_GAP_MOBILE = 42;
19058
+ var TOOLBAR_EDGE_MARGIN2 = 4;
19059
+ function StripToolbarSlot({ stripRect, children }) {
19060
+ const slotRef = (0, import_react10.useRef)(null);
19061
+ const [shift, setShift] = (0, import_react10.useState)(0);
19062
+ const centerX = stripRect.left + stripRect.width / 2;
19063
+ (0, import_react10.useLayoutEffect)(() => {
19064
+ const slot = slotRef.current;
19065
+ if (!slot) return;
19066
+ const clamp = () => {
19067
+ const w = slot.offsetWidth;
19068
+ if (w <= 0) return;
19069
+ const half = w / 2;
19070
+ const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
19071
+ setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
19072
+ };
19073
+ clamp();
19074
+ const ro = new ResizeObserver(clamp);
19075
+ ro.observe(slot);
19076
+ return () => ro.disconnect();
19077
+ }, [centerX, children]);
19078
+ const mobile = window.innerWidth <= 480;
19079
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19080
+ "div",
19081
+ {
19082
+ ref: slotRef,
19083
+ "data-ohw-bridge": "",
19084
+ "data-ohw-item-interaction": "",
19085
+ className: "pointer-events-auto fixed",
19086
+ style: {
19087
+ top: stripRect.top - (mobile ? TOOLBAR_STRIP_GAP_MOBILE : TOOLBAR_STRIP_GAP),
19088
+ left: centerX + shift,
19089
+ transform: "translate(-50%, -100%)",
19090
+ zIndex: 2147483645
19091
+ },
19092
+ children
19093
+ }
19094
+ );
19095
+ }
18765
19096
  function requestValidateTree(postToParent2, tree) {
18766
19097
  return new Promise((resolve) => {
18767
19098
  const id = `ai-validate-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -18798,116 +19129,72 @@ function requestAssetPick(postToParent2) {
18798
19129
  postToParent2({ type: "ow:asset-pick", id });
18799
19130
  });
18800
19131
  }
18801
- function Pill({
19132
+ function ToolbarAction({
18802
19133
  label,
18803
19134
  onClick,
18804
- disabled,
18805
- active
19135
+ disabled = false,
19136
+ active = false,
19137
+ children
18806
19138
  }) {
18807
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18808
- "button",
18809
- {
18810
- type: "button",
18811
- onClick,
18812
- disabled,
18813
- style: {
18814
- height: 28,
18815
- padding: "0 10px",
18816
- display: "inline-flex",
18817
- alignItems: "center",
18818
- gap: 4,
18819
- borderRadius: 999,
18820
- border: "none",
18821
- cursor: disabled ? "default" : "pointer",
18822
- background: active ? OHW_BLUE_DARK : "#ffffff",
18823
- color: active ? "#ffffff" : OHW_BLUE_DARK,
18824
- opacity: disabled ? 0.4 : 1,
18825
- fontSize: 12,
18826
- lineHeight: "16px",
18827
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18828
- whiteSpace: "nowrap",
18829
- flex: "0 0 auto"
18830
- },
18831
- children: label
18832
- }
18833
- );
19139
+ const button = /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, active, onClick, className: "size-8", children });
19140
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19141
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "inline-flex", children: button }) : button }),
19142
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19143
+ ] });
18834
19144
  }
18835
- function AltInput({ value, onCommit }) {
18836
- const [draft, setDraft] = (0, import_react10.useState)(value);
18837
- (0, import_react10.useEffect)(() => setDraft(value), [value]);
18838
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
18839
- "div",
18840
- {
18841
- style: {
18842
- display: "flex",
18843
- alignItems: "center",
18844
- gap: 6,
18845
- height: 28,
18846
- padding: "0 12px",
18847
- borderRadius: 999,
18848
- background: "#ffffff",
18849
- width: "100%",
18850
- maxWidth: 260,
18851
- boxSizing: "border-box"
18852
- },
18853
- children: [
18854
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18855
- "span",
18856
- {
18857
- style: {
18858
- flex: "0 0 auto",
18859
- color: OHW_BLUE_DARK,
18860
- fontSize: 10,
18861
- fontWeight: 700,
18862
- textTransform: "uppercase",
18863
- letterSpacing: 0.4
18864
- },
18865
- children: "Alt"
18866
- }
18867
- ),
18868
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18869
- "input",
19145
+ function ToolbarSeparator() {
19146
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19147
+ }
19148
+ function OptionMenu({
19149
+ label,
19150
+ value,
19151
+ options,
19152
+ onChange
19153
+ }) {
19154
+ const current = options.find((o) => o.value === value) ?? options[0];
19155
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19156
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xs text-muted-foreground", children: label }),
19157
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DropdownMenu, { modal: false, children: [
19158
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19159
+ "button",
19160
+ {
19161
+ type: "button",
19162
+ "aria-label": label,
19163
+ 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",
19164
+ children: [
19165
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "flex-1 text-left", children: current.label }),
19166
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ChevronDown, { size: 16, style: { pointerEvents: "none" } })
19167
+ ]
19168
+ }
19169
+ ) }),
19170
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[132px] min-w-[132px] p-1", children: options.map((option) => {
19171
+ const selected = option.value === value;
19172
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19173
+ DropdownMenuItem,
18870
19174
  {
18871
- type: "text",
18872
- value: draft,
18873
- onChange: (e) => setDraft(e.target.value),
18874
- onBlur: () => {
18875
- if (draft !== value) onCommit(draft);
18876
- },
18877
- onKeyDown: (e) => {
18878
- if (e.key === "Enter") e.currentTarget.blur();
18879
- if (e.key === "Escape") {
18880
- setDraft(value);
18881
- e.currentTarget.blur();
18882
- }
18883
- },
18884
- placeholder: "Describe this image",
18885
- "aria-label": "Image alt text",
18886
- style: {
18887
- flex: 1,
18888
- minWidth: 0,
18889
- border: "none",
18890
- outline: "none",
18891
- background: "transparent",
18892
- fontSize: 12,
18893
- lineHeight: "16px",
18894
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18895
- color: "#1f2937"
18896
- }
18897
- }
18898
- )
18899
- ]
18900
- }
18901
- );
19175
+ role: "menuitemradio",
19176
+ "aria-checked": selected,
19177
+ onSelect: () => onChange(option.value),
19178
+ className: cn("grid grid-cols-[1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
19179
+ children: [
19180
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: option.label }),
19181
+ selected && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Check, { size: 16, className: "text-bridge-primary" })
19182
+ ]
19183
+ },
19184
+ option.value
19185
+ );
19186
+ }) })
19187
+ ] })
19188
+ ] });
18902
19189
  }
18903
19190
  var VARIANTS = [
18904
19191
  { value: "logos", label: "Logos" },
18905
19192
  { value: "reel", label: "Reel" }
18906
19193
  ];
18907
19194
  var ALIGNS = [
18908
- { value: "left", label: "Left" },
18909
- { value: "center", label: "Center" },
18910
- { value: "right", label: "Right" }
19195
+ { value: "left", label: "Align left", Icon: import_lucide_react9.AlignLeft },
19196
+ { value: "center", label: "Align center", Icon: import_lucide_react9.AlignCenter },
19197
+ { value: "right", label: "Align right", Icon: import_lucide_react9.AlignRight }
18911
19198
  ];
18912
19199
  var HOVERS = [
18913
19200
  { value: "none", label: "None" },
@@ -18915,104 +19202,104 @@ var HOVERS = [
18915
19202
  { value: "zoom", label: "Zoom" },
18916
19203
  { value: "fade", label: "Fade" }
18917
19204
  ];
18918
- var TREATMENTS = [
18919
- { value: "color", label: "Color" },
18920
- { value: "grayscale", label: "Grayscale" }
18921
- ];
18922
19205
  var HTTP_URL_RE = /^https?:\/\/\S+$/i;
18923
- function LinkInput({ value, onCommit }) {
18924
- const [draft, setDraft] = (0, import_react10.useState)(value);
19206
+ function LinkModal({
19207
+ href,
19208
+ linkTarget,
19209
+ onClose,
19210
+ onSave
19211
+ }) {
19212
+ const [draft, setDraft] = (0, import_react10.useState)(href);
19213
+ const [newTab, setNewTab] = (0, import_react10.useState)(linkTarget !== "same-tab");
18925
19214
  const [invalid, setInvalid] = (0, import_react10.useState)(false);
18926
- (0, import_react10.useEffect)(() => {
18927
- setDraft(value);
18928
- setInvalid(false);
18929
- }, [value]);
18930
- const commit = () => {
19215
+ const save = () => {
18931
19216
  const next = draft.trim();
18932
- if (next === value) return setInvalid(false);
18933
- if (next && !HTTP_URL_RE.test(next)) return setInvalid(true);
18934
- setInvalid(false);
18935
- onCommit(next);
18936
- };
18937
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
18938
- "div",
18939
- {
18940
- style: {
18941
- display: "flex",
18942
- alignItems: "center",
18943
- gap: 6,
18944
- height: 28,
18945
- padding: "0 12px",
18946
- borderRadius: 999,
18947
- background: "#ffffff",
18948
- width: "100%",
18949
- maxWidth: 260,
18950
- boxSizing: "border-box",
18951
- boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
18952
- },
18953
- children: [
18954
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18955
- "span",
18956
- {
18957
- style: {
18958
- flex: "0 0 auto",
18959
- color: OHW_BLUE_DARK,
18960
- fontSize: 10,
18961
- fontWeight: 700,
18962
- textTransform: "uppercase",
18963
- letterSpacing: 0.4
18964
- },
18965
- children: "Link"
18966
- }
18967
- ),
18968
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18969
- "input",
18970
- {
18971
- type: "text",
18972
- inputMode: "url",
18973
- value: draft,
18974
- onChange: (e) => setDraft(e.target.value),
18975
- onBlur: commit,
18976
- onKeyDown: (e) => {
18977
- if (e.key === "Enter") e.currentTarget.blur();
18978
- if (e.key === "Escape") {
18979
- setDraft(value);
18980
- setInvalid(false);
18981
- e.currentTarget.blur();
18982
- }
18983
- },
18984
- placeholder: "https://example.com",
18985
- "aria-label": "Image link URL",
18986
- "aria-invalid": invalid,
18987
- style: {
18988
- flex: 1,
18989
- minWidth: 0,
18990
- border: "none",
18991
- outline: "none",
18992
- background: "transparent",
18993
- fontSize: 12,
18994
- lineHeight: "16px",
18995
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18996
- color: "#1f2937"
18997
- }
18998
- }
18999
- )
19000
- ]
19217
+ if (next && !HTTP_URL_RE.test(next)) {
19218
+ setInvalid(true);
19219
+ return;
19001
19220
  }
19002
- );
19221
+ onSave(next, newTab ? "new-tab" : "same-tab");
19222
+ onClose();
19223
+ };
19224
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Dialog2, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DialogContent, { "data-ohw-link-modal-root": "", className: "max-w-[400px] gap-4 p-5", children: [
19225
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DialogHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DialogTitle, { className: "text-base", children: "Image link" }) }),
19226
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex flex-col gap-3", children: [
19227
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19228
+ "input",
19229
+ {
19230
+ autoFocus: true,
19231
+ type: "text",
19232
+ inputMode: "url",
19233
+ value: draft,
19234
+ onChange: (e) => {
19235
+ setDraft(e.target.value);
19236
+ setInvalid(false);
19237
+ },
19238
+ onKeyDown: (e) => {
19239
+ if (e.key === "Enter") save();
19240
+ },
19241
+ placeholder: "https://example.com",
19242
+ "aria-label": "Image link URL",
19243
+ "aria-invalid": invalid,
19244
+ className: cn(
19245
+ "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",
19246
+ invalid && "border-destructive focus:border-destructive"
19247
+ )
19248
+ }
19249
+ ),
19250
+ invalid && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-xs text-destructive", children: "Enter a full URL starting with http:// or https://" }),
19251
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("label", { className: "flex items-center gap-2 text-[13px] text-foreground", children: [
19252
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("input", { type: "checkbox", checked: newTab, onChange: (e) => setNewTab(e.target.checked) }),
19253
+ "Open in new tab"
19254
+ ] })
19255
+ ] }),
19256
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DialogFooter, { children: [
19257
+ href && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19258
+ "button",
19259
+ {
19260
+ type: "button",
19261
+ onClick: () => {
19262
+ onSave("", "new-tab");
19263
+ onClose();
19264
+ },
19265
+ className: "mr-auto rounded-[6px] px-3 py-2 text-[13px] font-medium text-destructive transition-colors hover:bg-muted",
19266
+ children: "Remove link"
19267
+ }
19268
+ ),
19269
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19270
+ "button",
19271
+ {
19272
+ type: "button",
19273
+ onClick: onClose,
19274
+ className: "rounded-[6px] px-3 py-2 text-[13px] font-medium text-foreground transition-colors hover:bg-muted",
19275
+ children: "Cancel"
19276
+ }
19277
+ ),
19278
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19279
+ "button",
19280
+ {
19281
+ type: "button",
19282
+ onClick: save,
19283
+ className: "rounded-[6px] bg-bridge-primary px-3 py-2 text-[13px] font-medium text-primary-foreground transition-opacity hover:opacity-90",
19284
+ children: "Save"
19285
+ }
19286
+ )
19287
+ ] })
19288
+ ] }) });
19003
19289
  }
19004
19290
  function MediaStripToolbar({
19005
19291
  entry,
19006
19292
  index,
19007
19293
  pending,
19008
19294
  error,
19295
+ itemEditOn,
19296
+ onToggleItemEdit,
19009
19297
  onSetVariant,
19010
19298
  onSetAlign,
19011
19299
  onSetHover,
19012
19300
  onSetTreatment,
19013
19301
  onResize,
19014
- onSetAlt,
19015
- onSetLink,
19302
+ onOpenLink,
19016
19303
  onAddItem,
19017
19304
  onRemoveItem,
19018
19305
  onChangeImage,
@@ -19021,81 +19308,102 @@ function MediaStripToolbar({
19021
19308
  }) {
19022
19309
  const items = mediaStripItems(entry.tree);
19023
19310
  const itemCount = items.length;
19024
- const currentAlt = items[index]?.alt ?? "";
19025
- const currentHref = items[index]?.href ?? "";
19026
- const currentTarget = items[index]?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
19027
19311
  const slots = findMediaStripSlots(entry.tree);
19028
19312
  const variant = slots?.variant === "reel" ? "reel" : "logos";
19029
19313
  const align = slots?.align === "left" || slots?.align === "right" ? slots.align : "center";
19030
19314
  const hover = slots?.hover === "none" || slots?.hover === "lift" || slots?.hover === "zoom" || slots?.hover === "fade" ? slots.hover : "lift";
19031
19315
  const treatment = slots?.treatment === "color" || slots?.treatment === "grayscale" ? slots.treatment : variant === "logos" ? "grayscale" : "color";
19032
19316
  const sizeValue = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19033
- const isMobile = window.innerWidth <= 480;
19034
- const rowStyle = isMobile ? {
19035
- display: "flex",
19036
- gap: 6,
19037
- flexWrap: "nowrap",
19038
- justifyContent: "center",
19039
- overflowX: "auto",
19040
- WebkitOverflowScrolling: "touch",
19041
- width: "100%",
19042
- scrollbarWidth: "none"
19043
- } : { display: "flex", gap: 6, flexWrap: "nowrap", justifyContent: "center" };
19044
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
19317
+ const mobile = window.innerWidth <= 480;
19318
+ const gatedLabel = (label) => itemEditOn ? label : "Turn on the star to edit this image";
19319
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19045
19320
  "div",
19046
19321
  {
19047
19322
  "data-ohw-bridge": "",
19048
19323
  "data-ohw-toolbar": "",
19049
19324
  onMouseDown: (e) => e.stopPropagation(),
19050
- style: {
19051
- display: "flex",
19052
- flexDirection: "column",
19053
- gap: 6,
19054
- alignItems: "center",
19055
- background: OHW_BLUE,
19056
- padding: 8,
19057
- borderRadius: 12,
19058
- boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
19059
- pointerEvents: pending ? "none" : "auto",
19060
- opacity: pending ? 0.6 : 1,
19061
- maxWidth: isMobile ? "calc(100vw - 24px)" : "none"
19062
- },
19325
+ className: cn("flex flex-col items-center gap-1.5 font-sans", pending && "pointer-events-none opacity-60"),
19326
+ style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
19063
19327
  children: [
19064
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
19065
- VARIANTS.map((v) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: v.label, active: variant === v.value, onClick: () => onSetVariant(v.value) }, v.value)),
19066
- ALIGNS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: a.label, active: align === a.value, onClick: () => onSetAlign(a.value) }, a.value))
19067
- ] }),
19068
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
19069
- HOVERS.map((h) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: h.label, active: hover === h.value, onClick: () => onSetHover(h.value) }, h.value)),
19070
- TREATMENTS.map((t) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: t.label, active: treatment === t.value, onClick: () => onSetTreatment(t.value) }, t.value))
19071
- ] }),
19072
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
19073
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2212", onClick: () => onResize(-8) }),
19074
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { style: { color: "#ffffff", fontSize: 12, minWidth: 34, textAlign: "center" }, children: [
19075
- sizeValue,
19076
- "px"
19077
- ] }),
19078
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "+", onClick: () => onResize(8) })
19079
- ] }),
19080
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(AltInput, { value: currentAlt, onCommit: onSetAlt }) }),
19081
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(LinkInput, { value: currentHref, onCommit: (href) => onSetLink(href, currentTarget) }) }),
19082
- currentHref && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
19083
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "New tab", active: currentTarget === "new-tab", onClick: () => onSetLink(currentHref, "new-tab") }),
19084
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "Same tab", active: currentTarget === "same-tab", onClick: () => onSetLink(currentHref, "same-tab") })
19085
- ] }),
19086
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
19087
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "Change Image", onClick: onChangeImage }),
19088
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "+ Item", onClick: onAddItem }),
19089
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2212 Item", onClick: onRemoveItem, disabled: itemCount <= 1 })
19090
- ] }),
19091
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
19092
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2190 Move", onClick: onMoveLeft, disabled: index <= 0 }),
19093
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2192 Move", onClick: onMoveRight, disabled: index >= itemCount - 1 })
19094
- ] }),
19095
- error && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
19328
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19329
+ "div",
19330
+ {
19331
+ className: cn(
19332
+ "flex items-center gap-1 rounded-[10px] border border-border bg-background p-1 whitespace-nowrap",
19333
+ "shadow-[0px_10px_15px_-3px_rgba(0,0,0,0.1),0px_4px_6px_-4px_rgba(0,0,0,0.1)]",
19334
+ mobile ? "flex-wrap justify-center" : "flex-nowrap"
19335
+ ),
19336
+ children: [
19337
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19338
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19339
+ CustomToolbarButton,
19340
+ {
19341
+ type: "button",
19342
+ "aria-label": itemEditOn ? "Done editing this image" : "Edit this image",
19343
+ "aria-pressed": itemEditOn,
19344
+ onClick: onToggleItemEdit,
19345
+ className: "group size-8",
19346
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19347
+ import_lucide_react9.Star,
19348
+ {
19349
+ size: 16,
19350
+ fill: "currentColor",
19351
+ strokeWidth: 0,
19352
+ className: cn(
19353
+ "transition-colors",
19354
+ itemEditOn ? "text-[#f59e0b] group-hover:text-[#d97706]" : "text-[#d6d3d1] group-hover:text-[#fbbf24]"
19355
+ ),
19356
+ style: { pointerEvents: "none" }
19357
+ }
19358
+ )
19359
+ }
19360
+ ) }),
19361
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: itemEditOn ? "Done editing this image" : "Edit this image" })
19362
+ ] }),
19363
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19364
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: gatedLabel("Change image"), onClick: onChangeImage, disabled: !itemEditOn, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Image, { size: 16, style: { pointerEvents: "none" } }) }),
19365
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: gatedLabel("Link"), onClick: onOpenLink, disabled: !itemEditOn, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Link, { size: 16, style: { pointerEvents: "none" } }) }),
19366
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: gatedLabel("Move left"), onClick: onMoveLeft, disabled: !itemEditOn || index <= 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ArrowLeft, { size: 16, style: { pointerEvents: "none" } }) }),
19367
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19368
+ ToolbarAction,
19369
+ {
19370
+ label: gatedLabel("Move right"),
19371
+ onClick: onMoveRight,
19372
+ disabled: !itemEditOn || index >= itemCount - 1,
19373
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ArrowRight, { size: 16, style: { pointerEvents: "none" } })
19374
+ }
19375
+ ),
19376
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: gatedLabel("Remove image"), onClick: onRemoveItem, disabled: !itemEditOn || itemCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ImageMinus, { size: 16, style: { pointerEvents: "none" } }) }),
19377
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19378
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(OptionMenu, { label: "Layout", value: variant, options: VARIANTS, onChange: onSetVariant }),
19379
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(OptionMenu, { label: "Hover", value: hover, options: HOVERS, onChange: onSetHover }),
19380
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19381
+ ToolbarAction,
19382
+ {
19383
+ label: treatment === "grayscale" ? "Show in color" : "Show grayscale",
19384
+ active: treatment === "grayscale",
19385
+ onClick: () => onSetTreatment(treatment === "grayscale" ? "color" : "grayscale"),
19386
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Palette, { size: 16, style: { pointerEvents: "none" } })
19387
+ }
19388
+ ),
19389
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19390
+ ALIGNS.map(({ value, label, Icon }) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label, active: align === value, onClick: () => onSetAlign(value), children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Icon, { size: 16, style: { pointerEvents: "none" } }) }, value)),
19391
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19392
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Smaller", onClick: () => onResize(-8), children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Minus, { size: 16, style: { pointerEvents: "none" } }) }),
19393
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "min-w-[40px] text-center text-xs tabular-nums text-muted-foreground", children: [
19394
+ sizeValue,
19395
+ "px"
19396
+ ] }),
19397
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Larger", onClick: () => onResize(8), children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Plus, { size: 16, style: { pointerEvents: "none" } }) }),
19398
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19399
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Add image", onClick: onAddItem, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ImagePlus, { size: 16, style: { pointerEvents: "none" } }) })
19400
+ ]
19401
+ }
19402
+ ),
19403
+ error && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19096
19404
  ]
19097
19405
  }
19098
- );
19406
+ ) });
19099
19407
  }
19100
19408
  function findMediaStripSlots(tree) {
19101
19409
  const stack = tree.rows.flatMap((row) => row.blocks);
@@ -19114,31 +19422,23 @@ function AiMediaStripOverlay({
19114
19422
  }) {
19115
19423
  const [hover, setHover] = (0, import_react10.useState)(null);
19116
19424
  const [active, setActive] = (0, import_react10.useState)(null);
19425
+ const [itemEditOn, setItemEditOn] = (0, import_react10.useState)(false);
19426
+ const [linkOpen, setLinkOpen] = (0, import_react10.useState)(false);
19117
19427
  const [pending, setPending] = (0, import_react10.useState)(false);
19118
19428
  const [error, setError] = (0, import_react10.useState)(null);
19119
19429
  const activeRef = (0, import_react10.useRef)(null);
19120
19430
  activeRef.current = active;
19431
+ const linkOpenRef = (0, import_react10.useRef)(false);
19432
+ linkOpenRef.current = linkOpen;
19121
19433
  (0, import_react10.useEffect)(() => {
19122
19434
  const onMove = (e) => {
19435
+ const ref = itemRefFromTarget(e.target);
19123
19436
  const t = e.target;
19124
- if (!(t instanceof HTMLElement)) return;
19125
- 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]")) {
19126
- return;
19127
- }
19128
- const itemEl = t.closest("[data-ohw-media-strip-item]");
19129
- if (!itemEl) {
19130
- setHover(null);
19131
- return;
19132
- }
19133
- const sectionEl = itemEl.closest("[data-ohw-section]");
19134
- const unit = sectionEl ? movableUnit(sectionEl) : null;
19135
- const sectionId = unit ? instanceIdOf(unit) || null : null;
19136
- const index = Number(itemEl.dataset.ohwMediaStripIndex);
19137
- if (!sectionId || Number.isNaN(index)) {
19138
- setHover(null);
19139
- return;
19140
- }
19141
- setHover({ sectionId, index });
19437
+ const overEditable = Boolean(t?.closest?.("[data-ohw-editable]"));
19438
+ setHover((prev) => {
19439
+ const next = ref && !overEditable ? ref : null;
19440
+ return sameRef(prev, next) ? prev : next;
19441
+ });
19142
19442
  };
19143
19443
  const onLeave = () => setHover(null);
19144
19444
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -19150,23 +19450,25 @@ function AiMediaStripOverlay({
19150
19450
  }, []);
19151
19451
  (0, import_react10.useEffect)(() => {
19152
19452
  const onClick = (e) => {
19153
- const current = activeRef.current;
19154
- if (!current) return;
19155
19453
  const t = e.target;
19156
- if (!(t instanceof HTMLElement)) return;
19157
- if (t.closest("[data-ohw-item-interaction]")) return;
19158
- const itemEl = findMediaStripItemElement(current.sectionId, current.index);
19159
- if (itemEl && itemEl.contains(t)) return;
19160
- setActive(null);
19454
+ if (!(t instanceof Element)) return;
19455
+ if (t.closest(CHROME_SELECTOR)) return;
19456
+ const ref = itemRefFromTarget(t);
19457
+ if (sameRef(activeRef.current, ref)) return;
19458
+ if (ref) clearInteractionChromeRef.current();
19459
+ setItemEditOn(false);
19161
19460
  setError(null);
19461
+ setActive(ref);
19162
19462
  };
19163
19463
  document.addEventListener("click", onClick, true);
19164
19464
  return () => document.removeEventListener("click", onClick, true);
19165
- }, []);
19465
+ }, [clearInteractionChromeRef]);
19166
19466
  (0, import_react10.useEffect)(() => {
19167
19467
  const onBlur = () => {
19168
19468
  if (!activeRef.current) return;
19169
19469
  setActive(null);
19470
+ setItemEditOn(false);
19471
+ setLinkOpen(false);
19170
19472
  setError(null);
19171
19473
  };
19172
19474
  window.addEventListener("blur", onBlur);
@@ -19176,7 +19478,10 @@ function AiMediaStripOverlay({
19176
19478
  const onKeyDown = (e) => {
19177
19479
  if (e.key !== "Escape") return;
19178
19480
  if (!activeRef.current) return;
19481
+ if (linkOpenRef.current) return;
19482
+ if (document.querySelector('[data-ohw-more-menu][data-state="open"]')) return;
19179
19483
  setActive(null);
19484
+ setItemEditOn(false);
19180
19485
  setError(null);
19181
19486
  };
19182
19487
  document.addEventListener("keydown", onKeyDown);
@@ -19210,137 +19515,133 @@ function AiMediaStripOverlay({
19210
19515
  tree: nextTree,
19211
19516
  label: entry.label,
19212
19517
  media: extraMedia ? { ...entry.media, ...extraMedia } : entry.media,
19213
- ...entry.brand ? { brand: entry.brand } : {}
19518
+ ...entry.brand ? { brand: entry.brand } : {},
19519
+ // The user is already looking at the strip they just edited — recentering the viewport
19520
+ // on every edit reads as the section jumping around.
19521
+ skipScrollIntoView: true
19214
19522
  });
19215
19523
  return true;
19216
19524
  },
19217
19525
  [findEntry, postToParent2, applyTreeLocalRef]
19218
19526
  );
19219
19527
  const activeEntry = active ? findEntry(active.sectionId) : null;
19220
- const hoverHandleRect = useLiveItemRect(!active && hover ? hover : null);
19221
19528
  const activeItemRect = useLiveItemRect(active);
19222
- const TOOLBAR_APPROX_H = 170;
19223
- const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
19224
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
19225
- hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19226
- "button",
19227
- {
19228
- type: "button",
19229
- "data-ohw-toolbar": "",
19230
- onClick: () => {
19231
- clearInteractionChromeRef.current();
19232
- setActive(hover);
19233
- },
19234
- "aria-label": "Configure media item",
19235
- style: {
19236
- position: "fixed",
19237
- top: hoverHandleRect.top + 8,
19238
- left: hoverHandleRect.left + hoverHandleRect.width - 36,
19239
- width: 28,
19240
- height: 28,
19241
- borderRadius: 8,
19242
- border: `1.5px dashed ${PRIMARY3}`,
19243
- background: "#ffffff",
19244
- color: PRIMARY3,
19245
- display: "flex",
19246
- alignItems: "center",
19247
- justifyContent: "center",
19248
- cursor: "pointer",
19249
- zIndex: 2147483643,
19250
- fontSize: 14,
19251
- fontWeight: 700
19252
- },
19253
- children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react8.Star, { size: 14, style: { pointerEvents: "none" } })
19254
- }
19255
- ),
19256
- activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19257
- ItemInteractionLayer,
19258
- {
19259
- rect: activeItemRect,
19260
- state: toolbarState,
19261
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19262
- MediaStripToolbar,
19263
- {
19264
- entry: activeEntry,
19265
- index: active.index,
19266
- pending,
19267
- error,
19268
- onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
19269
- onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
19270
- onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
19271
- onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
19272
- onResize: (delta) => {
19273
- const slots = findMediaStripSlots(activeEntry.tree);
19274
- const variant = slots?.variant === "reel" ? "reel" : "logos";
19275
- const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19276
- void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
19277
- },
19278
- onSetAlt: (alt) => {
19279
- void runMutation(active.sectionId, (tree) => setMediaStripItemAlt(tree, active.index, alt));
19280
- },
19281
- onSetLink: (href, linkTarget) => {
19529
+ const activeStripRect = useLiveItemRect(active, true);
19530
+ const hoverRect = useLiveItemRect(hover && !sameRef(hover, active) ? hover : null);
19531
+ const activeItems = activeEntry ? mediaStripItems(activeEntry.tree) : [];
19532
+ const activeItem = active ? activeItems[active.index] : void 0;
19533
+ const activeHref = activeItem?.href ?? "";
19534
+ const activeTarget = activeItem?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
19535
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19536
+ hoverRect && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19537
+ activeItemRect && activeStripRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19538
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: activeItemRect, state: "active-top" }),
19539
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StripToolbarSlot, { stripRect: activeStripRect, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19540
+ MediaStripToolbar,
19541
+ {
19542
+ entry: activeEntry,
19543
+ index: active.index,
19544
+ pending,
19545
+ error,
19546
+ itemEditOn,
19547
+ onToggleItemEdit: () => setItemEditOn((on) => !on),
19548
+ onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
19549
+ onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
19550
+ onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
19551
+ onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
19552
+ onResize: (delta) => {
19553
+ const slots = findMediaStripSlots(activeEntry.tree);
19554
+ const variant = slots?.variant === "reel" ? "reel" : "logos";
19555
+ const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19556
+ void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
19557
+ },
19558
+ onOpenLink: () => setLinkOpen(true),
19559
+ onAddItem: () => {
19560
+ void requestAssetPick(postToParent2).then((picked) => {
19561
+ if (!picked) return;
19562
+ const item = { assetId: picked.assetId, alt: "Logo" };
19282
19563
  void runMutation(
19283
19564
  active.sectionId,
19284
- (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
19285
- );
19286
- },
19287
- onAddItem: () => {
19288
- void requestAssetPick(postToParent2).then((picked) => {
19289
- if (!picked) return;
19290
- const item = { assetId: picked.assetId, alt: "Logo" };
19291
- void runMutation(
19292
- active.sectionId,
19293
- (tree) => addMediaStripItem(tree, item, active.index),
19294
- { [picked.assetId]: picked.url }
19295
- ).then((applied) => {
19296
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19297
- });
19565
+ (tree) => addMediaStripItem(tree, item, active.index),
19566
+ { [picked.assetId]: picked.url }
19567
+ ).then((applied) => {
19568
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19298
19569
  });
19299
- },
19300
- onChangeImage: () => {
19301
- void requestAssetPick(postToParent2).then((picked) => {
19302
- if (!picked) return;
19303
- void runMutation(
19304
- active.sectionId,
19305
- (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
19306
- { [picked.assetId]: picked.url }
19307
- );
19308
- });
19309
- },
19310
- onRemoveItem: () => {
19311
- void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
19312
- (applied) => {
19313
- if (applied) setActive(null);
19314
- }
19315
- );
19316
- },
19317
- onMoveLeft: () => {
19318
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
19319
- (applied) => {
19320
- if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
19321
- }
19322
- );
19323
- },
19324
- onMoveRight: () => {
19325
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
19326
- (applied) => {
19327
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19328
- }
19570
+ });
19571
+ },
19572
+ onChangeImage: () => {
19573
+ void requestAssetPick(postToParent2).then((picked) => {
19574
+ if (!picked) return;
19575
+ void runMutation(
19576
+ active.sectionId,
19577
+ (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
19578
+ { [picked.assetId]: picked.url }
19329
19579
  );
19330
- }
19580
+ });
19581
+ },
19582
+ onRemoveItem: () => {
19583
+ void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
19584
+ (applied) => {
19585
+ if (applied) setActive(null);
19586
+ }
19587
+ );
19588
+ },
19589
+ onMoveLeft: () => {
19590
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
19591
+ (applied) => {
19592
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
19593
+ }
19594
+ );
19595
+ },
19596
+ onMoveRight: () => {
19597
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
19598
+ (applied) => {
19599
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19600
+ }
19601
+ );
19331
19602
  }
19332
- )
19333
- }
19334
- )
19603
+ }
19604
+ ) }),
19605
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19606
+ "div",
19607
+ {
19608
+ "data-ohw-bridge": "",
19609
+ "data-ohw-item-interaction": "",
19610
+ "aria-hidden": true,
19611
+ className: "pointer-events-none fixed rounded-[4px] px-[7px] py-[3px] font-sans text-[11px] font-medium leading-none text-primary-foreground whitespace-nowrap",
19612
+ style: {
19613
+ top: activeItemRect.top + activeItemRect.height + 10,
19614
+ left: activeItemRect.left - 5,
19615
+ background: PRIMARY3,
19616
+ zIndex: 2147483644
19617
+ },
19618
+ children: "Media item"
19619
+ }
19620
+ ),
19621
+ linkOpen && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19622
+ LinkModal,
19623
+ {
19624
+ href: activeHref,
19625
+ linkTarget: activeTarget,
19626
+ onClose: () => setLinkOpen(false),
19627
+ onSave: (href, linkTarget) => {
19628
+ void runMutation(
19629
+ active.sectionId,
19630
+ (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
19631
+ );
19632
+ }
19633
+ }
19634
+ )
19635
+ ] })
19335
19636
  ] });
19336
19637
  }
19337
19638
 
19338
19639
  // src/ui/ai-section/AiPricingCardOverlay.tsx
19339
19640
  var import_react11 = require("react");
19340
- var import_lucide_react9 = require("lucide-react");
19341
- var import_jsx_runtime21 = require("react/jsx-runtime");
19641
+ var import_lucide_react10 = require("lucide-react");
19642
+ var import_jsx_runtime22 = require("react/jsx-runtime");
19342
19643
  var PRIMARY4 = "var(--ohw-primary, #0885FE)";
19343
- 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]";
19644
+ 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]";
19344
19645
  function findPricingCardElement(sectionId, index) {
19345
19646
  const sectionEl = findByInstanceId(sectionId);
19346
19647
  if (!sectionEl) return null;
@@ -19352,7 +19653,7 @@ function findPricingCardElement(sectionId, index) {
19352
19653
  }
19353
19654
  function cardRefFromTarget(target) {
19354
19655
  if (!(target instanceof Element)) return null;
19355
- if (target.closest(CHROME_SELECTOR)) return null;
19656
+ if (target.closest(CHROME_SELECTOR2)) return null;
19356
19657
  const cardEl = target.closest("[data-ohw-pricing-card]");
19357
19658
  if (!cardEl) return null;
19358
19659
  const sectionEl = cardEl.closest("[data-ohw-section]");
@@ -19362,21 +19663,21 @@ function cardRefFromTarget(target) {
19362
19663
  if (!sectionId || Number.isNaN(index)) return null;
19363
19664
  return { sectionId, index };
19364
19665
  }
19365
- function sameRef(a, b) {
19666
+ function sameRef2(a, b) {
19366
19667
  return a?.sectionId === b?.sectionId && a?.index === b?.index;
19367
19668
  }
19368
- function rectOf(el) {
19669
+ function rectOf2(el) {
19369
19670
  if (!el) return null;
19370
19671
  const r2 = el.getBoundingClientRect();
19371
19672
  if (r2.width <= 0 || r2.height <= 0) return null;
19372
19673
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
19373
19674
  }
19374
19675
  function readRect3(ref) {
19375
- return rectOf(ref && findPricingCardElement(ref.sectionId, ref.index));
19676
+ return rectOf2(ref && findPricingCardElement(ref.sectionId, ref.index));
19376
19677
  }
19377
19678
  function readGridRect(ref) {
19378
19679
  const card = ref && findPricingCardElement(ref.sectionId, ref.index);
19379
- return rectOf(card?.closest("[data-ohw-pricing-collection]") ?? card);
19680
+ return rectOf2(card?.closest("[data-ohw-pricing-collection]") ?? card);
19380
19681
  }
19381
19682
  function useLiveCardRect(ref, grid = false) {
19382
19683
  const [rect, setRect] = (0, import_react11.useState)(null);
@@ -19406,7 +19707,7 @@ function useLiveCardRect(ref, grid = false) {
19406
19707
  }
19407
19708
  var TOOLBAR_GRID_GAP = 52;
19408
19709
  var TOOLBAR_GRID_GAP_MOBILE = 42;
19409
- var TOOLBAR_EDGE_MARGIN2 = 4;
19710
+ var TOOLBAR_EDGE_MARGIN3 = 4;
19410
19711
  function GridToolbarSlot({ gridRect, children }) {
19411
19712
  const slotRef = (0, import_react11.useRef)(null);
19412
19713
  const [shift, setShift] = (0, import_react11.useState)(0);
@@ -19418,7 +19719,7 @@ function GridToolbarSlot({ gridRect, children }) {
19418
19719
  const w = slot.offsetWidth;
19419
19720
  if (w <= 0) return;
19420
19721
  const half = w / 2;
19421
- const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
19722
+ const clamped = Math.max(TOOLBAR_EDGE_MARGIN3 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half));
19422
19723
  setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
19423
19724
  };
19424
19725
  clamp();
@@ -19427,7 +19728,7 @@ function GridToolbarSlot({ gridRect, children }) {
19427
19728
  return () => ro.disconnect();
19428
19729
  }, [centerX, children]);
19429
19730
  const mobile = window.innerWidth <= 480;
19430
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19731
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19431
19732
  "div",
19432
19733
  {
19433
19734
  ref: slotRef,
@@ -19462,7 +19763,7 @@ function requestValidateTree2(postToParent2, tree) {
19462
19763
  });
19463
19764
  }
19464
19765
  function StyleSwatch({ style }) {
19465
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19766
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19466
19767
  "span",
19467
19768
  {
19468
19769
  "aria-hidden": true,
@@ -19476,35 +19777,35 @@ function StyleSwatch({ style }) {
19476
19777
  );
19477
19778
  }
19478
19779
  function ButtonGlyph() {
19479
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("svg", { width: "18", height: "12", viewBox: "0 0 18 12", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19480
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("rect", { x: "0.75", y: "0.75", width: "16.5", height: "10.5", rx: "3", stroke: "currentColor", strokeWidth: "1.5" }),
19481
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
19780
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("svg", { width: "18", height: "12", viewBox: "0 0 18 12", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19781
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("rect", { x: "0.75", y: "0.75", width: "16.5", height: "10.5", rx: "3", stroke: "currentColor", strokeWidth: "1.5" }),
19782
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
19482
19783
  ] });
19483
19784
  }
19484
19785
  function FeatureGlyph({ mode }) {
19485
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19486
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
19487
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
19488
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("circle", { cx: "13.5", cy: "13.5", r: "4.25", fill: "var(--ohw-background, #ffffff)", stroke: "var(--ohw-background, #ffffff)", strokeWidth: "1" }),
19489
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("circle", { cx: "13.5", cy: "13.5", r: "3.75", fill: "var(--ohw-background, #ffffff)", stroke: PRIMARY4, strokeWidth: "1" }),
19490
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
19491
- mode === "add" && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
19786
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19787
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
19788
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
19789
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("circle", { cx: "13.5", cy: "13.5", r: "4.25", fill: "var(--ohw-background, #ffffff)", stroke: "var(--ohw-background, #ffffff)", strokeWidth: "1" }),
19790
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("circle", { cx: "13.5", cy: "13.5", r: "3.75", fill: "var(--ohw-background, #ffffff)", stroke: PRIMARY4, strokeWidth: "1" }),
19791
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
19792
+ mode === "add" && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
19492
19793
  ] });
19493
19794
  }
19494
- function ToolbarAction({
19795
+ function ToolbarAction2({
19495
19796
  label,
19496
19797
  onClick,
19497
19798
  disabled = false,
19498
19799
  children
19499
19800
  }) {
19500
- const button = /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
19501
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19502
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "inline-flex", children: button }) : button }),
19503
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19801
+ const button = /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
19802
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Tooltip, { children: [
19803
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "inline-flex", children: button }) : button }),
19804
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19504
19805
  ] });
19505
19806
  }
19506
- function ToolbarSeparator() {
19507
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19807
+ function ToolbarSeparator2() {
19808
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19508
19809
  }
19509
19810
  function CardStyleMenu({
19510
19811
  value,
@@ -19512,7 +19813,7 @@ function CardStyleMenu({
19512
19813
  disabled = false
19513
19814
  }) {
19514
19815
  const current = PRICING_CARD_STYLES.find((s) => s.value === value) ?? PRICING_CARD_STYLES[0];
19515
- const trigger = /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19816
+ const trigger = /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19516
19817
  "button",
19517
19818
  {
19518
19819
  type: "button",
@@ -19523,28 +19824,28 @@ function CardStyleMenu({
19523
19824
  disabled && "cursor-not-allowed opacity-40 hover:bg-transparent"
19524
19825
  ),
19525
19826
  children: [
19526
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StyleSwatch, { style: current.value }),
19527
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "flex-1 text-left", children: current.label }),
19528
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ChevronDown, { size: 16, style: { pointerEvents: "none" } })
19827
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(StyleSwatch, { style: current.value }),
19828
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "flex-1 text-left", children: current.label }),
19829
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.ChevronDown, { size: 16, style: { pointerEvents: "none" } })
19529
19830
  ]
19530
19831
  }
19531
19832
  );
19532
19833
  if (disabled) {
19533
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19534
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
19535
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19536
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "inline-flex", children: trigger }) }),
19537
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
19834
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19835
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
19836
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Tooltip, { children: [
19837
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "inline-flex", children: trigger }) }),
19838
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
19538
19839
  ] })
19539
19840
  ] });
19540
19841
  }
19541
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19542
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
19543
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DropdownMenu, { modal: false, children: [
19544
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuTrigger, { asChild: true, children: trigger }),
19545
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[152px] min-w-[152px] p-1", children: PRICING_CARD_STYLES.map((style) => {
19842
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19843
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
19844
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(DropdownMenu, { modal: false, children: [
19845
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(DropdownMenuTrigger, { asChild: true, children: trigger }),
19846
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[152px] min-w-[152px] p-1", children: PRICING_CARD_STYLES.map((style) => {
19546
19847
  const selected = style.value === value;
19547
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19848
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19548
19849
  DropdownMenuItem,
19549
19850
  {
19550
19851
  role: "menuitemradio",
@@ -19552,9 +19853,9 @@ function CardStyleMenu({
19552
19853
  onSelect: () => onChange(style.value),
19553
19854
  className: cn("grid grid-cols-[18px_1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
19554
19855
  children: [
19555
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StyleSwatch, { style: style.value }),
19556
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: style.label }),
19557
- selected && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Check, { size: 16, className: "text-bridge-primary" })
19856
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(StyleSwatch, { style: style.value }),
19857
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { children: style.label }),
19858
+ selected && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Check, { size: 16, className: "text-bridge-primary" })
19558
19859
  ]
19559
19860
  },
19560
19861
  style.value
@@ -19584,7 +19885,7 @@ function PricingCardToolbar({
19584
19885
  const style = pricingCardStyle(entry.tree, index);
19585
19886
  const featuredTag = pricingCardFeaturedTag(entry.tree, index);
19586
19887
  const mobile = window.innerWidth <= 480;
19587
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19888
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19588
19889
  "div",
19589
19890
  {
19590
19891
  "data-ohw-bridge": "",
@@ -19596,7 +19897,7 @@ function PricingCardToolbar({
19596
19897
  ),
19597
19898
  style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
19598
19899
  children: [
19599
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19900
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19600
19901
  "div",
19601
19902
  {
19602
19903
  className: cn(
@@ -19605,8 +19906,8 @@ function PricingCardToolbar({
19605
19906
  mobile ? "flex-wrap justify-center" : "flex-nowrap"
19606
19907
  ),
19607
19908
  children: [
19608
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19609
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19909
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Tooltip, { children: [
19910
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19610
19911
  CustomToolbarButton,
19611
19912
  {
19612
19913
  type: "button",
@@ -19614,8 +19915,8 @@ function PricingCardToolbar({
19614
19915
  "aria-pressed": featuredTag,
19615
19916
  onClick: () => onSetFeaturedTag(!featuredTag),
19616
19917
  className: "group size-8",
19617
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19618
- import_lucide_react9.Star,
19918
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19919
+ import_lucide_react10.Star,
19619
19920
  {
19620
19921
  size: 16,
19621
19922
  fill: "currentColor",
@@ -19629,25 +19930,25 @@ function PricingCardToolbar({
19629
19930
  )
19630
19931
  }
19631
19932
  ) }),
19632
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
19933
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
19633
19934
  ] }),
19634
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19635
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
19636
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19637
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ButtonGlyph, {}) }),
19638
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Remove button", onClick: onRemoveButton, disabled: buttonCount === 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "relative inline-flex", children: [
19639
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ButtonGlyph, {}),
19640
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Minus, { size: 10, strokeWidth: 2.5, className: "absolute -right-1.5 -top-1.5 rounded-full bg-background text-bridge-primary" })
19935
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarSeparator2, {}),
19936
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
19937
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarSeparator2, {}),
19938
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ButtonGlyph, {}) }),
19939
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Remove button", onClick: onRemoveButton, disabled: buttonCount === 0, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("span", { className: "relative inline-flex", children: [
19940
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ButtonGlyph, {}),
19941
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Minus, { size: 10, strokeWidth: 2.5, className: "absolute -right-1.5 -top-1.5 rounded-full bg-background text-bridge-primary" })
19641
19942
  ] }) }),
19642
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Add feature", onClick: onAddFeature, disabled: featureCount >= 10, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(FeatureGlyph, { mode: "add" }) }),
19643
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Remove feature", onClick: onRemoveFeature, disabled: featureCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(FeatureGlyph, { mode: "remove" }) }),
19644
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19645
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Add tier", onClick: onAddTier, disabled: collectionCount >= 4, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Plus, { size: 16, style: { pointerEvents: "none" } }) }),
19646
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Remove tier", onClick: onRemoveTier, disabled: collectionCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Minus, { size: 16, style: { pointerEvents: "none" } }) })
19943
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Add feature", onClick: onAddFeature, disabled: featureCount >= 10, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(FeatureGlyph, { mode: "add" }) }),
19944
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Remove feature", onClick: onRemoveFeature, disabled: featureCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(FeatureGlyph, { mode: "remove" }) }),
19945
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarSeparator2, {}),
19946
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Add tier", onClick: onAddTier, disabled: collectionCount >= 4, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Plus, { size: 16, style: { pointerEvents: "none" } }) }),
19947
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Remove tier", onClick: onRemoveTier, disabled: collectionCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Minus, { size: 16, style: { pointerEvents: "none" } }) })
19647
19948
  ]
19648
19949
  }
19649
19950
  ),
19650
- error && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19951
+ error && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19651
19952
  ]
19652
19953
  }
19653
19954
  ) });
@@ -19681,7 +19982,7 @@ function AiPricingCardOverlay({
19681
19982
  const overEditable = Boolean(t?.closest?.("[data-ohw-editable], [data-ohw-role], a, button"));
19682
19983
  setHover((prev) => {
19683
19984
  const next = ref && !overEditable ? ref : null;
19684
- return sameRef(prev, next) ? prev : next;
19985
+ return sameRef2(prev, next) ? prev : next;
19685
19986
  });
19686
19987
  };
19687
19988
  const onLeave = () => setHover(null);
@@ -19696,10 +19997,10 @@ function AiPricingCardOverlay({
19696
19997
  const onClick = (e) => {
19697
19998
  const t = e.target;
19698
19999
  if (!(t instanceof Element)) return;
19699
- if (t.closest(CHROME_SELECTOR)) return;
20000
+ if (t.closest(CHROME_SELECTOR2)) return;
19700
20001
  const ref = cardRefFromTarget(t);
19701
- setActive((prev) => sameRef(prev, ref) ? prev : ref);
19702
- if (!ref || !sameRef(activeRef.current, ref)) setError(null);
20002
+ setActive((prev) => sameRef2(prev, ref) ? prev : ref);
20003
+ if (!ref || !sameRef2(activeRef.current, ref)) setError(null);
19703
20004
  };
19704
20005
  document.addEventListener("click", onClick, true);
19705
20006
  return () => document.removeEventListener("click", onClick, true);
@@ -19764,12 +20065,12 @@ function AiPricingCardOverlay({
19764
20065
  const activeEntry = active ? findEntry(active.sectionId) : null;
19765
20066
  const activeCardRect = useLiveCardRect(active);
19766
20067
  const activeGridRect = useLiveCardRect(active, true);
19767
- const hoverRect = useLiveCardRect(hover && !sameRef(hover, active) ? hover : null);
19768
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19769
- hoverRect && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19770
- activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19771
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
19772
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
20068
+ const hoverRect = useLiveCardRect(hover && !sameRef2(hover, active) ? hover : null);
20069
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
20070
+ hoverRect && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
20071
+ activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
20072
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
20073
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19773
20074
  PricingCardToolbar,
19774
20075
  {
19775
20076
  entry: activeEntry,
@@ -19797,7 +20098,7 @@ function AiPricingCardOverlay({
19797
20098
  onRemoveFeature: () => runMutation(active.sectionId, (tree) => removePricingCardFeature(tree, active.index))
19798
20099
  }
19799
20100
  ) }),
19800
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
20101
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19801
20102
  "div",
19802
20103
  {
19803
20104
  "data-ohw-bridge": "",
@@ -19819,7 +20120,7 @@ function AiPricingCardOverlay({
19819
20120
 
19820
20121
  // src/ui/ai-section/AiVideoOverlay.tsx
19821
20122
  var import_react12 = require("react");
19822
- var import_lucide_react10 = require("lucide-react");
20123
+ var import_lucide_react11 = require("lucide-react");
19823
20124
 
19824
20125
  // src/lib/ai-video-tree.ts
19825
20126
  function resolveVideoBlock(tree, path) {
@@ -19890,10 +20191,10 @@ function extractYouTubeId(input) {
19890
20191
  }
19891
20192
 
19892
20193
  // src/ui/ai-section/AiVideoOverlay.tsx
19893
- var import_jsx_runtime22 = require("react/jsx-runtime");
20194
+ var import_jsx_runtime23 = require("react/jsx-runtime");
19894
20195
  var PRIMARY5 = "#0885FE";
19895
- var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY5})`;
19896
- var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
20196
+ var OHW_BLUE = `var(--ohw-primary, ${PRIMARY5})`;
20197
+ var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
19897
20198
  function findVideoBlockElement(sectionId, path) {
19898
20199
  const sectionEl = findByInstanceId(sectionId);
19899
20200
  if (!sectionEl) return null;
@@ -19953,12 +20254,12 @@ function requestValidateTree3(postToParent2, tree) {
19953
20254
  postToParent2({ type: "ow:ai-validate-tree-request", id, tree });
19954
20255
  });
19955
20256
  }
19956
- function Pill2({
20257
+ function Pill({
19957
20258
  label,
19958
20259
  onClick,
19959
20260
  disabled
19960
20261
  }) {
19961
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20262
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
19962
20263
  "button",
19963
20264
  {
19964
20265
  type: "button",
@@ -19974,7 +20275,7 @@ function Pill2({
19974
20275
  border: "none",
19975
20276
  cursor: disabled ? "default" : "pointer",
19976
20277
  background: "#ffffff",
19977
- color: OHW_BLUE_DARK2,
20278
+ color: OHW_BLUE_DARK,
19978
20279
  opacity: disabled ? 0.4 : 1,
19979
20280
  fontSize: 12,
19980
20281
  lineHeight: "16px",
@@ -19996,7 +20297,7 @@ function VideoUrlInput({ value, onCommit }) {
19996
20297
  setDraft(value ? watchUrl(value) : "");
19997
20298
  setInvalid(false);
19998
20299
  }, [value]);
19999
- const commit = () => {
20300
+ const commit2 = () => {
20000
20301
  const next = draft.trim();
20001
20302
  if (!next) {
20002
20303
  setInvalid(false);
@@ -20017,8 +20318,8 @@ function VideoUrlInput({ value, onCommit }) {
20017
20318
  onCommit(id);
20018
20319
  }
20019
20320
  };
20020
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
20021
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
20321
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
20322
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20022
20323
  "div",
20023
20324
  {
20024
20325
  style: {
@@ -20034,12 +20335,12 @@ function VideoUrlInput({ value, onCommit }) {
20034
20335
  boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
20035
20336
  },
20036
20337
  children: [
20037
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20338
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20038
20339
  "span",
20039
20340
  {
20040
20341
  style: {
20041
20342
  flex: "0 0 auto",
20042
- color: OHW_BLUE_DARK2,
20343
+ color: OHW_BLUE_DARK,
20043
20344
  fontSize: 10,
20044
20345
  lineHeight: "16px",
20045
20346
  fontWeight: 700,
@@ -20049,14 +20350,14 @@ function VideoUrlInput({ value, onCommit }) {
20049
20350
  children: "Video"
20050
20351
  }
20051
20352
  ),
20052
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20353
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20053
20354
  "input",
20054
20355
  {
20055
20356
  type: "text",
20056
20357
  inputMode: "url",
20057
20358
  value: draft,
20058
20359
  onChange: (e) => handleChange(e.target.value),
20059
- onBlur: commit,
20360
+ onBlur: commit2,
20060
20361
  onKeyDown: (e) => {
20061
20362
  if (e.key === "Enter") e.currentTarget.blur();
20062
20363
  if (e.key === "Escape") {
@@ -20086,7 +20387,7 @@ function VideoUrlInput({ value, onCommit }) {
20086
20387
  ]
20087
20388
  }
20088
20389
  ),
20089
- invalid && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, textAlign: "center" }, children: "That doesn't look like a YouTube link \u2014 try pasting the video URL again." })
20390
+ invalid && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, textAlign: "center" }, children: "That doesn't look like a YouTube link \u2014 try pasting the video URL again." })
20090
20391
  ] });
20091
20392
  }
20092
20393
  function VideoToolbar({
@@ -20096,7 +20397,7 @@ function VideoToolbar({
20096
20397
  onSetVideoId,
20097
20398
  onRemove
20098
20399
  }) {
20099
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
20400
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20100
20401
  "div",
20101
20402
  {
20102
20403
  "data-ohw-bridge": "",
@@ -20108,7 +20409,7 @@ function VideoToolbar({
20108
20409
  gap: 6,
20109
20410
  alignItems: "center",
20110
20411
  width: 280,
20111
- background: OHW_BLUE2,
20412
+ background: OHW_BLUE,
20112
20413
  padding: 8,
20113
20414
  borderRadius: 12,
20114
20415
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -20116,9 +20417,9 @@ function VideoToolbar({
20116
20417
  opacity: pending ? 0.6 : 1
20117
20418
  },
20118
20419
  children: [
20119
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
20120
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Pill2, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
20121
- error && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20420
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
20421
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Pill, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
20422
+ error && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20122
20423
  ]
20123
20424
  }
20124
20425
  );
@@ -20238,8 +20539,8 @@ function AiVideoOverlay({
20238
20539
  const activeBlockRect = useLiveBlockRect(active);
20239
20540
  const TOOLBAR_APPROX_H = 90;
20240
20541
  const toolbarState = activeBlockRect && activeBlockRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
20241
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
20242
- hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20542
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
20543
+ hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20243
20544
  "button",
20244
20545
  {
20245
20546
  type: "button",
@@ -20267,15 +20568,15 @@ function AiVideoOverlay({
20267
20568
  fontSize: 14,
20268
20569
  fontWeight: 700
20269
20570
  },
20270
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Star, { size: 14, style: { pointerEvents: "none" } })
20571
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.Star, { size: 14, style: { pointerEvents: "none" } })
20271
20572
  }
20272
20573
  ),
20273
- activeBlockRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20574
+ activeBlockRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20274
20575
  ItemInteractionLayer,
20275
20576
  {
20276
20577
  rect: activeBlockRect,
20277
20578
  state: toolbarState,
20278
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20579
+ toolbar: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20279
20580
  VideoToolbar,
20280
20581
  {
20281
20582
  videoId: videoIdAt(activeEntry.tree, active.path),
@@ -20299,7 +20600,7 @@ function AiVideoOverlay({
20299
20600
 
20300
20601
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
20301
20602
  var import_react13 = require("react");
20302
- var import_lucide_react11 = require("lucide-react");
20603
+ var import_lucide_react12 = require("lucide-react");
20303
20604
 
20304
20605
  // src/lib/ai-carousel-tree.ts
20305
20606
  function isCarouselBlock(node) {
@@ -20350,10 +20651,10 @@ function setCarouselAutoplay(tree, on) {
20350
20651
  }
20351
20652
 
20352
20653
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
20353
- var import_jsx_runtime23 = require("react/jsx-runtime");
20654
+ var import_jsx_runtime24 = require("react/jsx-runtime");
20354
20655
  var PRIMARY6 = "#0885FE";
20355
- var OHW_BLUE3 = `var(--ohw-primary, ${PRIMARY6})`;
20356
- var OHW_BLUE_DARK3 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
20656
+ var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY6})`;
20657
+ var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
20357
20658
  function findCarouselItemElement(sectionId, index) {
20358
20659
  const sectionEl = findByInstanceId(sectionId);
20359
20660
  if (!sectionEl) return null;
@@ -20432,8 +20733,8 @@ function requestAssetPick2(postToParent2) {
20432
20733
  postToParent2({ type: "ow:asset-pick", id });
20433
20734
  });
20434
20735
  }
20435
- function Pill3({ label, onClick, disabled }) {
20436
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20736
+ function Pill2({ label, onClick, disabled }) {
20737
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20437
20738
  "button",
20438
20739
  {
20439
20740
  type: "button",
@@ -20449,7 +20750,7 @@ function Pill3({ label, onClick, disabled }) {
20449
20750
  border: "none",
20450
20751
  cursor: disabled ? "default" : "pointer",
20451
20752
  background: "#ffffff",
20452
- color: OHW_BLUE_DARK3,
20753
+ color: OHW_BLUE_DARK2,
20453
20754
  opacity: disabled ? 0.4 : 1,
20454
20755
  fontSize: 12,
20455
20756
  lineHeight: "16px",
@@ -20467,7 +20768,7 @@ function TogglePill({
20467
20768
  onToggle,
20468
20769
  disabled
20469
20770
  }) {
20470
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20771
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
20471
20772
  "button",
20472
20773
  {
20473
20774
  type: "button",
@@ -20486,7 +20787,7 @@ function TogglePill({
20486
20787
  border: "none",
20487
20788
  cursor: disabled ? "default" : "pointer",
20488
20789
  background: "#ffffff",
20489
- color: OHW_BLUE_DARK3,
20790
+ color: OHW_BLUE_DARK2,
20490
20791
  opacity: disabled ? 0.4 : 1,
20491
20792
  fontSize: 12,
20492
20793
  lineHeight: "16px",
@@ -20496,7 +20797,7 @@ function TogglePill({
20496
20797
  },
20497
20798
  children: [
20498
20799
  label,
20499
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20800
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20500
20801
  "span",
20501
20802
  {
20502
20803
  "aria-hidden": true,
@@ -20509,10 +20810,10 @@ function TogglePill({
20509
20810
  // Off is a grey track, not a washed-out blue: the pill's own white background already
20510
20811
  // carries the brand colour in the text, and a pale blue track next to it read as
20511
20812
  // "disabled control" rather than "switch that is off".
20512
- background: on ? OHW_BLUE_DARK3 : "#CBD5E1",
20813
+ background: on ? OHW_BLUE_DARK2 : "#CBD5E1",
20513
20814
  transition: "background 120ms ease"
20514
20815
  },
20515
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20816
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20516
20817
  "span",
20517
20818
  {
20518
20819
  style: {
@@ -20541,7 +20842,7 @@ function CarouselSlideToolbar({
20541
20842
  onChangeImage,
20542
20843
  onToggleAutoplay
20543
20844
  }) {
20544
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20845
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
20545
20846
  "div",
20546
20847
  {
20547
20848
  "data-ohw-bridge": "",
@@ -20552,7 +20853,7 @@ function CarouselSlideToolbar({
20552
20853
  flexDirection: "column",
20553
20854
  gap: 6,
20554
20855
  alignItems: "center",
20555
- background: OHW_BLUE3,
20856
+ background: OHW_BLUE2,
20556
20857
  padding: 8,
20557
20858
  borderRadius: 12,
20558
20859
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -20564,11 +20865,11 @@ function CarouselSlideToolbar({
20564
20865
  pointerEvents: pending ? "none" : "auto"
20565
20866
  },
20566
20867
  children: [
20567
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20568
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Pill3, { label: "Change Image", onClick: onChangeImage }),
20569
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
20868
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20869
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(Pill2, { label: "Change Image", onClick: onChangeImage }),
20870
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
20570
20871
  ] }),
20571
- error && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20872
+ error && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20572
20873
  ]
20573
20874
  }
20574
20875
  );
@@ -20708,8 +21009,8 @@ function AiFadeCarouselOverlay({
20708
21009
  const activeItemRect = useLiveItemRect2(active);
20709
21010
  const TOOLBAR_APPROX_H = 90;
20710
21011
  const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
20711
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
20712
- hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
21012
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
21013
+ hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20713
21014
  "button",
20714
21015
  {
20715
21016
  type: "button",
@@ -20738,10 +21039,10 @@ function AiFadeCarouselOverlay({
20738
21039
  fontSize: 14,
20739
21040
  fontWeight: 700
20740
21041
  },
20741
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.Star, { size: 14, style: { pointerEvents: "none" } })
21042
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react12.Star, { size: 14, style: { pointerEvents: "none" } })
20742
21043
  }
20743
21044
  ),
20744
- swapping && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
21045
+ swapping && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20745
21046
  "div",
20746
21047
  {
20747
21048
  ref: skeletonRef,
@@ -20759,15 +21060,15 @@ function AiFadeCarouselOverlay({
20759
21060
  zIndex: 2147483646,
20760
21061
  pointerEvents: "none"
20761
21062
  },
20762
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("style", { children: MEDIA_SKELETON_CSS })
21063
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("style", { children: MEDIA_SKELETON_CSS })
20763
21064
  }
20764
21065
  ),
20765
- activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
21066
+ activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20766
21067
  ItemInteractionLayer,
20767
21068
  {
20768
21069
  rect: activeItemRect,
20769
21070
  state: toolbarState,
20770
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
21071
+ toolbar: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20771
21072
  CarouselSlideToolbar,
20772
21073
  {
20773
21074
  pending,
@@ -20985,113 +21286,6 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
20985
21286
  // src/ui/link-modal/LinkPopover.tsx
20986
21287
  var import_react17 = require("react");
20987
21288
 
20988
- // src/ui/dialog.tsx
20989
- var React9 = __toESM(require("react"), 1);
20990
- var import_radix_ui6 = require("radix-ui");
20991
- var import_lucide_react12 = require("lucide-react");
20992
- var import_jsx_runtime24 = require("react/jsx-runtime");
20993
- function Dialog2({
20994
- ...props
20995
- }) {
20996
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_radix_ui6.Dialog.Root, { "data-slot": "dialog", ...props });
20997
- }
20998
- function DialogPortal({
20999
- ...props
21000
- }) {
21001
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_radix_ui6.Dialog.Portal, { ...props });
21002
- }
21003
- function DialogOverlay({
21004
- className,
21005
- ...props
21006
- }) {
21007
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21008
- import_radix_ui6.Dialog.Overlay,
21009
- {
21010
- "data-slot": "dialog-overlay",
21011
- "data-ohw-link-modal-root": "",
21012
- className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
21013
- ...props
21014
- }
21015
- );
21016
- }
21017
- var DialogContent = React9.forwardRef(
21018
- ({ className, children, showCloseButton = true, container, ...props }, ref) => {
21019
- const positionMode = container ? "absolute" : "fixed";
21020
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(DialogPortal, { container: container ?? void 0, children: [
21021
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
21022
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
21023
- import_radix_ui6.Dialog.Content,
21024
- {
21025
- ref,
21026
- "data-slot": "dialog-content",
21027
- className: cn(
21028
- positionMode,
21029
- "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",
21030
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
21031
- container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
21032
- className
21033
- ),
21034
- onMouseDown: (e) => e.stopPropagation(),
21035
- ...props,
21036
- children: [
21037
- children,
21038
- showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21039
- import_radix_ui6.Dialog.Close,
21040
- {
21041
- type: "button",
21042
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
21043
- "aria-label": "Close",
21044
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react12.X, { size: 16, "aria-hidden": true })
21045
- }
21046
- ) : null
21047
- ]
21048
- }
21049
- )
21050
- ] });
21051
- }
21052
- );
21053
- DialogContent.displayName = import_radix_ui6.Dialog.Content.displayName;
21054
- function DialogHeader({
21055
- className,
21056
- ...props
21057
- }) {
21058
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
21059
- }
21060
- function DialogFooter({
21061
- className,
21062
- ...props
21063
- }) {
21064
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21065
- "div",
21066
- {
21067
- className: cn("flex items-center justify-end gap-2", className),
21068
- ...props
21069
- }
21070
- );
21071
- }
21072
- var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21073
- import_radix_ui6.Dialog.Title,
21074
- {
21075
- ref,
21076
- className: cn(
21077
- "text-lg font-semibold leading-none tracking-tight text-card-foreground",
21078
- className
21079
- ),
21080
- ...props
21081
- }
21082
- ));
21083
- DialogTitle.displayName = import_radix_ui6.Dialog.Title.displayName;
21084
- var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21085
- import_radix_ui6.Dialog.Description,
21086
- {
21087
- ref,
21088
- className: cn("text-sm text-muted-foreground", className),
21089
- ...props
21090
- }
21091
- ));
21092
- DialogDescription.displayName = import_radix_ui6.Dialog.Description.displayName;
21093
- var DialogClose = import_radix_ui6.Dialog.Close;
21094
-
21095
21289
  // src/ui/link-modal/LinkEditorPanel.tsx
21096
21290
  var import_lucide_react16 = require("lucide-react");
21097
21291
 
@@ -22195,6 +22389,7 @@ function shouldUseDevFixtures() {
22195
22389
  function getLogoElement(el) {
22196
22390
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
22197
22391
  if (marked) return marked;
22392
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
22198
22393
  const root = el.closest("nav, [data-ohw-nav-root], footer");
22199
22394
  if (!root) return null;
22200
22395
  const anchor = el.closest("a");
@@ -23889,6 +24084,102 @@ function deleteSelectedNavFooterItem(deps) {
23889
24084
  return false;
23890
24085
  }
23891
24086
 
24087
+ // src/lib/nav-page-sync.ts
24088
+ function labelKeyFor(hrefKey) {
24089
+ return hrefKey.replace(/-href$/, "-label");
24090
+ }
24091
+ function isPageLinkKey(key) {
24092
+ return isNavbarHrefKey(key) || isFooterHrefKey(key);
24093
+ }
24094
+ function currentHref(key, anchor, content) {
24095
+ const stored = content[key];
24096
+ if (typeof stored === "string" && stored.length > 0) return stored;
24097
+ return anchor.getAttribute("href") ?? "";
24098
+ }
24099
+ function normalizePath2(value) {
24100
+ if (value.length > 1 && value.endsWith("/")) return value.slice(0, -1);
24101
+ return value;
24102
+ }
24103
+ function pointsAt(href, path) {
24104
+ const normalized = normalizePath2(href);
24105
+ const target = normalizePath2(path);
24106
+ if (normalized === target) return true;
24107
+ return (normalized.startsWith(`${target}#`) || normalized.startsWith(`${target}?`)) && target !== "/";
24108
+ }
24109
+ function retargetHref(href, oldPath, newPath) {
24110
+ const normalized = normalizePath2(href);
24111
+ const target = normalizePath2(oldPath);
24112
+ if (normalized === target) return newPath;
24113
+ return `${newPath}${normalized.slice(target.length)}`;
24114
+ }
24115
+ function setLabelText(key, text) {
24116
+ document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`).forEach((el) => {
24117
+ el.textContent = text;
24118
+ });
24119
+ }
24120
+ function commit(delta, deps) {
24121
+ if (Object.keys(delta).length === 0) return;
24122
+ deps.setEditContent({ ...deps.getEditContent(), ...delta });
24123
+ deps.postToParent({
24124
+ type: "ow:change",
24125
+ nodes: Object.entries(delta).map(([key, text]) => ({ key, text }))
24126
+ });
24127
+ }
24128
+ function navLinksTo(path, content) {
24129
+ return Array.from(document.querySelectorAll("[data-ohw-href-key]")).filter((anchor) => {
24130
+ const key = anchor.getAttribute("data-ohw-href-key");
24131
+ return isNavbarHrefKey(key) && pointsAt(currentHref(key, anchor, content), path);
24132
+ }).map((anchor) => anchor.getAttribute("data-ohw-href-key"));
24133
+ }
24134
+ function addNavLinkForPage(page, deps) {
24135
+ const content = deps.getEditContent();
24136
+ if (navLinksTo(page.path, content).length > 0) return false;
24137
+ const roots = listNavbarRootItems();
24138
+ const afterAnchor = roots[roots.length - 1] ?? null;
24139
+ let inserted;
24140
+ try {
24141
+ inserted = insertNavbarItem(page.path, page.title, afterAnchor);
24142
+ } catch {
24143
+ return false;
24144
+ }
24145
+ const { hrefKey, labelKey, index } = inserted;
24146
+ deps.applyLinkByKey(hrefKey, page.path);
24147
+ setLabelText(labelKey, page.title);
24148
+ commit(
24149
+ {
24150
+ [hrefKey]: page.path,
24151
+ [labelKey]: page.title,
24152
+ [NAV_COUNT_KEY]: String(index + 1),
24153
+ [NAV_ORDER_KEY]: getNavbarOrderJson()
24154
+ },
24155
+ deps
24156
+ );
24157
+ return true;
24158
+ }
24159
+ function retargetPageLinks(change, deps) {
24160
+ const { oldPath, newPath, oldTitle, newTitle } = change;
24161
+ if (!oldPath || !newPath || normalizePath2(oldPath) === normalizePath2(newPath)) return 0;
24162
+ const content = deps.getEditContent();
24163
+ const delta = {};
24164
+ for (const anchor of Array.from(document.querySelectorAll("[data-ohw-href-key]"))) {
24165
+ const hrefKey = anchor.getAttribute("data-ohw-href-key");
24166
+ if (!isPageLinkKey(hrefKey)) continue;
24167
+ const href = currentHref(hrefKey, anchor, content);
24168
+ if (!pointsAt(href, oldPath)) continue;
24169
+ const nextHref = retargetHref(href, oldPath, newPath);
24170
+ deps.applyLinkByKey(hrefKey, nextHref);
24171
+ delta[hrefKey] = nextHref;
24172
+ if (!oldTitle || !newTitle) continue;
24173
+ const labelKey = labelKeyFor(hrefKey);
24174
+ const label = content[labelKey] ?? anchor.querySelector('[data-ohw-editable="text"]')?.textContent ?? "";
24175
+ if (label.trim() !== oldTitle) continue;
24176
+ setLabelText(labelKey, newTitle);
24177
+ delta[labelKey] = newTitle;
24178
+ }
24179
+ commit(delta, deps);
24180
+ return Object.keys(delta).filter((key) => key.endsWith("-href")).length;
24181
+ }
24182
+
23892
24183
  // src/ui/navbar-container-chrome.tsx
23893
24184
  var import_lucide_react21 = require("lucide-react");
23894
24185
  var import_jsx_runtime37 = require("react/jsx-runtime");
@@ -24031,6 +24322,7 @@ function applyEditableImageSrc(img, url) {
24031
24322
  img.removeAttribute("srcset");
24032
24323
  img.removeAttribute("sizes");
24033
24324
  if (img.loading === "lazy") img.loading = "eager";
24325
+ releaseTemplateLogoIfReplaced(img, url);
24034
24326
  img.src = url;
24035
24327
  }
24036
24328
  function fadeInBgImage(el, url, onReady) {
@@ -24775,7 +25067,7 @@ function getNavigationSelectionParent(el) {
24775
25067
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
24776
25068
  return resolveFooterLinksContainerFrom(el);
24777
25069
  }
24778
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
25070
+ 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)) {
24779
25071
  return getNavigationRoot(el);
24780
25072
  }
24781
25073
  return null;
@@ -24997,7 +25289,6 @@ var ICONS = {
24997
25289
  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"/>',
24998
25290
  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"/>'
24999
25291
  };
25000
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
25001
25292
  var SELECTION_CHROME_GAP2 = 4;
25002
25293
  var TOOLBAR_STROKE_GAP2 = 4;
25003
25294
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -25095,7 +25386,7 @@ function applyVisibleViewport(el, parentScroll) {
25095
25386
  el.style.top = `${top}px`;
25096
25387
  el.style.height = `${height}px`;
25097
25388
  }
25098
- var TOOLBAR_EDGE_MARGIN3 = 4;
25389
+ var TOOLBAR_EDGE_MARGIN4 = 4;
25099
25390
  function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
25100
25391
  const isAbove = transform.includes("translateY(-100%)");
25101
25392
  let visualTop = isAbove ? top - approxH : top;
@@ -25122,8 +25413,8 @@ function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
25122
25413
  function clampToolbarLeft(centerX, toolbarWidth) {
25123
25414
  const half = toolbarWidth / 2;
25124
25415
  return Math.max(
25125
- TOOLBAR_EDGE_MARGIN3 + half,
25126
- Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half)
25416
+ TOOLBAR_EDGE_MARGIN4 + half,
25417
+ Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN4 - half)
25127
25418
  );
25128
25419
  }
25129
25420
  function calcToolbarPos(rect, parentScroll, toolbarW = 306) {
@@ -26003,21 +26294,6 @@ function OhhwellsBridge() {
26003
26294
  const [isItemDragging, setIsItemDragging] = (0, import_react21.useState)(false);
26004
26295
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react21.useState)(false);
26005
26296
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
26006
- const [floatingPanel, setFloatingPanel] = (0, import_react21.useState)(null);
26007
- const floatingPanelOpenRef = (0, import_react21.useRef)(false);
26008
- floatingPanelOpenRef.current = floatingPanel !== null;
26009
- const floatingPanelRef = (0, import_react21.useRef)(null);
26010
- floatingPanelRef.current = floatingPanel;
26011
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react21.useState)(null);
26012
- const buttonCollectionDragRef = (0, import_react21.useRef)(null);
26013
- const buttonDropInsertIndexRef = (0, import_react21.useRef)(null);
26014
- const [buttonDropSlots, setButtonDropSlots] = (0, import_react21.useState)([]);
26015
- const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react21.useState)(null);
26016
- const buttonPointerDragRef = (0, import_react21.useRef)(null);
26017
- const [buttonStyleTick, setButtonStyleTick] = (0, import_react21.useState)(0);
26018
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react21.useState)(null);
26019
- const [editorViewport, setEditorViewport] = (0, import_react21.useState)("desktop");
26020
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react21.useState)(null);
26021
26297
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react21.useState)(null);
26022
26298
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react21.useState)(null);
26023
26299
  const footerDragRef = (0, import_react21.useRef)(null);
@@ -26043,6 +26319,21 @@ function OhhwellsBridge() {
26043
26319
  const stylesRef = (0, import_react21.useRef)("");
26044
26320
  const appliedDrawerRef = (0, import_react21.useRef)(null);
26045
26321
  const pendingDeleteUndoRef = (0, import_react21.useRef)(null);
26322
+ const [floatingPanel, setFloatingPanel] = (0, import_react21.useState)(null);
26323
+ const floatingPanelOpenRef = (0, import_react21.useRef)(false);
26324
+ const floatingPanelRef = (0, import_react21.useRef)(null);
26325
+ floatingPanelRef.current = floatingPanel;
26326
+ const setFloatingPanelRef = (0, import_react21.useRef)(setFloatingPanel);
26327
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react21.useState)(null);
26328
+ const buttonCollectionDragRef = (0, import_react21.useRef)(null);
26329
+ const buttonDropInsertIndexRef = (0, import_react21.useRef)(null);
26330
+ const [buttonDropSlots, setButtonDropSlots] = (0, import_react21.useState)([]);
26331
+ const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react21.useState)(null);
26332
+ const buttonPointerDragRef = (0, import_react21.useRef)(null);
26333
+ const [buttonStyleTick, setButtonStyleTick] = (0, import_react21.useState)(0);
26334
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react21.useState)(null);
26335
+ const [editorViewport, setEditorViewport] = (0, import_react21.useState)("desktop");
26336
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react21.useState)(null);
26046
26337
  const [sitePages, setSitePages] = (0, import_react21.useState)([]);
26047
26338
  const collectionItemsRef = (0, import_react21.useRef)(/* @__PURE__ */ new Map());
26048
26339
  const [sectionsByPath, setSectionsByPath] = (0, import_react21.useState)({});
@@ -26052,7 +26343,18 @@ function OhhwellsBridge() {
26052
26343
  const linkPopoverOpenRef = (0, import_react21.useRef)(false);
26053
26344
  const linkPopoverGraceUntilRef = (0, import_react21.useRef)(0);
26054
26345
  setLinkPopoverRef.current = setLinkPopover;
26346
+ setFloatingPanelRef.current = setFloatingPanel;
26055
26347
  linkPopoverSessionRef.current = linkPopover;
26348
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
26349
+ (0, import_react21.useEffect)(() => {
26350
+ const syncViewport = () => {
26351
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
26352
+ setEditorViewport((prev) => prev === next ? prev : next);
26353
+ };
26354
+ syncViewport();
26355
+ window.addEventListener("resize", syncViewport);
26356
+ return () => window.removeEventListener("resize", syncViewport);
26357
+ }, []);
26056
26358
  (0, import_react21.useEffect)(() => {
26057
26359
  setSectionOrderPersistHandler((entries) => {
26058
26360
  const currentPath = window.location.pathname;
@@ -26201,6 +26503,43 @@ function OhhwellsBridge() {
26201
26503
  if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
26202
26504
  return () => window.removeEventListener("message", onSitePages);
26203
26505
  }, [isEditMode, postToParent2]);
26506
+ (0, import_react21.useEffect)(() => {
26507
+ if (!isEditMode) return;
26508
+ const deps = {
26509
+ getEditContent: () => editContentRef.current,
26510
+ setEditContent: (next) => {
26511
+ editContentRef.current = next;
26512
+ },
26513
+ applyLinkByKey,
26514
+ postToParent: postToParent2
26515
+ };
26516
+ const onPageMessage = (e) => {
26517
+ if (e.data?.type === "ow:page-created") {
26518
+ const { path, title } = e.data;
26519
+ if (typeof path !== "string" || !path) return;
26520
+ if (addNavLinkForPage({ path, title: typeof title === "string" && title ? title : path }, deps)) {
26521
+ enforceLinkHrefs();
26522
+ }
26523
+ return;
26524
+ }
26525
+ if (e.data?.type === "ow:page-renamed") {
26526
+ const { oldPath, newPath, oldTitle, newTitle } = e.data;
26527
+ if (typeof oldPath !== "string" || typeof newPath !== "string") return;
26528
+ const moved = retargetPageLinks(
26529
+ {
26530
+ oldPath,
26531
+ newPath,
26532
+ oldTitle: typeof oldTitle === "string" ? oldTitle : void 0,
26533
+ newTitle: typeof newTitle === "string" ? newTitle : void 0
26534
+ },
26535
+ deps
26536
+ );
26537
+ if (moved > 0) enforceLinkHrefs();
26538
+ }
26539
+ };
26540
+ window.addEventListener("message", onPageMessage);
26541
+ return () => window.removeEventListener("message", onPageMessage);
26542
+ }, [isEditMode, postToParent2]);
26204
26543
  (0, import_react21.useEffect)(() => {
26205
26544
  if (!isEditMode || shouldUseDevFixtures()) return;
26206
26545
  void loadAllSectionsManifest().then((manifest) => {
@@ -27749,6 +28088,7 @@ function OhhwellsBridge() {
27749
28088
  applyAiFooterFromContent(content);
27750
28089
  if (typeof content[STYLE_STORE_KEY] === "string") {
27751
28090
  stylesRef.current = content[STYLE_STORE_KEY];
28091
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
27752
28092
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
27753
28093
  }
27754
28094
  applyBrandChrome(content);
@@ -27759,12 +28099,12 @@ function OhhwellsBridge() {
27759
28099
  if (key === "__ohw_sections") continue;
27760
28100
  if (key === AI_SECTIONS_KEY) continue;
27761
28101
  if (key === AI_FOOTER_KEY) continue;
28102
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
28103
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
27762
28104
  if (key === BRAND_KIT_KEY) continue;
27763
28105
  if (key === STYLE_STORE_KEY) continue;
27764
28106
  if (BRAND_CHROME_KEYS.has(key)) continue;
27765
- if (key === LOGO_PLACEHOLDER_KEY) continue;
27766
28107
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
27767
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
27768
28108
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
27769
28109
  continue;
27770
28110
  }
@@ -27995,17 +28335,18 @@ function OhhwellsBridge() {
27995
28335
  }
27996
28336
  applyAiFooterFromContent(content);
27997
28337
  if (typeof content[STYLE_STORE_KEY] === "string") {
28338
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
27998
28339
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
27999
28340
  }
28000
28341
  for (const [key, val] of Object.entries(content)) {
28001
28342
  if (key === "__ohw_sections") continue;
28002
28343
  if (key === AI_SECTIONS_KEY) continue;
28003
28344
  if (key === AI_FOOTER_KEY) continue;
28345
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
28346
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
28004
28347
  if (key === BRAND_KIT_KEY) continue;
28005
28348
  if (key === STYLE_STORE_KEY) continue;
28006
28349
  if (BRAND_CHROME_KEYS.has(key)) continue;
28007
- if (key === LOGO_PLACEHOLDER_KEY) continue;
28008
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
28009
28350
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
28010
28351
  continue;
28011
28352
  }
@@ -28272,26 +28613,11 @@ function OhhwellsBridge() {
28272
28613
  const t2 = setTimeout(measure, 500);
28273
28614
  const ro = new ResizeObserver(schedule);
28274
28615
  ro.observe(document.body);
28275
- let lastWidth = window.innerWidth;
28276
- let resizeTimers = [];
28277
- const clearResizeTimers = () => {
28278
- resizeTimers.forEach(clearTimeout);
28279
- resizeTimers = [];
28280
- };
28281
- const handleResize = () => {
28282
- if (window.innerWidth === lastWidth) return;
28283
- lastWidth = window.innerWidth;
28284
- clearResizeTimers();
28285
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
28286
- };
28287
- window.addEventListener("resize", handleResize);
28288
28616
  return () => {
28289
28617
  clearTimeout(t1);
28290
28618
  clearTimeout(t2);
28291
28619
  if (raf != null) cancelAnimationFrame(raf);
28292
28620
  ro.disconnect();
28293
- clearResizeTimers();
28294
- window.removeEventListener("resize", handleResize);
28295
28621
  };
28296
28622
  }, [pathname, isEditMode, postToParent2]);
28297
28623
  (0, import_react21.useEffect)(() => {
@@ -28588,6 +28914,13 @@ function OhhwellsBridge() {
28588
28914
  if (target.closest("[data-ohw-state-toggle]")) return;
28589
28915
  if (target.closest("[data-ohw-max-badge]")) return;
28590
28916
  if (isInsideLinkEditor(target)) return;
28917
+ if (isInsideFloatingPanel(target)) return;
28918
+ if (target.closest("[data-ohw-form-toolbar]")) return;
28919
+ if (target.closest(
28920
+ '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
28921
+ )) {
28922
+ return;
28923
+ }
28591
28924
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
28592
28925
  clearMediaSelectionRef.current();
28593
28926
  }
@@ -28611,8 +28944,6 @@ function OhhwellsBridge() {
28611
28944
  return;
28612
28945
  }
28613
28946
  }
28614
- if (isInsideFloatingPanel(target)) return;
28615
- if (target.closest("[data-ohw-form-toolbar]")) return;
28616
28947
  const collectionItem = matchCollectionItemClick(target, collectionItemsRef.current);
28617
28948
  if (collectionItem) {
28618
28949
  e.preventDefault();
@@ -28624,11 +28955,6 @@ function OhhwellsBridge() {
28624
28955
  });
28625
28956
  return;
28626
28957
  }
28627
- if (target.closest(
28628
- '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
28629
- )) {
28630
- return;
28631
- }
28632
28958
  {
28633
28959
  const formEl = getFormElement(target);
28634
28960
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -28782,9 +29108,13 @@ function OhhwellsBridge() {
28782
29108
  if (isIconEditable(editable) && !getSocialItem(editable)) {
28783
29109
  e.preventDefault();
28784
29110
  e.stopPropagation();
28785
- selectIconElementRef.current(editable);
28786
29111
  const aiIcon = editable.hasAttribute("data-ai-icon");
28787
29112
  const featureIcon = aiIcon && /\.features\.\d+\.icon$/.test(editable.dataset.ohwKey ?? "");
29113
+ if (aiIcon) {
29114
+ aiSectionApiRef.current?.selectFromElement(editable);
29115
+ } else {
29116
+ selectIconElementRef.current(editable);
29117
+ }
28788
29118
  postToParentRef.current({
28789
29119
  type: "ow:icon-pick",
28790
29120
  key: editable.dataset.ohwKey ?? "",
@@ -29012,6 +29342,9 @@ function OhhwellsBridge() {
29012
29342
  setHoveredItemRect(null);
29013
29343
  hoveredNavContainerRef.current = null;
29014
29344
  setHoveredNavContainerRect(null);
29345
+ siblingHintElRef.current = null;
29346
+ setSiblingHintRect(null);
29347
+ setSiblingHintRects([]);
29015
29348
  return;
29016
29349
  }
29017
29350
  {
@@ -29132,7 +29465,6 @@ function OhhwellsBridge() {
29132
29465
  hoveredNavContainerRef.current = null;
29133
29466
  setHoveredNavContainerRect(null);
29134
29467
  hoveredItemElRef.current = editable;
29135
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
29136
29468
  }
29137
29469
  }
29138
29470
  }
@@ -29842,7 +30174,11 @@ function OhhwellsBridge() {
29842
30174
  };
29843
30175
  const handleMouseMove = (e) => {
29844
30176
  const { clientX, clientY } = e;
29845
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
30177
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
30178
+ dismissImageHover();
30179
+ clearImageHover();
30180
+ return;
30181
+ }
29846
30182
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) {
29847
30183
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
29848
30184
  formHoverElRef.current = null;
@@ -29871,7 +30207,12 @@ function OhhwellsBridge() {
29871
30207
  setHoveredItemRect(null);
29872
30208
  hoveredNavContainerRef.current = null;
29873
30209
  setHoveredNavContainerRect(null);
30210
+ siblingHintElRef.current = null;
30211
+ setSiblingHintRect(null);
30212
+ setSiblingHintRects([]);
29874
30213
  dismissImageHover();
30214
+ clearImageHover();
30215
+ setSectionGap(null);
29875
30216
  return;
29876
30217
  }
29877
30218
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -29883,7 +30224,11 @@ function OhhwellsBridge() {
29883
30224
  if (e.data?.type !== "ow:pointer-sync") return;
29884
30225
  const { clientX, clientY } = e.data;
29885
30226
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
29886
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
30227
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
30228
+ dismissImageHover();
30229
+ clearImageHover();
30230
+ return;
30231
+ }
29887
30232
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) return;
29888
30233
  if (probeSocialsRowAt(clientX, clientY)) return;
29889
30234
  probeSectionGapAt(clientX, clientY);
@@ -30263,6 +30608,7 @@ function OhhwellsBridge() {
30263
30608
  applyAiFooterFromContent(content);
30264
30609
  if (typeof content[STYLE_STORE_KEY] === "string") {
30265
30610
  stylesRef.current = content[STYLE_STORE_KEY];
30611
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
30266
30612
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
30267
30613
  }
30268
30614
  applyBrandChrome(content);
@@ -30277,12 +30623,12 @@ function OhhwellsBridge() {
30277
30623
  }
30278
30624
  if (key === AI_SECTIONS_KEY) continue;
30279
30625
  if (key === AI_FOOTER_KEY) continue;
30626
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
30627
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
30280
30628
  if (key === BRAND_KIT_KEY) continue;
30281
30629
  if (key === STYLE_STORE_KEY) continue;
30282
30630
  if (BRAND_CHROME_KEYS.has(key)) continue;
30283
- if (key === LOGO_PLACEHOLDER_KEY) continue;
30284
30631
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
30285
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
30286
30632
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
30287
30633
  continue;
30288
30634
  }
@@ -30918,6 +31264,12 @@ function OhhwellsBridge() {
30918
31264
  closeLinkPopoverRef.current();
30919
31265
  return;
30920
31266
  }
31267
+ if (floatingPanelOpenRef.current) {
31268
+ setFloatingPanelRef.current(null);
31269
+ deselectRef.current();
31270
+ deactivateRef.current();
31271
+ return;
31272
+ }
30921
31273
  deselectRef.current();
30922
31274
  deactivateRef.current();
30923
31275
  clearMediaSelectionRef.current();
@@ -31747,10 +32099,10 @@ function OhhwellsBridge() {
31747
32099
  window.removeEventListener("message", handleDuplicateSection);
31748
32100
  window.removeEventListener("message", handleDeactivate);
31749
32101
  window.removeEventListener("message", handleSelectNav);
31750
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
31751
32102
  window.removeEventListener("message", handleToastAction);
31752
32103
  window.removeEventListener("message", handleFormCount);
31753
32104
  window.removeEventListener("message", handleUiEscape);
32105
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
31754
32106
  autoSaveTimers.current.forEach(clearTimeout);
31755
32107
  autoSaveTimers.current.clear();
31756
32108
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -32044,7 +32396,7 @@ function OhhwellsBridge() {
32044
32396
  postToParent2({
32045
32397
  type: "ow:ready",
32046
32398
  version: "1",
32047
- bridgeVersion: "0.1.157",
32399
+ bridgeVersion: "0.1.158-next.423",
32048
32400
  path: pathname,
32049
32401
  nodes: collectEditableNodes(editContentRef.current),
32050
32402
  sections
@@ -33251,6 +33603,7 @@ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey
33251
33603
  OhwLoaderSurface,
33252
33604
  SITE_OG_IMAGE_KEY,
33253
33605
  SchedulingWidget,
33606
+ TEMPLATE_LOGO_ATTR,
33254
33607
  Toggle,
33255
33608
  ToggleGroup,
33256
33609
  ToggleGroupItem,
@@ -33284,6 +33637,7 @@ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey
33284
33637
  parseAiSectionsState,
33285
33638
  parseTarget,
33286
33639
  recordOverriddenPaths,
33640
+ releaseTemplateLogoIfReplaced,
33287
33641
  removeFooterBlock,
33288
33642
  resetOverriddenPathsCache,
33289
33643
  resolveFooterCopyright,