@ohhwells/bridge 0.1.158 → 0.1.160-next.427

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -220,6 +220,56 @@ function isRenderableTree(value) {
220
220
  return AI_TREE_SCHEMA_VERSIONS.includes(tree.schemaVersion) && tree.type === "section" && Array.isArray(tree.rows);
221
221
  }
222
222
 
223
+ // src/lib/headline-size.ts
224
+ var HEADLINE_SIZE_MULTIPLIERS = {
225
+ xs: 0.7,
226
+ sm: 0.85,
227
+ base: 1,
228
+ lg: 1.2,
229
+ xl: 1.45,
230
+ "2xl": 1.75
231
+ };
232
+ var HEADLINE_SIZE_ATTR = "data-ohw-style-headlinesize";
233
+ var PREV_ATTR = "data-ohw-style-prev-fsmul";
234
+ var NODE_FONT_SIZE_ATTR = "data-ohw-style-prev-font-size";
235
+ function headingsIn(section) {
236
+ return Array.from(section.querySelectorAll("h1, h2, h3"));
237
+ }
238
+ function clearHeadlineSizes(root = document) {
239
+ for (const el of Array.from(root.querySelectorAll(`[${PREV_ATTR}]`))) {
240
+ const prev = el.getAttribute(PREV_ATTR) ?? "";
241
+ if (prev) el.style.setProperty("font-size", prev);
242
+ else el.style.removeProperty("font-size");
243
+ el.removeAttribute(PREV_ATTR);
244
+ }
245
+ }
246
+ function applyHeadlineSizes(root = document) {
247
+ clearHeadlineSizes(root);
248
+ for (const section of Array.from(
249
+ root.querySelectorAll(`[${HEADLINE_SIZE_ATTR}]`)
250
+ )) {
251
+ const multiplier = HEADLINE_SIZE_MULTIPLIERS[section.getAttribute(HEADLINE_SIZE_ATTR) ?? ""];
252
+ if (!multiplier || multiplier === 1) continue;
253
+ for (const heading of headingsIn(section)) {
254
+ if (heading.hasAttribute(NODE_FONT_SIZE_ATTR)) continue;
255
+ const base = Number.parseFloat(window.getComputedStyle(heading).fontSize);
256
+ if (!Number.isFinite(base) || base <= 0) continue;
257
+ heading.setAttribute(PREV_ATTR, heading.style.getPropertyValue("font-size"));
258
+ heading.style.setProperty("font-size", `${Math.round(base * multiplier * 100) / 100}px`, "important");
259
+ }
260
+ }
261
+ }
262
+ var resizeInstalled = false;
263
+ function installHeadlineSizeResize() {
264
+ if (resizeInstalled || typeof window === "undefined") return;
265
+ resizeInstalled = true;
266
+ let timer;
267
+ window.addEventListener("resize", () => {
268
+ if (timer) clearTimeout(timer);
269
+ timer = setTimeout(() => applyHeadlineSizes(), 120);
270
+ });
271
+ }
272
+
223
273
  // src/lib/sections.ts
224
274
  var LINK_PICKER_EXCLUDED_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
225
275
  function isChromeSection(el) {
@@ -6011,6 +6061,7 @@ var SECTION_ATTRS = {
6011
6061
  sectionBackground: "data-ohw-style-bg",
6012
6062
  textDistribution: "data-ohw-style-distribution",
6013
6063
  headlineScale: "data-ohw-style-headline",
6064
+ headlineSize: HEADLINE_SIZE_ATTR,
6014
6065
  imageAspect: "data-ohw-style-aspect",
6015
6066
  spacing: "data-ohw-style-spacing",
6016
6067
  verticalPadding: "data-ohw-style-vpadding",
@@ -6079,6 +6130,15 @@ function clearSectionAttrs(root) {
6079
6130
  }
6080
6131
  }
6081
6132
  var ITEM_WROTE_ATTR = "data-ohw-style-item";
6133
+ function paintIcon(slot, color) {
6134
+ for (const el of [slot, ...Array.from(slot.querySelectorAll("svg, svg *"))]) {
6135
+ const styled = el;
6136
+ if (!styled.style) continue;
6137
+ saveInline(styled, "color");
6138
+ styled.style.setProperty("color", color, "important");
6139
+ styled.setAttribute(NODE_WROTE_ATTR, "");
6140
+ }
6141
+ }
6082
6142
  function clearNodeProps(root) {
6083
6143
  for (const el of Array.from(root.querySelectorAll(`[${NODE_WROTE_ATTR}]`))) {
6084
6144
  const h = el;
@@ -6116,6 +6176,7 @@ function applyStylesToDom(store) {
6116
6176
  ensureStyleSheet();
6117
6177
  clearSectionAttrs(document);
6118
6178
  clearNodeProps(document);
6179
+ clearHeadlineSizes(document);
6119
6180
  loadStyleFonts(
6120
6181
  store ? [
6121
6182
  ...Object.values(store.nodes).flatMap((n) => n.fontFamily ? [n.fontFamily] : []),
@@ -6189,9 +6250,13 @@ function applyStylesToDom(store) {
6189
6250
  const nodes = document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`);
6190
6251
  for (const el of Array.from(nodes)) {
6191
6252
  if (override.color !== void 0) {
6192
- saveInline(el, "color");
6193
- el.style.setProperty("color", override.color, "important");
6194
- el.setAttribute(NODE_WROTE_ATTR, "");
6253
+ if (el.dataset.ohwEditable === "icon") {
6254
+ paintIcon(el, override.color);
6255
+ } else {
6256
+ saveInline(el, "color");
6257
+ el.style.setProperty("color", override.color, "important");
6258
+ el.setAttribute(NODE_WROTE_ATTR, "");
6259
+ }
6195
6260
  }
6196
6261
  if (override.fontFamily !== void 0) {
6197
6262
  saveInline(el, "font-family");
@@ -6248,6 +6313,18 @@ function applyStylesToDom(store) {
6248
6313
  }
6249
6314
  }
6250
6315
  }
6316
+ for (const [sectionId, override] of Object.entries(store.sections)) {
6317
+ if (override.iconColor === void 0) continue;
6318
+ const escaped = CSS.escape(sectionId);
6319
+ const byInstance = document.querySelectorAll(`[data-ohw-instance="${escaped}"]`);
6320
+ const sections = byInstance.length ? byInstance : document.querySelectorAll(`[data-ohw-section="${escaped}"]`);
6321
+ for (const marker of Array.from(sections)) {
6322
+ const icons = marker.querySelectorAll('[data-ohw-editable="icon"]');
6323
+ for (const el of Array.from(icons)) paintIcon(el, override.iconColor);
6324
+ }
6325
+ }
6326
+ applyHeadlineSizes(document);
6327
+ installHeadlineSizeResize();
6251
6328
  }
6252
6329
 
6253
6330
  // src/lib/ai-sections-store.ts
@@ -6287,17 +6364,21 @@ function resolveSectionTree(sectionId, aiSectionsRaw) {
6287
6364
  return parseAiSectionsState(aiSectionsRaw).sections.find((entry) => entry.id === sectionId)?.tree;
6288
6365
  }
6289
6366
  function resolveSectionSettingsContext(sectionId, aiSectionsRaw, stylesRaw) {
6367
+ const styleOverride = parseStyleStore(stylesRaw)?.sections[sectionId];
6368
+ const headlineSize = styleOverride?.headlineSize;
6290
6369
  const genSettings = parseAiSectionsState(aiSectionsRaw).sections.find(
6291
6370
  (entry) => entry.id === sectionId
6292
6371
  )?.tree.settings;
6293
- if (genSettings) return genSettings;
6294
- const styleOverride = parseStyleStore(stylesRaw)?.sections[sectionId];
6295
- if (!styleOverride?.verticalPadding && !styleOverride?.minHeight && !styleOverride?.spacing) {
6372
+ if (genSettings) {
6373
+ return { ...genSettings, ...headlineSize ? { headlineSize } : {} };
6374
+ }
6375
+ if (!styleOverride?.verticalPadding && !styleOverride?.minHeight && !styleOverride?.spacing && !headlineSize) {
6296
6376
  return void 0;
6297
6377
  }
6298
6378
  return {
6299
- verticalPadding: styleOverride.verticalPadding ?? legacyVerticalPaddingAlias(styleOverride.spacing),
6300
- minHeight: styleOverride.minHeight
6379
+ verticalPadding: styleOverride?.verticalPadding ?? legacyVerticalPaddingAlias(styleOverride?.spacing),
6380
+ minHeight: styleOverride?.minHeight,
6381
+ ...headlineSize ? { headlineSize } : {}
6301
6382
  };
6302
6383
  }
6303
6384
  function applyTreeToState(state, payload) {
@@ -6682,6 +6763,21 @@ function pricingCardFeatureCount(tree, index) {
6682
6763
  const target = findPricingCollection(tree)?.children?.[index];
6683
6764
  return target ? pricingFeatureList(target).length : 0;
6684
6765
  }
6766
+ function prevailingFeatureIcon(features) {
6767
+ const counts = /* @__PURE__ */ new Map();
6768
+ for (const row of features) {
6769
+ const key = `${row.icon ?? ""}|${row.iconColor ?? ""}`;
6770
+ const entry = counts.get(key) ?? { count: 0, ...row.icon ? { icon: row.icon } : {}, ...row.iconColor ? { iconColor: row.iconColor } : {} };
6771
+ entry.count += 1;
6772
+ counts.set(key, entry);
6773
+ }
6774
+ let best = null;
6775
+ for (const entry of counts.values()) {
6776
+ if (!best || entry.count > best.count) best = entry;
6777
+ }
6778
+ if (!best) return {};
6779
+ return { ...best.icon ? { icon: best.icon } : {}, ...best.iconColor ? { iconColor: best.iconColor } : {} };
6780
+ }
6685
6781
  function addPricingCardFeature(tree, index, text) {
6686
6782
  return withPricingCollection(tree, (collection) => {
6687
6783
  const children = collection.children;
@@ -6690,7 +6786,7 @@ function addPricingCardFeature(tree, index, text) {
6690
6786
  const features = pricingFeatureList(target);
6691
6787
  if (features.length >= PRICING_FEATURES_MAX) return false;
6692
6788
  target.slots = target.slots ?? {};
6693
- target.slots.features = [...features, { text }];
6789
+ target.slots.features = [...features, { text, ...prevailingFeatureIcon(features) }];
6694
6790
  return true;
6695
6791
  });
6696
6792
  }
@@ -7302,6 +7398,23 @@ function applyCustomCodeToHead(content) {
7302
7398
  if (beforeNodes.length > 0) document.head.append(...beforeNodes);
7303
7399
  }
7304
7400
 
7401
+ // src/lib/node-font-size.ts
7402
+ var TEXT_NODE_TYPES = /* @__PURE__ */ new Set(["text", "text/rich", "plain", "link"]);
7403
+ function escapeKey(key) {
7404
+ return typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(key) : key.replace(/["\\]/g, "\\$&");
7405
+ }
7406
+ function measureNodeFontSize(key, type, root) {
7407
+ if (!TEXT_NODE_TYPES.has(type)) return void 0;
7408
+ const el = root.querySelector(`[data-ohw-editable][data-ohw-key="${escapeKey(key)}"]`);
7409
+ if (!el) return void 0;
7410
+ const size = Number.parseFloat(window.getComputedStyle(el).fontSize);
7411
+ return Number.isFinite(size) && size > 0 ? Math.round(size) : void 0;
7412
+ }
7413
+ function withNodeFontSize(node, root) {
7414
+ const fontSize = measureNodeFontSize(node.key, node.type, root);
7415
+ return fontSize === void 0 ? node : { ...node, fontSize };
7416
+ }
7417
+
7305
7418
  // src/lib/selectNavContainer.ts
7306
7419
  function isOnScreen(el) {
7307
7420
  const rect = el.getBoundingClientRect();
@@ -7890,7 +8003,7 @@ var AI_MOBILE_CSS = [
7890
8003
  // page's first section. Top only (bottom needs no nav clearance); ~96px approximates the
7891
8004
  // template nav's own height (measured ~94px) — there's no shared `--ohw-header-h` var to read
7892
8005
  // an exact figure from (no live measurement exists), so this is a safe approximation, not a
7893
- // pixel-exact fit. Horizontal spacing already comes from `data-ai-container`.
8006
+ // pixel-exact fit. Horizontal spacing already comes from `data-ai-section-inner`.
7894
8007
  '[data-ai-overlay-clearance] [data-ai-overlap-layer="overlay"]{padding-top:96px}',
7895
8008
  // A full-bleed carousel's height is pure arithmetic: it spans the whole window, so 16/9 turns
7896
8009
  // window width straight into height. Measured at 16/9 it came out ~90% of the viewport at EVERY
@@ -7906,8 +8019,10 @@ var AI_MOBILE_CSS = [
7906
8019
  "[data-ai-section]{overflow-x:hidden}",
7907
8020
  // `:not([data-ai-full-bleed])`: this `!important` gutter beat the inline `padding: 0` that a
7908
8021
  // full-bleed section's container sets, so a carousel/media hero that is edge-to-edge on desktop
7909
- // still got a 20px band of section background down both sides on a phone.
7910
- "[data-ai-container]:not([data-ai-full-bleed]){padding:0 20px !important}",
8022
+ // still got a 20px band of section background down both sides on a phone. The footer's own
8023
+ // container keeps the unguarded rule below — `data-ai-full-bleed` can never appear on it.
8024
+ "[data-ai-section-inner]:not([data-ai-full-bleed]){padding:0 20px !important}",
8025
+ "[data-ai-container]{padding:0 20px !important}",
7911
8026
  "[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
7912
8027
  "[data-ai-cell]{width:100%;min-width:0}",
7913
8028
  // The rule above turns every row into a COLUMN flex container on phones, which silently breaks
@@ -8112,6 +8227,31 @@ function pricingCardEmphasis(featured, ctx, hasBg) {
8112
8227
  function textAttrs(ctx, path) {
8113
8228
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
8114
8229
  }
8230
+ var AI_RESPONSIVE_CSS = [
8231
+ "@media (max-width: 960px) {",
8232
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
8233
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
8234
+ "}",
8235
+ "@media (max-width: 640px) {",
8236
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
8237
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
8238
+ // Group containers flatten to a column on phones; span placements come along for free.
8239
+ // `:not([data-ai-group="overlap"])` for the same reason as the twin pair in AI_MOBILE_CSS — see
8240
+ // the comment there. This sheet's copy is the narrower selector, so without the exemption it
8241
+ // flattens an `overlap` hero below 640px even though the other rule stands down: measured a 390px
8242
+ // viewport with the heading/CTA in flow *under* the image, 219px of section background where the
8243
+ // layered text used to be.
8244
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) { display: flex !important; flex-direction: column !important; }',
8245
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) > * { grid-column: auto !important; }',
8246
+ // `:not([data-ai-full-bleed])` for the same reason as the twin rule in AI_MOBILE_CSS: this one
8247
+ // is narrower (0,2,0 vs 0,1,0) and also `!important`, so without the guard it wins outright and
8248
+ // a full-bleed hero gets its 20px side band back below 640px only — the one width where the
8249
+ // other rule had already been taught to stand down.
8250
+ " [data-ai-responsive] [data-ai-section-inner]:not([data-ai-full-bleed]) { padding-left: 20px !important; padding-right: 20px !important; }",
8251
+ " [data-ai-responsive] { overflow-x: hidden; }",
8252
+ " [data-ai-responsive] img { max-width: 100%; }",
8253
+ "}"
8254
+ ].join("\n");
8115
8255
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
8116
8256
  function MediaBox({
8117
8257
  refValue,
@@ -8166,7 +8306,7 @@ function MediaBox({
8166
8306
  position: fill ? "absolute" : void 0,
8167
8307
  inset: fill ? 0 : void 0,
8168
8308
  width: fill ? "100%" : fixedSize ? "auto" : "100%",
8169
- height: fill ? "100%" : void 0,
8309
+ height: fill ? "100%" : fixedSize ? height : void 0,
8170
8310
  aspectRatio: fill ? void 0 : aspectRatio,
8171
8311
  minHeight: fill || fixedSize ? void 0 : height ?? (aspectRatio ? void 0 : 180),
8172
8312
  borderRadius: radius,
@@ -9662,7 +9802,7 @@ function CollectionBlock({ node, ctx, path }) {
9662
9802
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
9663
9803
  "div",
9664
9804
  {
9665
- "data-ai-grid": "",
9805
+ "data-ai-grid": String(itemsPerRow),
9666
9806
  "data-ohw-pricing-collection": isPricing ? "" : void 0,
9667
9807
  style: {
9668
9808
  display: "grid",
@@ -9964,7 +10104,7 @@ function renderNode(node, ctx, path, ambientAlign) {
9964
10104
  },
9965
10105
  children: [
9966
10106
  i > 0 && ctx.heroOverlapFullBleed ? (
9967
- // The section's `data-ai-container` gave up its max-width/gutter so the base
10107
+ // The section's `data-ai-section-inner` gave up its max-width/gutter so the base
9968
10108
  // layer (this group's i===0 sibling) can span edge-to-edge — reintroduced here,
9969
10109
  // scoped to just the overlay text, so a heading/copy/CTA stack doesn't stretch
9970
10110
  // the full section width instead of reading at the usual content width.
@@ -10383,6 +10523,7 @@ function AiTreeRenderer({
10383
10523
  ...fillHeight ? { "data-ai-fill-height": "" } : {},
10384
10524
  ...needsOverlayClearance ? { "data-ai-overlay-clearance": "" } : {},
10385
10525
  ...bgAttrs,
10526
+ "data-ai-responsive": "",
10386
10527
  style: {
10387
10528
  position: "relative",
10388
10529
  padding: `${padCss} 0`,
@@ -10396,12 +10537,13 @@ function AiTreeRenderer({
10396
10537
  ...fillHeight ? { display: "grid" } : {}
10397
10538
  },
10398
10539
  children: [
10399
- isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10540
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
10400
10541
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
10542
+ isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10401
10543
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
10402
10544
  "div",
10403
10545
  {
10404
- "data-ai-container": "",
10546
+ "data-ai-section-inner": "",
10405
10547
  ...fullBleedMedia ? { "data-ai-full-bleed": "" } : {},
10406
10548
  style: {
10407
10549
  position: "relative",
@@ -10418,7 +10560,7 @@ function AiTreeRenderer({
10418
10560
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
10419
10561
  "div",
10420
10562
  {
10421
- "data-ai-row": "",
10563
+ "data-ai-columns": "",
10422
10564
  style: {
10423
10565
  display: "grid",
10424
10566
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -11080,9 +11222,10 @@ function applyAiSectionsToDom(state, options) {
11080
11222
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
11081
11223
  continue;
11082
11224
  }
11083
- const isNewMount = !existing || !existing.container.isConnected;
11084
11225
  let container = existing?.container;
11226
+ let isNewMount = false;
11085
11227
  if (!container || !container.isConnected) {
11228
+ isNewMount = true;
11086
11229
  existing?.root.unmount();
11087
11230
  container?.remove();
11088
11231
  container = document.createElement("div");
@@ -17241,7 +17384,7 @@ function apiBase() {
17241
17384
  function bridgeVersion() {
17242
17385
  try {
17243
17386
  if (true) {
17244
- return "0.1.158";
17387
+ return "0.1.160-next.427";
17245
17388
  }
17246
17389
  } catch {
17247
17390
  }
@@ -17255,7 +17398,7 @@ function stampBridgeVersionOnDocument() {
17255
17398
  return;
17256
17399
  }
17257
17400
  try {
17258
- const v = true ? "0.1.158" : "";
17401
+ const v = true ? "0.1.160-next.427" : "";
17259
17402
  if (v) {
17260
17403
  document.documentElement.setAttribute("data-ohw-bridge-version", v);
17261
17404
  window.__OHW_BRIDGE_VERSION__ = v;
@@ -17933,6 +18076,7 @@ function MediaOverlay({
17933
18076
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
17934
18077
  );
17935
18078
  }, [isVideo]);
18079
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
17936
18080
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
17937
18081
  const box = {
17938
18082
  position: "fixed",
@@ -18031,11 +18175,10 @@ function MediaOverlay({
18031
18175
  className: "flex items-center justify-center cursor-pointer",
18032
18176
  style: {
18033
18177
  ...box,
18034
- // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
18035
- // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
18036
- // in-document, pointer-events does it natively. The button below opts back in, so
18037
- // Replace still works.
18038
- pointerEvents: hover.hasTextOverlap || cueOnly ? "none" : "auto",
18178
+ // Visual only. `pointer-events: auto` on this full-bleed rect covered the navbar
18179
+ // (hero bg includes the absolute TopNav) and swallowed CTA press-drag. Text overlay
18180
+ // and cueOnly also need clicks to fall through — Replace / video controls opt back in.
18181
+ pointerEvents: "none",
18039
18182
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
18040
18183
  // than hovered. Hover keeps the existing tinted preview.
18041
18184
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -18062,17 +18205,17 @@ function MediaOverlay({
18062
18205
  },
18063
18206
  children: [
18064
18207
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
18065
- isVideo ? "Replace video" : "Replace image"
18208
+ replaceLabel
18066
18209
  ]
18067
18210
  }
18068
18211
  ),
18069
- replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
18212
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
18070
18213
  Button,
18071
18214
  {
18072
18215
  "data-ohw-media-overlay": "",
18073
18216
  variant: "outline",
18074
18217
  size: "sm",
18075
- "aria-label": isVideo ? "Replace video" : "Replace image",
18218
+ "aria-label": replaceLabel,
18076
18219
  className: "gap-1.5 cursor-pointer hover:bg-background",
18077
18220
  style: {
18078
18221
  ...OVERLAY_BUTTON_STYLE,
@@ -18095,7 +18238,7 @@ function MediaOverlay({
18095
18238
  },
18096
18239
  children: [
18097
18240
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
18098
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
18241
+ replaceMode === "full" ? replaceLabel : null
18099
18242
  ]
18100
18243
  }
18101
18244
  )
@@ -18164,6 +18307,89 @@ function CarouselOverlay({
18164
18307
  // src/ui/ai-section/AiSectionOverlay.tsx
18165
18308
  var import_react9 = require("react");
18166
18309
  var import_lucide_react7 = require("lucide-react");
18310
+
18311
+ // src/lib/ai-change-highlight.ts
18312
+ var STYLE_ID = "ohw-ai-changed-style";
18313
+ var MARK_ATTR = "data-ohw-ai-changed";
18314
+ var HIGHLIGHT_MS = 2200;
18315
+ var HIGHLIGHT_CSS = `
18316
+ @keyframes ohw-ai-changed-pulse {
18317
+ 0% { outline-color: rgba(0, 120, 229, 0.95); box-shadow: 0 0 0 5px rgba(0, 120, 229, 0.20); }
18318
+ 72% { outline-color: rgba(0, 120, 229, 0.95); box-shadow: 0 0 0 5px rgba(0, 120, 229, 0.12); }
18319
+ 100% { outline-color: rgba(0, 120, 229, 0); box-shadow: 0 0 0 5px rgba(0, 120, 229, 0); }
18320
+ }
18321
+ [${MARK_ATTR}] {
18322
+ outline: 2px solid rgba(0, 120, 229, 0.95) !important;
18323
+ outline-offset: 3px !important;
18324
+ animation: ohw-ai-changed-pulse ${HIGHLIGHT_MS}ms ease-out forwards !important;
18325
+ }
18326
+ @media (prefers-reduced-motion: reduce) {
18327
+ [${MARK_ATTR}] { animation: none !important; }
18328
+ }
18329
+ `;
18330
+ function ensureStyleSheet2() {
18331
+ if (document.getElementById(STYLE_ID)) return;
18332
+ const el = document.createElement("style");
18333
+ el.id = STYLE_ID;
18334
+ el.textContent = HIGHLIGHT_CSS;
18335
+ document.head.appendChild(el);
18336
+ }
18337
+ function targetsFor(keys, item) {
18338
+ const out = /* @__PURE__ */ new Set();
18339
+ for (const key of keys) {
18340
+ if (!key) continue;
18341
+ for (const el of Array.from(
18342
+ document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`)
18343
+ )) {
18344
+ out.add(el);
18345
+ }
18346
+ }
18347
+ if (item) {
18348
+ const section = document.querySelector(`[data-ohw-instance="${CSS.escape(item.sectionId)}"]`) ?? document.querySelector(`[data-ohw-section="${CSS.escape(item.sectionId)}"]`);
18349
+ const stamp = `${item.group}:${item.index}`;
18350
+ const el = section?.querySelector(`[data-ohw-item="${CSS.escape(stamp)}"]`);
18351
+ if (el) out.add(el);
18352
+ }
18353
+ return [...out];
18354
+ }
18355
+ var clearTimer = null;
18356
+ function clearChangeHighlight() {
18357
+ if (clearTimer !== null) {
18358
+ clearTimeout(clearTimer);
18359
+ clearTimer = null;
18360
+ }
18361
+ for (const el of Array.from(document.querySelectorAll(`[${MARK_ATTR}]`))) {
18362
+ el.removeAttribute(MARK_ATTR);
18363
+ }
18364
+ }
18365
+ function highlightChangedElements(keys, item) {
18366
+ clearChangeHighlight();
18367
+ const targets = targetsFor(keys, item);
18368
+ if (targets.length === 0) return 0;
18369
+ ensureStyleSheet2();
18370
+ for (const el of targets) el.setAttribute(MARK_ATTR, "");
18371
+ const rect = targets[0].getBoundingClientRect();
18372
+ if (rect.bottom < 0 || rect.top > window.innerHeight) {
18373
+ targets[0].scrollIntoView({ behavior: "smooth", block: "center" });
18374
+ }
18375
+ clearTimer = setTimeout(clearChangeHighlight, HIGHLIGHT_MS);
18376
+ return targets.length;
18377
+ }
18378
+ function installAiChangeHighlight() {
18379
+ const onMessage = (e) => {
18380
+ const data = e.data;
18381
+ if (data?.type !== "ow:ai-highlight") return;
18382
+ const keys = Array.isArray(data.keys) ? data.keys.filter((k) => typeof k === "string") : [];
18383
+ const raw = data.item;
18384
+ 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;
18385
+ const found = highlightChangedElements(keys, item);
18386
+ window.parent?.postMessage({ type: "ow:ai-highlight-done", found }, "*");
18387
+ };
18388
+ window.addEventListener("message", onMessage);
18389
+ return () => window.removeEventListener("message", onMessage);
18390
+ }
18391
+
18392
+ // src/ui/ai-section/AiSectionOverlay.tsx
18167
18393
  var import_jsx_runtime19 = require("react/jsx-runtime");
18168
18394
  var findSectionElement = findByInstanceId;
18169
18395
  function readRect(instanceId) {
@@ -18415,6 +18641,7 @@ function AiSectionOverlay({
18415
18641
  window.addEventListener("message", onMessage);
18416
18642
  return () => window.removeEventListener("message", onMessage);
18417
18643
  }, [postToParent2]);
18644
+ (0, import_react9.useEffect)(() => installAiChangeHighlight(), []);
18418
18645
  const [hoveredId, setHoveredId] = (0, import_react9.useState)(null);
18419
18646
  (0, import_react9.useEffect)(() => {
18420
18647
  const onMove = (e) => {
@@ -18605,7 +18832,7 @@ function AiSectionOverlay({
18605
18832
 
18606
18833
  // src/ui/ai-section/AiMediaStripOverlay.tsx
18607
18834
  var import_react10 = require("react");
18608
- var import_lucide_react8 = require("lucide-react");
18835
+ var import_lucide_react9 = require("lucide-react");
18609
18836
 
18610
18837
  // src/lib/ai-media-strip-tree.ts
18611
18838
  var MEDIA_STRIP_ITEM_MIN = 1;
@@ -18719,14 +18946,6 @@ function setMediaStripItemAsset(tree, index, assetId) {
18719
18946
  return true;
18720
18947
  });
18721
18948
  }
18722
- function setMediaStripItemAlt(tree, index, alt) {
18723
- return withMediaStrip(tree, (block) => {
18724
- const items = block.items;
18725
- if (index < 0 || index >= items.length) return false;
18726
- items[index] = { ...items[index], alt };
18727
- return true;
18728
- });
18729
- }
18730
18949
  function setMediaStripItemLink(tree, index, link) {
18731
18950
  return withMediaStrip(tree, (block) => {
18732
18951
  const items = block.items;
@@ -18744,11 +18963,117 @@ function setMediaStripItemLink(tree, index, link) {
18744
18963
  });
18745
18964
  }
18746
18965
 
18747
- // src/ui/ai-section/AiMediaStripOverlay.tsx
18966
+ // src/ui/dialog.tsx
18967
+ var React9 = __toESM(require("react"), 1);
18968
+ var import_radix_ui6 = require("radix-ui");
18969
+ var import_lucide_react8 = require("lucide-react");
18748
18970
  var import_jsx_runtime20 = require("react/jsx-runtime");
18749
- var PRIMARY3 = "#0885FE";
18750
- var OHW_BLUE = `var(--ohw-primary, ${PRIMARY3})`;
18751
- var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY3}) 60%, #000000)`;
18971
+ function Dialog2({
18972
+ ...props
18973
+ }) {
18974
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_radix_ui6.Dialog.Root, { "data-slot": "dialog", ...props });
18975
+ }
18976
+ function DialogPortal({
18977
+ ...props
18978
+ }) {
18979
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_radix_ui6.Dialog.Portal, { ...props });
18980
+ }
18981
+ function DialogOverlay({
18982
+ className,
18983
+ ...props
18984
+ }) {
18985
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18986
+ import_radix_ui6.Dialog.Overlay,
18987
+ {
18988
+ "data-slot": "dialog-overlay",
18989
+ "data-ohw-link-modal-root": "",
18990
+ className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
18991
+ ...props
18992
+ }
18993
+ );
18994
+ }
18995
+ var DialogContent = React9.forwardRef(
18996
+ ({ className, children, showCloseButton = true, container, ...props }, ref) => {
18997
+ const positionMode = container ? "absolute" : "fixed";
18998
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(DialogPortal, { container: container ?? void 0, children: [
18999
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
19000
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
19001
+ import_radix_ui6.Dialog.Content,
19002
+ {
19003
+ ref,
19004
+ "data-slot": "dialog-content",
19005
+ className: cn(
19006
+ positionMode,
19007
+ "left-1/2 top-1/2 z-[2147483647] flex w-full max-w-[448px] -translate-x-1/2 -translate-y-1/2 flex-col overflow-visible",
19008
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
19009
+ container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
19010
+ className
19011
+ ),
19012
+ onMouseDown: (e) => e.stopPropagation(),
19013
+ ...props,
19014
+ children: [
19015
+ children,
19016
+ showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19017
+ import_radix_ui6.Dialog.Close,
19018
+ {
19019
+ type: "button",
19020
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
19021
+ "aria-label": "Close",
19022
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react8.X, { size: 16, "aria-hidden": true })
19023
+ }
19024
+ ) : null
19025
+ ]
19026
+ }
19027
+ )
19028
+ ] });
19029
+ }
19030
+ );
19031
+ DialogContent.displayName = import_radix_ui6.Dialog.Content.displayName;
19032
+ function DialogHeader({
19033
+ className,
19034
+ ...props
19035
+ }) {
19036
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
19037
+ }
19038
+ function DialogFooter({
19039
+ className,
19040
+ ...props
19041
+ }) {
19042
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19043
+ "div",
19044
+ {
19045
+ className: cn("flex items-center justify-end gap-2", className),
19046
+ ...props
19047
+ }
19048
+ );
19049
+ }
19050
+ var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19051
+ import_radix_ui6.Dialog.Title,
19052
+ {
19053
+ ref,
19054
+ className: cn(
19055
+ "text-lg font-semibold leading-none tracking-tight text-card-foreground",
19056
+ className
19057
+ ),
19058
+ ...props
19059
+ }
19060
+ ));
19061
+ DialogTitle.displayName = import_radix_ui6.Dialog.Title.displayName;
19062
+ var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19063
+ import_radix_ui6.Dialog.Description,
19064
+ {
19065
+ ref,
19066
+ className: cn("text-sm text-muted-foreground", className),
19067
+ ...props
19068
+ }
19069
+ ));
19070
+ DialogDescription.displayName = import_radix_ui6.Dialog.Description.displayName;
19071
+ var DialogClose = import_radix_ui6.Dialog.Close;
19072
+
19073
+ // src/ui/ai-section/AiMediaStripOverlay.tsx
19074
+ var import_jsx_runtime21 = require("react/jsx-runtime");
19075
+ var PRIMARY3 = "var(--ohw-primary, #0885FE)";
19076
+ var CHROME_SELECTOR = "[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-link-modal-root], [data-ohw-section-picker], [data-ohw-ai-review], [data-ohw-more-menu]";
18752
19077
  function findMediaStripItemElement(sectionId, index) {
18753
19078
  const sectionEl = findByInstanceId(sectionId);
18754
19079
  if (!sectionEl) return null;
@@ -18758,14 +19083,35 @@ function findMediaStripItemElement(sectionId, index) {
18758
19083
  }
18759
19084
  return null;
18760
19085
  }
18761
- function readRect2(ref) {
18762
- const el = ref && findMediaStripItemElement(ref.sectionId, ref.index);
19086
+ function itemRefFromTarget(target) {
19087
+ if (!(target instanceof Element)) return null;
19088
+ if (target.closest(CHROME_SELECTOR)) return null;
19089
+ const itemEl = target.closest("[data-ohw-media-strip-item]");
19090
+ if (!itemEl) return null;
19091
+ const sectionEl = itemEl.closest("[data-ohw-section]");
19092
+ const unit = sectionEl ? movableUnit(sectionEl) : null;
19093
+ const sectionId = unit ? instanceIdOf(unit) || null : null;
19094
+ const index = Number(itemEl.dataset.ohwMediaStripIndex);
19095
+ if (!sectionId || Number.isNaN(index)) return null;
19096
+ return { sectionId, index };
19097
+ }
19098
+ function sameRef(a, b) {
19099
+ return a?.sectionId === b?.sectionId && a?.index === b?.index;
19100
+ }
19101
+ function rectOf(el) {
18763
19102
  if (!el) return null;
18764
19103
  const r2 = el.getBoundingClientRect();
18765
19104
  if (r2.width <= 0 || r2.height <= 0) return null;
18766
19105
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
18767
19106
  }
18768
- function useLiveItemRect(ref) {
19107
+ function readRect2(ref) {
19108
+ return rectOf(ref && findMediaStripItemElement(ref.sectionId, ref.index));
19109
+ }
19110
+ function readStripRect(ref) {
19111
+ const item = ref && findMediaStripItemElement(ref.sectionId, ref.index);
19112
+ return rectOf(item?.closest("[data-ohw-media-strip]") ?? item);
19113
+ }
19114
+ function useLiveItemRect(ref, strip = false) {
18769
19115
  const [rect, setRect] = (0, import_react10.useState)(null);
18770
19116
  (0, import_react10.useEffect)(() => {
18771
19117
  if (!ref) {
@@ -18773,7 +19119,7 @@ function useLiveItemRect(ref) {
18773
19119
  return;
18774
19120
  }
18775
19121
  const update = () => {
18776
- const next = readRect2(ref);
19122
+ const next = strip ? readStripRect(ref) : readRect2(ref);
18777
19123
  setRect(
18778
19124
  (prev) => prev && next && prev.top === next.top && prev.left === next.left && prev.width === next.width && prev.height === next.height ? prev : next
18779
19125
  );
@@ -18788,9 +19134,49 @@ function useLiveItemRect(ref) {
18788
19134
  window.removeEventListener("resize", update);
18789
19135
  clearInterval(interval);
18790
19136
  };
18791
- }, [ref?.sectionId, ref?.index]);
19137
+ }, [ref?.sectionId, ref?.index, strip]);
18792
19138
  return rect;
18793
19139
  }
19140
+ var TOOLBAR_STRIP_GAP = 52;
19141
+ var TOOLBAR_STRIP_GAP_MOBILE = 42;
19142
+ var TOOLBAR_EDGE_MARGIN2 = 4;
19143
+ function StripToolbarSlot({ stripRect, children }) {
19144
+ const slotRef = (0, import_react10.useRef)(null);
19145
+ const [shift, setShift] = (0, import_react10.useState)(0);
19146
+ const centerX = stripRect.left + stripRect.width / 2;
19147
+ (0, import_react10.useLayoutEffect)(() => {
19148
+ const slot = slotRef.current;
19149
+ if (!slot) return;
19150
+ const clamp = () => {
19151
+ const w = slot.offsetWidth;
19152
+ if (w <= 0) return;
19153
+ const half = w / 2;
19154
+ const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
19155
+ setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
19156
+ };
19157
+ clamp();
19158
+ const ro = new ResizeObserver(clamp);
19159
+ ro.observe(slot);
19160
+ return () => ro.disconnect();
19161
+ }, [centerX, children]);
19162
+ const mobile = window.innerWidth <= 480;
19163
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19164
+ "div",
19165
+ {
19166
+ ref: slotRef,
19167
+ "data-ohw-bridge": "",
19168
+ "data-ohw-item-interaction": "",
19169
+ className: "pointer-events-auto fixed",
19170
+ style: {
19171
+ top: stripRect.top - (mobile ? TOOLBAR_STRIP_GAP_MOBILE : TOOLBAR_STRIP_GAP),
19172
+ left: centerX + shift,
19173
+ transform: "translate(-50%, -100%)",
19174
+ zIndex: 2147483645
19175
+ },
19176
+ children
19177
+ }
19178
+ );
19179
+ }
18794
19180
  function requestValidateTree(postToParent2, tree) {
18795
19181
  return new Promise((resolve) => {
18796
19182
  const id = `ai-validate-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -18827,116 +19213,72 @@ function requestAssetPick(postToParent2) {
18827
19213
  postToParent2({ type: "ow:asset-pick", id });
18828
19214
  });
18829
19215
  }
18830
- function Pill({
19216
+ function ToolbarAction({
18831
19217
  label,
18832
19218
  onClick,
18833
- disabled,
18834
- active
19219
+ disabled = false,
19220
+ active = false,
19221
+ children
18835
19222
  }) {
18836
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18837
- "button",
18838
- {
18839
- type: "button",
18840
- onClick,
18841
- disabled,
18842
- style: {
18843
- height: 28,
18844
- padding: "0 10px",
18845
- display: "inline-flex",
18846
- alignItems: "center",
18847
- gap: 4,
18848
- borderRadius: 999,
18849
- border: "none",
18850
- cursor: disabled ? "default" : "pointer",
18851
- background: active ? OHW_BLUE_DARK : "#ffffff",
18852
- color: active ? "#ffffff" : OHW_BLUE_DARK,
18853
- opacity: disabled ? 0.4 : 1,
18854
- fontSize: 12,
18855
- lineHeight: "16px",
18856
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18857
- whiteSpace: "nowrap",
18858
- flex: "0 0 auto"
18859
- },
18860
- children: label
18861
- }
18862
- );
19223
+ const button = /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, active, onClick, className: "size-8", children });
19224
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19225
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "inline-flex", children: button }) : button }),
19226
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19227
+ ] });
18863
19228
  }
18864
- function AltInput({ value, onCommit }) {
18865
- const [draft, setDraft] = (0, import_react10.useState)(value);
18866
- (0, import_react10.useEffect)(() => setDraft(value), [value]);
18867
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
18868
- "div",
18869
- {
18870
- style: {
18871
- display: "flex",
18872
- alignItems: "center",
18873
- gap: 6,
18874
- height: 28,
18875
- padding: "0 12px",
18876
- borderRadius: 999,
18877
- background: "#ffffff",
18878
- width: "100%",
18879
- maxWidth: 260,
18880
- boxSizing: "border-box"
18881
- },
18882
- children: [
18883
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18884
- "span",
18885
- {
18886
- style: {
18887
- flex: "0 0 auto",
18888
- color: OHW_BLUE_DARK,
18889
- fontSize: 10,
18890
- fontWeight: 700,
18891
- textTransform: "uppercase",
18892
- letterSpacing: 0.4
18893
- },
18894
- children: "Alt"
18895
- }
18896
- ),
18897
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18898
- "input",
18899
- {
18900
- type: "text",
18901
- value: draft,
18902
- onChange: (e) => setDraft(e.target.value),
18903
- onBlur: () => {
18904
- if (draft !== value) onCommit(draft);
18905
- },
18906
- onKeyDown: (e) => {
18907
- if (e.key === "Enter") e.currentTarget.blur();
18908
- if (e.key === "Escape") {
18909
- setDraft(value);
18910
- e.currentTarget.blur();
18911
- }
18912
- },
18913
- placeholder: "Describe this image",
18914
- "aria-label": "Image alt text",
18915
- style: {
18916
- flex: 1,
18917
- minWidth: 0,
18918
- border: "none",
18919
- outline: "none",
18920
- background: "transparent",
18921
- fontSize: 12,
18922
- lineHeight: "16px",
18923
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18924
- color: "#1f2937"
18925
- }
18926
- }
18927
- )
18928
- ]
18929
- }
18930
- );
19229
+ function ToolbarSeparator() {
19230
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19231
+ }
19232
+ function OptionMenu({
19233
+ label,
19234
+ value,
19235
+ options,
19236
+ onChange
19237
+ }) {
19238
+ const current = options.find((o) => o.value === value) ?? options[0];
19239
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19240
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xs text-muted-foreground", children: label }),
19241
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DropdownMenu, { modal: false, children: [
19242
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19243
+ "button",
19244
+ {
19245
+ type: "button",
19246
+ "aria-label": label,
19247
+ className: "inline-flex h-8 min-w-[76px] items-center gap-[7px] rounded-[6px] bg-transparent px-2 text-[13px] font-medium text-foreground transition-colors hover:bg-muted data-[state=open]:bg-muted",
19248
+ children: [
19249
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "flex-1 text-left", children: current.label }),
19250
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ChevronDown, { size: 16, style: { pointerEvents: "none" } })
19251
+ ]
19252
+ }
19253
+ ) }),
19254
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[132px] min-w-[132px] p-1", children: options.map((option) => {
19255
+ const selected = option.value === value;
19256
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19257
+ DropdownMenuItem,
19258
+ {
19259
+ role: "menuitemradio",
19260
+ "aria-checked": selected,
19261
+ onSelect: () => onChange(option.value),
19262
+ className: cn("grid grid-cols-[1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
19263
+ children: [
19264
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: option.label }),
19265
+ selected && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Check, { size: 16, className: "text-bridge-primary" })
19266
+ ]
19267
+ },
19268
+ option.value
19269
+ );
19270
+ }) })
19271
+ ] })
19272
+ ] });
18931
19273
  }
18932
19274
  var VARIANTS = [
18933
19275
  { value: "logos", label: "Logos" },
18934
19276
  { value: "reel", label: "Reel" }
18935
19277
  ];
18936
19278
  var ALIGNS = [
18937
- { value: "left", label: "Left" },
18938
- { value: "center", label: "Center" },
18939
- { value: "right", label: "Right" }
19279
+ { value: "left", label: "Align left", Icon: import_lucide_react9.AlignLeft },
19280
+ { value: "center", label: "Align center", Icon: import_lucide_react9.AlignCenter },
19281
+ { value: "right", label: "Align right", Icon: import_lucide_react9.AlignRight }
18940
19282
  ];
18941
19283
  var HOVERS = [
18942
19284
  { value: "none", label: "None" },
@@ -18944,104 +19286,104 @@ var HOVERS = [
18944
19286
  { value: "zoom", label: "Zoom" },
18945
19287
  { value: "fade", label: "Fade" }
18946
19288
  ];
18947
- var TREATMENTS = [
18948
- { value: "color", label: "Color" },
18949
- { value: "grayscale", label: "Grayscale" }
18950
- ];
18951
19289
  var HTTP_URL_RE = /^https?:\/\/\S+$/i;
18952
- function LinkInput({ value, onCommit }) {
18953
- const [draft, setDraft] = (0, import_react10.useState)(value);
19290
+ function LinkModal({
19291
+ href,
19292
+ linkTarget,
19293
+ onClose,
19294
+ onSave
19295
+ }) {
19296
+ const [draft, setDraft] = (0, import_react10.useState)(href);
19297
+ const [newTab, setNewTab] = (0, import_react10.useState)(linkTarget !== "same-tab");
18954
19298
  const [invalid, setInvalid] = (0, import_react10.useState)(false);
18955
- (0, import_react10.useEffect)(() => {
18956
- setDraft(value);
18957
- setInvalid(false);
18958
- }, [value]);
18959
- const commit = () => {
19299
+ const save = () => {
18960
19300
  const next = draft.trim();
18961
- if (next === value) return setInvalid(false);
18962
- if (next && !HTTP_URL_RE.test(next)) return setInvalid(true);
18963
- setInvalid(false);
18964
- onCommit(next);
18965
- };
18966
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
18967
- "div",
18968
- {
18969
- style: {
18970
- display: "flex",
18971
- alignItems: "center",
18972
- gap: 6,
18973
- height: 28,
18974
- padding: "0 12px",
18975
- borderRadius: 999,
18976
- background: "#ffffff",
18977
- width: "100%",
18978
- maxWidth: 260,
18979
- boxSizing: "border-box",
18980
- boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
18981
- },
18982
- children: [
18983
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18984
- "span",
18985
- {
18986
- style: {
18987
- flex: "0 0 auto",
18988
- color: OHW_BLUE_DARK,
18989
- fontSize: 10,
18990
- fontWeight: 700,
18991
- textTransform: "uppercase",
18992
- letterSpacing: 0.4
18993
- },
18994
- children: "Link"
18995
- }
18996
- ),
18997
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
18998
- "input",
18999
- {
19000
- type: "text",
19001
- inputMode: "url",
19002
- value: draft,
19003
- onChange: (e) => setDraft(e.target.value),
19004
- onBlur: commit,
19005
- onKeyDown: (e) => {
19006
- if (e.key === "Enter") e.currentTarget.blur();
19007
- if (e.key === "Escape") {
19008
- setDraft(value);
19009
- setInvalid(false);
19010
- e.currentTarget.blur();
19011
- }
19012
- },
19013
- placeholder: "https://example.com",
19014
- "aria-label": "Image link URL",
19015
- "aria-invalid": invalid,
19016
- style: {
19017
- flex: 1,
19018
- minWidth: 0,
19019
- border: "none",
19020
- outline: "none",
19021
- background: "transparent",
19022
- fontSize: 12,
19023
- lineHeight: "16px",
19024
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
19025
- color: "#1f2937"
19026
- }
19027
- }
19028
- )
19029
- ]
19301
+ if (next && !HTTP_URL_RE.test(next)) {
19302
+ setInvalid(true);
19303
+ return;
19030
19304
  }
19031
- );
19305
+ onSave(next, newTab ? "new-tab" : "same-tab");
19306
+ onClose();
19307
+ };
19308
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Dialog2, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DialogContent, { "data-ohw-link-modal-root": "", className: "max-w-[400px] gap-4 p-5", children: [
19309
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DialogHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DialogTitle, { className: "text-base", children: "Image link" }) }),
19310
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex flex-col gap-3", children: [
19311
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19312
+ "input",
19313
+ {
19314
+ autoFocus: true,
19315
+ type: "text",
19316
+ inputMode: "url",
19317
+ value: draft,
19318
+ onChange: (e) => {
19319
+ setDraft(e.target.value);
19320
+ setInvalid(false);
19321
+ },
19322
+ onKeyDown: (e) => {
19323
+ if (e.key === "Enter") save();
19324
+ },
19325
+ placeholder: "https://example.com",
19326
+ "aria-label": "Image link URL",
19327
+ "aria-invalid": invalid,
19328
+ className: cn(
19329
+ "h-9 w-full rounded-[6px] border border-border bg-background px-3 text-[13px] text-foreground outline-none placeholder:text-muted-foreground/70 focus:border-bridge-primary",
19330
+ invalid && "border-destructive focus:border-destructive"
19331
+ )
19332
+ }
19333
+ ),
19334
+ invalid && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-xs text-destructive", children: "Enter a full URL starting with http:// or https://" }),
19335
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("label", { className: "flex items-center gap-2 text-[13px] text-foreground", children: [
19336
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("input", { type: "checkbox", checked: newTab, onChange: (e) => setNewTab(e.target.checked) }),
19337
+ "Open in new tab"
19338
+ ] })
19339
+ ] }),
19340
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DialogFooter, { children: [
19341
+ href && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19342
+ "button",
19343
+ {
19344
+ type: "button",
19345
+ onClick: () => {
19346
+ onSave("", "new-tab");
19347
+ onClose();
19348
+ },
19349
+ className: "mr-auto rounded-[6px] px-3 py-2 text-[13px] font-medium text-destructive transition-colors hover:bg-muted",
19350
+ children: "Remove link"
19351
+ }
19352
+ ),
19353
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19354
+ "button",
19355
+ {
19356
+ type: "button",
19357
+ onClick: onClose,
19358
+ className: "rounded-[6px] px-3 py-2 text-[13px] font-medium text-foreground transition-colors hover:bg-muted",
19359
+ children: "Cancel"
19360
+ }
19361
+ ),
19362
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19363
+ "button",
19364
+ {
19365
+ type: "button",
19366
+ onClick: save,
19367
+ className: "rounded-[6px] bg-bridge-primary px-3 py-2 text-[13px] font-medium text-primary-foreground transition-opacity hover:opacity-90",
19368
+ children: "Save"
19369
+ }
19370
+ )
19371
+ ] })
19372
+ ] }) });
19032
19373
  }
19033
19374
  function MediaStripToolbar({
19034
19375
  entry,
19035
19376
  index,
19036
19377
  pending,
19037
19378
  error,
19379
+ itemEditOn,
19380
+ onToggleItemEdit,
19038
19381
  onSetVariant,
19039
19382
  onSetAlign,
19040
19383
  onSetHover,
19041
19384
  onSetTreatment,
19042
19385
  onResize,
19043
- onSetAlt,
19044
- onSetLink,
19386
+ onOpenLink,
19045
19387
  onAddItem,
19046
19388
  onRemoveItem,
19047
19389
  onChangeImage,
@@ -19050,81 +19392,102 @@ function MediaStripToolbar({
19050
19392
  }) {
19051
19393
  const items = mediaStripItems(entry.tree);
19052
19394
  const itemCount = items.length;
19053
- const currentAlt = items[index]?.alt ?? "";
19054
- const currentHref = items[index]?.href ?? "";
19055
- const currentTarget = items[index]?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
19056
19395
  const slots = findMediaStripSlots(entry.tree);
19057
19396
  const variant = slots?.variant === "reel" ? "reel" : "logos";
19058
19397
  const align = slots?.align === "left" || slots?.align === "right" ? slots.align : "center";
19059
19398
  const hover = slots?.hover === "none" || slots?.hover === "lift" || slots?.hover === "zoom" || slots?.hover === "fade" ? slots.hover : "lift";
19060
19399
  const treatment = slots?.treatment === "color" || slots?.treatment === "grayscale" ? slots.treatment : variant === "logos" ? "grayscale" : "color";
19061
19400
  const sizeValue = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19062
- const isMobile = window.innerWidth <= 480;
19063
- const rowStyle = isMobile ? {
19064
- display: "flex",
19065
- gap: 6,
19066
- flexWrap: "nowrap",
19067
- justifyContent: "center",
19068
- overflowX: "auto",
19069
- WebkitOverflowScrolling: "touch",
19070
- width: "100%",
19071
- scrollbarWidth: "none"
19072
- } : { display: "flex", gap: 6, flexWrap: "nowrap", justifyContent: "center" };
19073
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
19401
+ const mobile = window.innerWidth <= 480;
19402
+ const gatedLabel = (label) => itemEditOn ? label : "Turn on the star to edit this image";
19403
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19074
19404
  "div",
19075
19405
  {
19076
19406
  "data-ohw-bridge": "",
19077
19407
  "data-ohw-toolbar": "",
19078
19408
  onMouseDown: (e) => e.stopPropagation(),
19079
- style: {
19080
- display: "flex",
19081
- flexDirection: "column",
19082
- gap: 6,
19083
- alignItems: "center",
19084
- background: OHW_BLUE,
19085
- padding: 8,
19086
- borderRadius: 12,
19087
- boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
19088
- pointerEvents: pending ? "none" : "auto",
19089
- opacity: pending ? 0.6 : 1,
19090
- maxWidth: isMobile ? "calc(100vw - 24px)" : "none"
19091
- },
19409
+ className: cn("flex flex-col items-center gap-1.5 font-sans", pending && "pointer-events-none opacity-60"),
19410
+ style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
19092
19411
  children: [
19093
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
19094
- VARIANTS.map((v) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: v.label, active: variant === v.value, onClick: () => onSetVariant(v.value) }, v.value)),
19095
- ALIGNS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: a.label, active: align === a.value, onClick: () => onSetAlign(a.value) }, a.value))
19096
- ] }),
19097
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
19098
- HOVERS.map((h) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: h.label, active: hover === h.value, onClick: () => onSetHover(h.value) }, h.value)),
19099
- TREATMENTS.map((t) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: t.label, active: treatment === t.value, onClick: () => onSetTreatment(t.value) }, t.value))
19100
- ] }),
19101
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
19102
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2212", onClick: () => onResize(-8) }),
19103
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { style: { color: "#ffffff", fontSize: 12, minWidth: 34, textAlign: "center" }, children: [
19104
- sizeValue,
19105
- "px"
19106
- ] }),
19107
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "+", onClick: () => onResize(8) })
19108
- ] }),
19109
- /* @__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 }) }),
19110
- /* @__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) }) }),
19111
- currentHref && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
19112
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "New tab", active: currentTarget === "new-tab", onClick: () => onSetLink(currentHref, "new-tab") }),
19113
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "Same tab", active: currentTarget === "same-tab", onClick: () => onSetLink(currentHref, "same-tab") })
19114
- ] }),
19115
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
19116
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "Change Image", onClick: onChangeImage }),
19117
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "+ Item", onClick: onAddItem }),
19118
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2212 Item", onClick: onRemoveItem, disabled: itemCount <= 1 })
19119
- ] }),
19120
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
19121
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2190 Move", onClick: onMoveLeft, disabled: index <= 0 }),
19122
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2192 Move", onClick: onMoveRight, disabled: index >= itemCount - 1 })
19123
- ] }),
19124
- error && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
19412
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19413
+ "div",
19414
+ {
19415
+ className: cn(
19416
+ "flex items-center gap-1 rounded-[10px] border border-border bg-background p-1 whitespace-nowrap",
19417
+ "shadow-[0px_10px_15px_-3px_rgba(0,0,0,0.1),0px_4px_6px_-4px_rgba(0,0,0,0.1)]",
19418
+ mobile ? "flex-wrap justify-center" : "flex-nowrap"
19419
+ ),
19420
+ children: [
19421
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19422
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19423
+ CustomToolbarButton,
19424
+ {
19425
+ type: "button",
19426
+ "aria-label": itemEditOn ? "Done editing this image" : "Edit this image",
19427
+ "aria-pressed": itemEditOn,
19428
+ onClick: onToggleItemEdit,
19429
+ className: "group size-8",
19430
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19431
+ import_lucide_react9.Star,
19432
+ {
19433
+ size: 16,
19434
+ fill: "currentColor",
19435
+ strokeWidth: 0,
19436
+ className: cn(
19437
+ "transition-colors",
19438
+ itemEditOn ? "text-[#f59e0b] group-hover:text-[#d97706]" : "text-[#d6d3d1] group-hover:text-[#fbbf24]"
19439
+ ),
19440
+ style: { pointerEvents: "none" }
19441
+ }
19442
+ )
19443
+ }
19444
+ ) }),
19445
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: itemEditOn ? "Done editing this image" : "Edit this image" })
19446
+ ] }),
19447
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19448
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: gatedLabel("Change image"), onClick: onChangeImage, disabled: !itemEditOn, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Image, { size: 16, style: { pointerEvents: "none" } }) }),
19449
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: gatedLabel("Link"), onClick: onOpenLink, disabled: !itemEditOn, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Link, { size: 16, style: { pointerEvents: "none" } }) }),
19450
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: gatedLabel("Move left"), onClick: onMoveLeft, disabled: !itemEditOn || index <= 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ArrowLeft, { size: 16, style: { pointerEvents: "none" } }) }),
19451
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19452
+ ToolbarAction,
19453
+ {
19454
+ label: gatedLabel("Move right"),
19455
+ onClick: onMoveRight,
19456
+ disabled: !itemEditOn || index >= itemCount - 1,
19457
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ArrowRight, { size: 16, style: { pointerEvents: "none" } })
19458
+ }
19459
+ ),
19460
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: gatedLabel("Remove image"), onClick: onRemoveItem, disabled: !itemEditOn || itemCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ImageMinus, { size: 16, style: { pointerEvents: "none" } }) }),
19461
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19462
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(OptionMenu, { label: "Layout", value: variant, options: VARIANTS, onChange: onSetVariant }),
19463
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(OptionMenu, { label: "Hover", value: hover, options: HOVERS, onChange: onSetHover }),
19464
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19465
+ ToolbarAction,
19466
+ {
19467
+ label: treatment === "grayscale" ? "Show in color" : "Show grayscale",
19468
+ active: treatment === "grayscale",
19469
+ onClick: () => onSetTreatment(treatment === "grayscale" ? "color" : "grayscale"),
19470
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Palette, { size: 16, style: { pointerEvents: "none" } })
19471
+ }
19472
+ ),
19473
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19474
+ ALIGNS.map(({ value, label, Icon }) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label, active: align === value, onClick: () => onSetAlign(value), children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Icon, { size: 16, style: { pointerEvents: "none" } }) }, value)),
19475
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19476
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Smaller", onClick: () => onResize(-8), children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Minus, { size: 16, style: { pointerEvents: "none" } }) }),
19477
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "min-w-[40px] text-center text-xs tabular-nums text-muted-foreground", children: [
19478
+ sizeValue,
19479
+ "px"
19480
+ ] }),
19481
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Larger", onClick: () => onResize(8), children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Plus, { size: 16, style: { pointerEvents: "none" } }) }),
19482
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19483
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Add image", onClick: onAddItem, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ImagePlus, { size: 16, style: { pointerEvents: "none" } }) })
19484
+ ]
19485
+ }
19486
+ ),
19487
+ error && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19125
19488
  ]
19126
19489
  }
19127
- );
19490
+ ) });
19128
19491
  }
19129
19492
  function findMediaStripSlots(tree) {
19130
19493
  const stack = tree.rows.flatMap((row) => row.blocks);
@@ -19143,31 +19506,23 @@ function AiMediaStripOverlay({
19143
19506
  }) {
19144
19507
  const [hover, setHover] = (0, import_react10.useState)(null);
19145
19508
  const [active, setActive] = (0, import_react10.useState)(null);
19509
+ const [itemEditOn, setItemEditOn] = (0, import_react10.useState)(false);
19510
+ const [linkOpen, setLinkOpen] = (0, import_react10.useState)(false);
19146
19511
  const [pending, setPending] = (0, import_react10.useState)(false);
19147
19512
  const [error, setError] = (0, import_react10.useState)(null);
19148
19513
  const activeRef = (0, import_react10.useRef)(null);
19149
19514
  activeRef.current = active;
19515
+ const linkOpenRef = (0, import_react10.useRef)(false);
19516
+ linkOpenRef.current = linkOpen;
19150
19517
  (0, import_react10.useEffect)(() => {
19151
19518
  const onMove = (e) => {
19519
+ const ref = itemRefFromTarget(e.target);
19152
19520
  const t = e.target;
19153
- if (!(t instanceof HTMLElement)) return;
19154
- 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]")) {
19155
- return;
19156
- }
19157
- const itemEl = t.closest("[data-ohw-media-strip-item]");
19158
- if (!itemEl) {
19159
- setHover(null);
19160
- return;
19161
- }
19162
- const sectionEl = itemEl.closest("[data-ohw-section]");
19163
- const unit = sectionEl ? movableUnit(sectionEl) : null;
19164
- const sectionId = unit ? instanceIdOf(unit) || null : null;
19165
- const index = Number(itemEl.dataset.ohwMediaStripIndex);
19166
- if (!sectionId || Number.isNaN(index)) {
19167
- setHover(null);
19168
- return;
19169
- }
19170
- setHover({ sectionId, index });
19521
+ const overEditable = Boolean(t?.closest?.("[data-ohw-editable]"));
19522
+ setHover((prev) => {
19523
+ const next = ref && !overEditable ? ref : null;
19524
+ return sameRef(prev, next) ? prev : next;
19525
+ });
19171
19526
  };
19172
19527
  const onLeave = () => setHover(null);
19173
19528
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -19179,23 +19534,25 @@ function AiMediaStripOverlay({
19179
19534
  }, []);
19180
19535
  (0, import_react10.useEffect)(() => {
19181
19536
  const onClick = (e) => {
19182
- const current = activeRef.current;
19183
- if (!current) return;
19184
19537
  const t = e.target;
19185
- if (!(t instanceof HTMLElement)) return;
19186
- if (t.closest("[data-ohw-item-interaction]")) return;
19187
- const itemEl = findMediaStripItemElement(current.sectionId, current.index);
19188
- if (itemEl && itemEl.contains(t)) return;
19189
- setActive(null);
19538
+ if (!(t instanceof Element)) return;
19539
+ if (t.closest(CHROME_SELECTOR)) return;
19540
+ const ref = itemRefFromTarget(t);
19541
+ if (sameRef(activeRef.current, ref)) return;
19542
+ if (ref) clearInteractionChromeRef.current();
19543
+ setItemEditOn(false);
19190
19544
  setError(null);
19545
+ setActive(ref);
19191
19546
  };
19192
19547
  document.addEventListener("click", onClick, true);
19193
19548
  return () => document.removeEventListener("click", onClick, true);
19194
- }, []);
19549
+ }, [clearInteractionChromeRef]);
19195
19550
  (0, import_react10.useEffect)(() => {
19196
19551
  const onBlur = () => {
19197
19552
  if (!activeRef.current) return;
19198
19553
  setActive(null);
19554
+ setItemEditOn(false);
19555
+ setLinkOpen(false);
19199
19556
  setError(null);
19200
19557
  };
19201
19558
  window.addEventListener("blur", onBlur);
@@ -19205,7 +19562,10 @@ function AiMediaStripOverlay({
19205
19562
  const onKeyDown = (e) => {
19206
19563
  if (e.key !== "Escape") return;
19207
19564
  if (!activeRef.current) return;
19565
+ if (linkOpenRef.current) return;
19566
+ if (document.querySelector('[data-ohw-more-menu][data-state="open"]')) return;
19208
19567
  setActive(null);
19568
+ setItemEditOn(false);
19209
19569
  setError(null);
19210
19570
  };
19211
19571
  document.addEventListener("keydown", onKeyDown);
@@ -19239,137 +19599,133 @@ function AiMediaStripOverlay({
19239
19599
  tree: nextTree,
19240
19600
  label: entry.label,
19241
19601
  media: extraMedia ? { ...entry.media, ...extraMedia } : entry.media,
19242
- ...entry.brand ? { brand: entry.brand } : {}
19602
+ ...entry.brand ? { brand: entry.brand } : {},
19603
+ // The user is already looking at the strip they just edited — recentering the viewport
19604
+ // on every edit reads as the section jumping around.
19605
+ skipScrollIntoView: true
19243
19606
  });
19244
19607
  return true;
19245
19608
  },
19246
19609
  [findEntry, postToParent2, applyTreeLocalRef]
19247
19610
  );
19248
19611
  const activeEntry = active ? findEntry(active.sectionId) : null;
19249
- const hoverHandleRect = useLiveItemRect(!active && hover ? hover : null);
19250
19612
  const activeItemRect = useLiveItemRect(active);
19251
- const TOOLBAR_APPROX_H = 170;
19252
- const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
19253
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
19254
- hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19255
- "button",
19256
- {
19257
- type: "button",
19258
- "data-ohw-toolbar": "",
19259
- onClick: () => {
19260
- clearInteractionChromeRef.current();
19261
- setActive(hover);
19262
- },
19263
- "aria-label": "Configure media item",
19264
- style: {
19265
- position: "fixed",
19266
- top: hoverHandleRect.top + 8,
19267
- left: hoverHandleRect.left + hoverHandleRect.width - 36,
19268
- width: 28,
19269
- height: 28,
19270
- borderRadius: 8,
19271
- border: `1.5px dashed ${PRIMARY3}`,
19272
- background: "#ffffff",
19273
- color: PRIMARY3,
19274
- display: "flex",
19275
- alignItems: "center",
19276
- justifyContent: "center",
19277
- cursor: "pointer",
19278
- zIndex: 2147483643,
19279
- fontSize: 14,
19280
- fontWeight: 700
19281
- },
19282
- children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react8.Star, { size: 14, style: { pointerEvents: "none" } })
19283
- }
19284
- ),
19285
- activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19286
- ItemInteractionLayer,
19287
- {
19288
- rect: activeItemRect,
19289
- state: toolbarState,
19290
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19291
- MediaStripToolbar,
19292
- {
19293
- entry: activeEntry,
19294
- index: active.index,
19295
- pending,
19296
- error,
19297
- onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
19298
- onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
19299
- onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
19300
- onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
19301
- onResize: (delta) => {
19302
- const slots = findMediaStripSlots(activeEntry.tree);
19303
- const variant = slots?.variant === "reel" ? "reel" : "logos";
19304
- const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19305
- void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
19306
- },
19307
- onSetAlt: (alt) => {
19308
- void runMutation(active.sectionId, (tree) => setMediaStripItemAlt(tree, active.index, alt));
19309
- },
19310
- onSetLink: (href, linkTarget) => {
19613
+ const activeStripRect = useLiveItemRect(active, true);
19614
+ const hoverRect = useLiveItemRect(hover && !sameRef(hover, active) ? hover : null);
19615
+ const activeItems = activeEntry ? mediaStripItems(activeEntry.tree) : [];
19616
+ const activeItem = active ? activeItems[active.index] : void 0;
19617
+ const activeHref = activeItem?.href ?? "";
19618
+ const activeTarget = activeItem?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
19619
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19620
+ hoverRect && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19621
+ activeItemRect && activeStripRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19622
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: activeItemRect, state: "active-top" }),
19623
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StripToolbarSlot, { stripRect: activeStripRect, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19624
+ MediaStripToolbar,
19625
+ {
19626
+ entry: activeEntry,
19627
+ index: active.index,
19628
+ pending,
19629
+ error,
19630
+ itemEditOn,
19631
+ onToggleItemEdit: () => setItemEditOn((on) => !on),
19632
+ onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
19633
+ onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
19634
+ onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
19635
+ onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
19636
+ onResize: (delta) => {
19637
+ const slots = findMediaStripSlots(activeEntry.tree);
19638
+ const variant = slots?.variant === "reel" ? "reel" : "logos";
19639
+ const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19640
+ void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
19641
+ },
19642
+ onOpenLink: () => setLinkOpen(true),
19643
+ onAddItem: () => {
19644
+ void requestAssetPick(postToParent2).then((picked) => {
19645
+ if (!picked) return;
19646
+ const item = { assetId: picked.assetId, alt: "Logo" };
19311
19647
  void runMutation(
19312
19648
  active.sectionId,
19313
- (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
19314
- );
19315
- },
19316
- onAddItem: () => {
19317
- void requestAssetPick(postToParent2).then((picked) => {
19318
- if (!picked) return;
19319
- const item = { assetId: picked.assetId, alt: "Logo" };
19320
- void runMutation(
19321
- active.sectionId,
19322
- (tree) => addMediaStripItem(tree, item, active.index),
19323
- { [picked.assetId]: picked.url }
19324
- ).then((applied) => {
19325
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19326
- });
19649
+ (tree) => addMediaStripItem(tree, item, active.index),
19650
+ { [picked.assetId]: picked.url }
19651
+ ).then((applied) => {
19652
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19327
19653
  });
19328
- },
19329
- onChangeImage: () => {
19330
- void requestAssetPick(postToParent2).then((picked) => {
19331
- if (!picked) return;
19332
- void runMutation(
19333
- active.sectionId,
19334
- (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
19335
- { [picked.assetId]: picked.url }
19336
- );
19337
- });
19338
- },
19339
- onRemoveItem: () => {
19340
- void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
19341
- (applied) => {
19342
- if (applied) setActive(null);
19343
- }
19344
- );
19345
- },
19346
- onMoveLeft: () => {
19347
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
19348
- (applied) => {
19349
- if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
19350
- }
19351
- );
19352
- },
19353
- onMoveRight: () => {
19354
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
19355
- (applied) => {
19356
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19357
- }
19654
+ });
19655
+ },
19656
+ onChangeImage: () => {
19657
+ void requestAssetPick(postToParent2).then((picked) => {
19658
+ if (!picked) return;
19659
+ void runMutation(
19660
+ active.sectionId,
19661
+ (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
19662
+ { [picked.assetId]: picked.url }
19358
19663
  );
19359
- }
19664
+ });
19665
+ },
19666
+ onRemoveItem: () => {
19667
+ void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
19668
+ (applied) => {
19669
+ if (applied) setActive(null);
19670
+ }
19671
+ );
19672
+ },
19673
+ onMoveLeft: () => {
19674
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
19675
+ (applied) => {
19676
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
19677
+ }
19678
+ );
19679
+ },
19680
+ onMoveRight: () => {
19681
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
19682
+ (applied) => {
19683
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19684
+ }
19685
+ );
19360
19686
  }
19361
- )
19362
- }
19363
- )
19687
+ }
19688
+ ) }),
19689
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19690
+ "div",
19691
+ {
19692
+ "data-ohw-bridge": "",
19693
+ "data-ohw-item-interaction": "",
19694
+ "aria-hidden": true,
19695
+ className: "pointer-events-none fixed rounded-[4px] px-[7px] py-[3px] font-sans text-[11px] font-medium leading-none text-primary-foreground whitespace-nowrap",
19696
+ style: {
19697
+ top: activeItemRect.top + activeItemRect.height + 10,
19698
+ left: activeItemRect.left - 5,
19699
+ background: PRIMARY3,
19700
+ zIndex: 2147483644
19701
+ },
19702
+ children: "Media item"
19703
+ }
19704
+ ),
19705
+ linkOpen && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19706
+ LinkModal,
19707
+ {
19708
+ href: activeHref,
19709
+ linkTarget: activeTarget,
19710
+ onClose: () => setLinkOpen(false),
19711
+ onSave: (href, linkTarget) => {
19712
+ void runMutation(
19713
+ active.sectionId,
19714
+ (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
19715
+ );
19716
+ }
19717
+ }
19718
+ )
19719
+ ] })
19364
19720
  ] });
19365
19721
  }
19366
19722
 
19367
19723
  // src/ui/ai-section/AiPricingCardOverlay.tsx
19368
19724
  var import_react11 = require("react");
19369
- var import_lucide_react9 = require("lucide-react");
19370
- var import_jsx_runtime21 = require("react/jsx-runtime");
19725
+ var import_lucide_react10 = require("lucide-react");
19726
+ var import_jsx_runtime22 = require("react/jsx-runtime");
19371
19727
  var PRIMARY4 = "var(--ohw-primary, #0885FE)";
19372
- var CHROME_SELECTOR = "[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-link-modal-root], [data-ohw-section-picker], [data-ohw-ai-review], [data-ohw-more-menu]";
19728
+ var CHROME_SELECTOR2 = "[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-link-modal-root], [data-ohw-section-picker], [data-ohw-ai-review], [data-ohw-more-menu]";
19373
19729
  function findPricingCardElement(sectionId, index) {
19374
19730
  const sectionEl = findByInstanceId(sectionId);
19375
19731
  if (!sectionEl) return null;
@@ -19381,7 +19737,7 @@ function findPricingCardElement(sectionId, index) {
19381
19737
  }
19382
19738
  function cardRefFromTarget(target) {
19383
19739
  if (!(target instanceof Element)) return null;
19384
- if (target.closest(CHROME_SELECTOR)) return null;
19740
+ if (target.closest(CHROME_SELECTOR2)) return null;
19385
19741
  const cardEl = target.closest("[data-ohw-pricing-card]");
19386
19742
  if (!cardEl) return null;
19387
19743
  const sectionEl = cardEl.closest("[data-ohw-section]");
@@ -19391,21 +19747,21 @@ function cardRefFromTarget(target) {
19391
19747
  if (!sectionId || Number.isNaN(index)) return null;
19392
19748
  return { sectionId, index };
19393
19749
  }
19394
- function sameRef(a, b) {
19750
+ function sameRef2(a, b) {
19395
19751
  return a?.sectionId === b?.sectionId && a?.index === b?.index;
19396
19752
  }
19397
- function rectOf(el) {
19753
+ function rectOf2(el) {
19398
19754
  if (!el) return null;
19399
19755
  const r2 = el.getBoundingClientRect();
19400
19756
  if (r2.width <= 0 || r2.height <= 0) return null;
19401
19757
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
19402
19758
  }
19403
19759
  function readRect3(ref) {
19404
- return rectOf(ref && findPricingCardElement(ref.sectionId, ref.index));
19760
+ return rectOf2(ref && findPricingCardElement(ref.sectionId, ref.index));
19405
19761
  }
19406
19762
  function readGridRect(ref) {
19407
19763
  const card = ref && findPricingCardElement(ref.sectionId, ref.index);
19408
- return rectOf(card?.closest("[data-ohw-pricing-collection]") ?? card);
19764
+ return rectOf2(card?.closest("[data-ohw-pricing-collection]") ?? card);
19409
19765
  }
19410
19766
  function useLiveCardRect(ref, grid = false) {
19411
19767
  const [rect, setRect] = (0, import_react11.useState)(null);
@@ -19435,7 +19791,7 @@ function useLiveCardRect(ref, grid = false) {
19435
19791
  }
19436
19792
  var TOOLBAR_GRID_GAP = 52;
19437
19793
  var TOOLBAR_GRID_GAP_MOBILE = 42;
19438
- var TOOLBAR_EDGE_MARGIN2 = 4;
19794
+ var TOOLBAR_EDGE_MARGIN3 = 4;
19439
19795
  function GridToolbarSlot({ gridRect, children }) {
19440
19796
  const slotRef = (0, import_react11.useRef)(null);
19441
19797
  const [shift, setShift] = (0, import_react11.useState)(0);
@@ -19447,7 +19803,7 @@ function GridToolbarSlot({ gridRect, children }) {
19447
19803
  const w = slot.offsetWidth;
19448
19804
  if (w <= 0) return;
19449
19805
  const half = w / 2;
19450
- const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
19806
+ const clamped = Math.max(TOOLBAR_EDGE_MARGIN3 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half));
19451
19807
  setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
19452
19808
  };
19453
19809
  clamp();
@@ -19456,7 +19812,7 @@ function GridToolbarSlot({ gridRect, children }) {
19456
19812
  return () => ro.disconnect();
19457
19813
  }, [centerX, children]);
19458
19814
  const mobile = window.innerWidth <= 480;
19459
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19815
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19460
19816
  "div",
19461
19817
  {
19462
19818
  ref: slotRef,
@@ -19491,7 +19847,7 @@ function requestValidateTree2(postToParent2, tree) {
19491
19847
  });
19492
19848
  }
19493
19849
  function StyleSwatch({ style }) {
19494
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19850
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19495
19851
  "span",
19496
19852
  {
19497
19853
  "aria-hidden": true,
@@ -19505,35 +19861,35 @@ function StyleSwatch({ style }) {
19505
19861
  );
19506
19862
  }
19507
19863
  function ButtonGlyph() {
19508
- 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: [
19509
- /* @__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" }),
19510
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
19864
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("svg", { width: "18", height: "12", viewBox: "0 0 18 12", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19865
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("rect", { x: "0.75", y: "0.75", width: "16.5", height: "10.5", rx: "3", stroke: "currentColor", strokeWidth: "1.5" }),
19866
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
19511
19867
  ] });
19512
19868
  }
19513
19869
  function FeatureGlyph({ mode }) {
19514
- 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: [
19515
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
19516
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
19517
- /* @__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" }),
19518
- /* @__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" }),
19519
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
19520
- mode === "add" && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
19870
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19871
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
19872
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
19873
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("circle", { cx: "13.5", cy: "13.5", r: "4.25", fill: "var(--ohw-background, #ffffff)", stroke: "var(--ohw-background, #ffffff)", strokeWidth: "1" }),
19874
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("circle", { cx: "13.5", cy: "13.5", r: "3.75", fill: "var(--ohw-background, #ffffff)", stroke: PRIMARY4, strokeWidth: "1" }),
19875
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
19876
+ mode === "add" && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
19521
19877
  ] });
19522
19878
  }
19523
- function ToolbarAction({
19879
+ function ToolbarAction2({
19524
19880
  label,
19525
19881
  onClick,
19526
19882
  disabled = false,
19527
19883
  children
19528
19884
  }) {
19529
- const button = /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
19530
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19531
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "inline-flex", children: button }) : button }),
19532
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19885
+ const button = /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
19886
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Tooltip, { children: [
19887
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "inline-flex", children: button }) : button }),
19888
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19533
19889
  ] });
19534
19890
  }
19535
- function ToolbarSeparator() {
19536
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19891
+ function ToolbarSeparator2() {
19892
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19537
19893
  }
19538
19894
  function CardStyleMenu({
19539
19895
  value,
@@ -19541,7 +19897,7 @@ function CardStyleMenu({
19541
19897
  disabled = false
19542
19898
  }) {
19543
19899
  const current = PRICING_CARD_STYLES.find((s) => s.value === value) ?? PRICING_CARD_STYLES[0];
19544
- const trigger = /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19900
+ const trigger = /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19545
19901
  "button",
19546
19902
  {
19547
19903
  type: "button",
@@ -19552,28 +19908,28 @@ function CardStyleMenu({
19552
19908
  disabled && "cursor-not-allowed opacity-40 hover:bg-transparent"
19553
19909
  ),
19554
19910
  children: [
19555
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StyleSwatch, { style: current.value }),
19556
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "flex-1 text-left", children: current.label }),
19557
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ChevronDown, { size: 16, style: { pointerEvents: "none" } })
19911
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(StyleSwatch, { style: current.value }),
19912
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "flex-1 text-left", children: current.label }),
19913
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.ChevronDown, { size: 16, style: { pointerEvents: "none" } })
19558
19914
  ]
19559
19915
  }
19560
19916
  );
19561
19917
  if (disabled) {
19562
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19563
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
19564
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19565
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "inline-flex", children: trigger }) }),
19566
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
19918
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19919
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
19920
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Tooltip, { children: [
19921
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "inline-flex", children: trigger }) }),
19922
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
19567
19923
  ] })
19568
19924
  ] });
19569
19925
  }
19570
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19571
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
19572
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DropdownMenu, { modal: false, children: [
19573
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuTrigger, { asChild: true, children: trigger }),
19574
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[152px] min-w-[152px] p-1", children: PRICING_CARD_STYLES.map((style) => {
19926
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19927
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
19928
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(DropdownMenu, { modal: false, children: [
19929
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(DropdownMenuTrigger, { asChild: true, children: trigger }),
19930
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[152px] min-w-[152px] p-1", children: PRICING_CARD_STYLES.map((style) => {
19575
19931
  const selected = style.value === value;
19576
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19932
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19577
19933
  DropdownMenuItem,
19578
19934
  {
19579
19935
  role: "menuitemradio",
@@ -19581,9 +19937,9 @@ function CardStyleMenu({
19581
19937
  onSelect: () => onChange(style.value),
19582
19938
  className: cn("grid grid-cols-[18px_1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
19583
19939
  children: [
19584
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StyleSwatch, { style: style.value }),
19585
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: style.label }),
19586
- selected && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Check, { size: 16, className: "text-bridge-primary" })
19940
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(StyleSwatch, { style: style.value }),
19941
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { children: style.label }),
19942
+ selected && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Check, { size: 16, className: "text-bridge-primary" })
19587
19943
  ]
19588
19944
  },
19589
19945
  style.value
@@ -19613,7 +19969,7 @@ function PricingCardToolbar({
19613
19969
  const style = pricingCardStyle(entry.tree, index);
19614
19970
  const featuredTag = pricingCardFeaturedTag(entry.tree, index);
19615
19971
  const mobile = window.innerWidth <= 480;
19616
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19972
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19617
19973
  "div",
19618
19974
  {
19619
19975
  "data-ohw-bridge": "",
@@ -19625,7 +19981,7 @@ function PricingCardToolbar({
19625
19981
  ),
19626
19982
  style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
19627
19983
  children: [
19628
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19984
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19629
19985
  "div",
19630
19986
  {
19631
19987
  className: cn(
@@ -19634,8 +19990,8 @@ function PricingCardToolbar({
19634
19990
  mobile ? "flex-wrap justify-center" : "flex-nowrap"
19635
19991
  ),
19636
19992
  children: [
19637
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19638
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19993
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Tooltip, { children: [
19994
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19639
19995
  CustomToolbarButton,
19640
19996
  {
19641
19997
  type: "button",
@@ -19643,8 +19999,8 @@ function PricingCardToolbar({
19643
19999
  "aria-pressed": featuredTag,
19644
20000
  onClick: () => onSetFeaturedTag(!featuredTag),
19645
20001
  className: "group size-8",
19646
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19647
- import_lucide_react9.Star,
20002
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20003
+ import_lucide_react10.Star,
19648
20004
  {
19649
20005
  size: 16,
19650
20006
  fill: "currentColor",
@@ -19658,25 +20014,25 @@ function PricingCardToolbar({
19658
20014
  )
19659
20015
  }
19660
20016
  ) }),
19661
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
20017
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
19662
20018
  ] }),
19663
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19664
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
19665
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19666
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ButtonGlyph, {}) }),
19667
- /* @__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: [
19668
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ButtonGlyph, {}),
19669
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Minus, { size: 10, strokeWidth: 2.5, className: "absolute -right-1.5 -top-1.5 rounded-full bg-background text-bridge-primary" })
20019
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarSeparator2, {}),
20020
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
20021
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarSeparator2, {}),
20022
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ButtonGlyph, {}) }),
20023
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Remove button", onClick: onRemoveButton, disabled: buttonCount === 0, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("span", { className: "relative inline-flex", children: [
20024
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ButtonGlyph, {}),
20025
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Minus, { size: 10, strokeWidth: 2.5, className: "absolute -right-1.5 -top-1.5 rounded-full bg-background text-bridge-primary" })
19670
20026
  ] }) }),
19671
- /* @__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" }) }),
19672
- /* @__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" }) }),
19673
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19674
- /* @__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" } }) }),
19675
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Remove tier", onClick: onRemoveTier, disabled: collectionCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Minus, { size: 16, style: { pointerEvents: "none" } }) })
20027
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Add feature", onClick: onAddFeature, disabled: featureCount >= 10, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(FeatureGlyph, { mode: "add" }) }),
20028
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Remove feature", onClick: onRemoveFeature, disabled: featureCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(FeatureGlyph, { mode: "remove" }) }),
20029
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarSeparator2, {}),
20030
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Add tier", onClick: onAddTier, disabled: collectionCount >= 4, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Plus, { size: 16, style: { pointerEvents: "none" } }) }),
20031
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Remove tier", onClick: onRemoveTier, disabled: collectionCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Minus, { size: 16, style: { pointerEvents: "none" } }) })
19676
20032
  ]
19677
20033
  }
19678
20034
  ),
19679
- error && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
20035
+ error && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19680
20036
  ]
19681
20037
  }
19682
20038
  ) });
@@ -19710,7 +20066,7 @@ function AiPricingCardOverlay({
19710
20066
  const overEditable = Boolean(t?.closest?.("[data-ohw-editable], [data-ohw-role], a, button"));
19711
20067
  setHover((prev) => {
19712
20068
  const next = ref && !overEditable ? ref : null;
19713
- return sameRef(prev, next) ? prev : next;
20069
+ return sameRef2(prev, next) ? prev : next;
19714
20070
  });
19715
20071
  };
19716
20072
  const onLeave = () => setHover(null);
@@ -19725,10 +20081,10 @@ function AiPricingCardOverlay({
19725
20081
  const onClick = (e) => {
19726
20082
  const t = e.target;
19727
20083
  if (!(t instanceof Element)) return;
19728
- if (t.closest(CHROME_SELECTOR)) return;
20084
+ if (t.closest(CHROME_SELECTOR2)) return;
19729
20085
  const ref = cardRefFromTarget(t);
19730
- setActive((prev) => sameRef(prev, ref) ? prev : ref);
19731
- if (!ref || !sameRef(activeRef.current, ref)) setError(null);
20086
+ setActive((prev) => sameRef2(prev, ref) ? prev : ref);
20087
+ if (!ref || !sameRef2(activeRef.current, ref)) setError(null);
19732
20088
  };
19733
20089
  document.addEventListener("click", onClick, true);
19734
20090
  return () => document.removeEventListener("click", onClick, true);
@@ -19792,13 +20148,13 @@ function AiPricingCardOverlay({
19792
20148
  );
19793
20149
  const activeEntry = active ? findEntry(active.sectionId) : null;
19794
20150
  const activeCardRect = useLiveCardRect(active);
19795
- const activeGridRect = useLiveCardRect(active, true);
19796
- const hoverRect = useLiveCardRect(hover && !sameRef(hover, active) ? hover : null);
19797
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19798
- hoverRect && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19799
- activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19800
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
19801
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
20151
+ const activeGridRect = useLiveCardRect(active, true);
20152
+ const hoverRect = useLiveCardRect(hover && !sameRef2(hover, active) ? hover : null);
20153
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
20154
+ hoverRect && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
20155
+ activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
20156
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
20157
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19802
20158
  PricingCardToolbar,
19803
20159
  {
19804
20160
  entry: activeEntry,
@@ -19826,7 +20182,7 @@ function AiPricingCardOverlay({
19826
20182
  onRemoveFeature: () => runMutation(active.sectionId, (tree) => removePricingCardFeature(tree, active.index))
19827
20183
  }
19828
20184
  ) }),
19829
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
20185
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19830
20186
  "div",
19831
20187
  {
19832
20188
  "data-ohw-bridge": "",
@@ -19848,7 +20204,7 @@ function AiPricingCardOverlay({
19848
20204
 
19849
20205
  // src/ui/ai-section/AiVideoOverlay.tsx
19850
20206
  var import_react12 = require("react");
19851
- var import_lucide_react10 = require("lucide-react");
20207
+ var import_lucide_react11 = require("lucide-react");
19852
20208
 
19853
20209
  // src/lib/ai-video-tree.ts
19854
20210
  function resolveVideoBlock(tree, path) {
@@ -19919,10 +20275,10 @@ function extractYouTubeId(input) {
19919
20275
  }
19920
20276
 
19921
20277
  // src/ui/ai-section/AiVideoOverlay.tsx
19922
- var import_jsx_runtime22 = require("react/jsx-runtime");
20278
+ var import_jsx_runtime23 = require("react/jsx-runtime");
19923
20279
  var PRIMARY5 = "#0885FE";
19924
- var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY5})`;
19925
- var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
20280
+ var OHW_BLUE = `var(--ohw-primary, ${PRIMARY5})`;
20281
+ var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
19926
20282
  function findVideoBlockElement(sectionId, path) {
19927
20283
  const sectionEl = findByInstanceId(sectionId);
19928
20284
  if (!sectionEl) return null;
@@ -19982,12 +20338,12 @@ function requestValidateTree3(postToParent2, tree) {
19982
20338
  postToParent2({ type: "ow:ai-validate-tree-request", id, tree });
19983
20339
  });
19984
20340
  }
19985
- function Pill2({
20341
+ function Pill({
19986
20342
  label,
19987
20343
  onClick,
19988
20344
  disabled
19989
20345
  }) {
19990
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20346
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
19991
20347
  "button",
19992
20348
  {
19993
20349
  type: "button",
@@ -20003,7 +20359,7 @@ function Pill2({
20003
20359
  border: "none",
20004
20360
  cursor: disabled ? "default" : "pointer",
20005
20361
  background: "#ffffff",
20006
- color: OHW_BLUE_DARK2,
20362
+ color: OHW_BLUE_DARK,
20007
20363
  opacity: disabled ? 0.4 : 1,
20008
20364
  fontSize: 12,
20009
20365
  lineHeight: "16px",
@@ -20025,7 +20381,7 @@ function VideoUrlInput({ value, onCommit }) {
20025
20381
  setDraft(value ? watchUrl(value) : "");
20026
20382
  setInvalid(false);
20027
20383
  }, [value]);
20028
- const commit = () => {
20384
+ const commit2 = () => {
20029
20385
  const next = draft.trim();
20030
20386
  if (!next) {
20031
20387
  setInvalid(false);
@@ -20046,8 +20402,8 @@ function VideoUrlInput({ value, onCommit }) {
20046
20402
  onCommit(id);
20047
20403
  }
20048
20404
  };
20049
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
20050
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
20405
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
20406
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20051
20407
  "div",
20052
20408
  {
20053
20409
  style: {
@@ -20063,12 +20419,12 @@ function VideoUrlInput({ value, onCommit }) {
20063
20419
  boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
20064
20420
  },
20065
20421
  children: [
20066
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20422
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20067
20423
  "span",
20068
20424
  {
20069
20425
  style: {
20070
20426
  flex: "0 0 auto",
20071
- color: OHW_BLUE_DARK2,
20427
+ color: OHW_BLUE_DARK,
20072
20428
  fontSize: 10,
20073
20429
  lineHeight: "16px",
20074
20430
  fontWeight: 700,
@@ -20078,14 +20434,14 @@ function VideoUrlInput({ value, onCommit }) {
20078
20434
  children: "Video"
20079
20435
  }
20080
20436
  ),
20081
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20437
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20082
20438
  "input",
20083
20439
  {
20084
20440
  type: "text",
20085
20441
  inputMode: "url",
20086
20442
  value: draft,
20087
20443
  onChange: (e) => handleChange(e.target.value),
20088
- onBlur: commit,
20444
+ onBlur: commit2,
20089
20445
  onKeyDown: (e) => {
20090
20446
  if (e.key === "Enter") e.currentTarget.blur();
20091
20447
  if (e.key === "Escape") {
@@ -20115,7 +20471,7 @@ function VideoUrlInput({ value, onCommit }) {
20115
20471
  ]
20116
20472
  }
20117
20473
  ),
20118
- invalid && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, textAlign: "center" }, children: "That doesn't look like a YouTube link \u2014 try pasting the video URL again." })
20474
+ invalid && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, textAlign: "center" }, children: "That doesn't look like a YouTube link \u2014 try pasting the video URL again." })
20119
20475
  ] });
20120
20476
  }
20121
20477
  function VideoToolbar({
@@ -20125,7 +20481,7 @@ function VideoToolbar({
20125
20481
  onSetVideoId,
20126
20482
  onRemove
20127
20483
  }) {
20128
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
20484
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20129
20485
  "div",
20130
20486
  {
20131
20487
  "data-ohw-bridge": "",
@@ -20137,7 +20493,7 @@ function VideoToolbar({
20137
20493
  gap: 6,
20138
20494
  alignItems: "center",
20139
20495
  width: 280,
20140
- background: OHW_BLUE2,
20496
+ background: OHW_BLUE,
20141
20497
  padding: 8,
20142
20498
  borderRadius: 12,
20143
20499
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -20145,9 +20501,9 @@ function VideoToolbar({
20145
20501
  opacity: pending ? 0.6 : 1
20146
20502
  },
20147
20503
  children: [
20148
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
20149
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Pill2, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
20150
- error && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20504
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
20505
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Pill, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
20506
+ error && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20151
20507
  ]
20152
20508
  }
20153
20509
  );
@@ -20267,8 +20623,8 @@ function AiVideoOverlay({
20267
20623
  const activeBlockRect = useLiveBlockRect(active);
20268
20624
  const TOOLBAR_APPROX_H = 90;
20269
20625
  const toolbarState = activeBlockRect && activeBlockRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
20270
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
20271
- hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20626
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
20627
+ hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20272
20628
  "button",
20273
20629
  {
20274
20630
  type: "button",
@@ -20296,15 +20652,15 @@ function AiVideoOverlay({
20296
20652
  fontSize: 14,
20297
20653
  fontWeight: 700
20298
20654
  },
20299
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Star, { size: 14, style: { pointerEvents: "none" } })
20655
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.Star, { size: 14, style: { pointerEvents: "none" } })
20300
20656
  }
20301
20657
  ),
20302
- activeBlockRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20658
+ activeBlockRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20303
20659
  ItemInteractionLayer,
20304
20660
  {
20305
20661
  rect: activeBlockRect,
20306
20662
  state: toolbarState,
20307
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20663
+ toolbar: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20308
20664
  VideoToolbar,
20309
20665
  {
20310
20666
  videoId: videoIdAt(activeEntry.tree, active.path),
@@ -20328,7 +20684,7 @@ function AiVideoOverlay({
20328
20684
 
20329
20685
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
20330
20686
  var import_react13 = require("react");
20331
- var import_lucide_react11 = require("lucide-react");
20687
+ var import_lucide_react12 = require("lucide-react");
20332
20688
 
20333
20689
  // src/lib/ai-carousel-tree.ts
20334
20690
  function isCarouselBlock(node) {
@@ -20379,10 +20735,10 @@ function setCarouselAutoplay(tree, on) {
20379
20735
  }
20380
20736
 
20381
20737
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
20382
- var import_jsx_runtime23 = require("react/jsx-runtime");
20738
+ var import_jsx_runtime24 = require("react/jsx-runtime");
20383
20739
  var PRIMARY6 = "#0885FE";
20384
- var OHW_BLUE3 = `var(--ohw-primary, ${PRIMARY6})`;
20385
- var OHW_BLUE_DARK3 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
20740
+ var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY6})`;
20741
+ var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
20386
20742
  function findCarouselItemElement(sectionId, index) {
20387
20743
  const sectionEl = findByInstanceId(sectionId);
20388
20744
  if (!sectionEl) return null;
@@ -20461,8 +20817,8 @@ function requestAssetPick2(postToParent2) {
20461
20817
  postToParent2({ type: "ow:asset-pick", id });
20462
20818
  });
20463
20819
  }
20464
- function Pill3({ label, onClick, disabled }) {
20465
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20820
+ function Pill2({ label, onClick, disabled }) {
20821
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20466
20822
  "button",
20467
20823
  {
20468
20824
  type: "button",
@@ -20478,7 +20834,7 @@ function Pill3({ label, onClick, disabled }) {
20478
20834
  border: "none",
20479
20835
  cursor: disabled ? "default" : "pointer",
20480
20836
  background: "#ffffff",
20481
- color: OHW_BLUE_DARK3,
20837
+ color: OHW_BLUE_DARK2,
20482
20838
  opacity: disabled ? 0.4 : 1,
20483
20839
  fontSize: 12,
20484
20840
  lineHeight: "16px",
@@ -20496,7 +20852,7 @@ function TogglePill({
20496
20852
  onToggle,
20497
20853
  disabled
20498
20854
  }) {
20499
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20855
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
20500
20856
  "button",
20501
20857
  {
20502
20858
  type: "button",
@@ -20515,7 +20871,7 @@ function TogglePill({
20515
20871
  border: "none",
20516
20872
  cursor: disabled ? "default" : "pointer",
20517
20873
  background: "#ffffff",
20518
- color: OHW_BLUE_DARK3,
20874
+ color: OHW_BLUE_DARK2,
20519
20875
  opacity: disabled ? 0.4 : 1,
20520
20876
  fontSize: 12,
20521
20877
  lineHeight: "16px",
@@ -20525,7 +20881,7 @@ function TogglePill({
20525
20881
  },
20526
20882
  children: [
20527
20883
  label,
20528
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20884
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20529
20885
  "span",
20530
20886
  {
20531
20887
  "aria-hidden": true,
@@ -20538,10 +20894,10 @@ function TogglePill({
20538
20894
  // Off is a grey track, not a washed-out blue: the pill's own white background already
20539
20895
  // carries the brand colour in the text, and a pale blue track next to it read as
20540
20896
  // "disabled control" rather than "switch that is off".
20541
- background: on ? OHW_BLUE_DARK3 : "#CBD5E1",
20897
+ background: on ? OHW_BLUE_DARK2 : "#CBD5E1",
20542
20898
  transition: "background 120ms ease"
20543
20899
  },
20544
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20900
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20545
20901
  "span",
20546
20902
  {
20547
20903
  style: {
@@ -20570,7 +20926,7 @@ function CarouselSlideToolbar({
20570
20926
  onChangeImage,
20571
20927
  onToggleAutoplay
20572
20928
  }) {
20573
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20929
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
20574
20930
  "div",
20575
20931
  {
20576
20932
  "data-ohw-bridge": "",
@@ -20581,7 +20937,7 @@ function CarouselSlideToolbar({
20581
20937
  flexDirection: "column",
20582
20938
  gap: 6,
20583
20939
  alignItems: "center",
20584
- background: OHW_BLUE3,
20940
+ background: OHW_BLUE2,
20585
20941
  padding: 8,
20586
20942
  borderRadius: 12,
20587
20943
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -20593,11 +20949,11 @@ function CarouselSlideToolbar({
20593
20949
  pointerEvents: pending ? "none" : "auto"
20594
20950
  },
20595
20951
  children: [
20596
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20597
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Pill3, { label: "Change Image", onClick: onChangeImage }),
20598
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
20952
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20953
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(Pill2, { label: "Change Image", onClick: onChangeImage }),
20954
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
20599
20955
  ] }),
20600
- error && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20956
+ error && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20601
20957
  ]
20602
20958
  }
20603
20959
  );
@@ -20737,8 +21093,8 @@ function AiFadeCarouselOverlay({
20737
21093
  const activeItemRect = useLiveItemRect2(active);
20738
21094
  const TOOLBAR_APPROX_H = 90;
20739
21095
  const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
20740
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
20741
- hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
21096
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
21097
+ hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20742
21098
  "button",
20743
21099
  {
20744
21100
  type: "button",
@@ -20767,10 +21123,10 @@ function AiFadeCarouselOverlay({
20767
21123
  fontSize: 14,
20768
21124
  fontWeight: 700
20769
21125
  },
20770
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.Star, { size: 14, style: { pointerEvents: "none" } })
21126
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react12.Star, { size: 14, style: { pointerEvents: "none" } })
20771
21127
  }
20772
21128
  ),
20773
- swapping && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
21129
+ swapping && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20774
21130
  "div",
20775
21131
  {
20776
21132
  ref: skeletonRef,
@@ -20788,15 +21144,15 @@ function AiFadeCarouselOverlay({
20788
21144
  zIndex: 2147483646,
20789
21145
  pointerEvents: "none"
20790
21146
  },
20791
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("style", { children: MEDIA_SKELETON_CSS })
21147
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("style", { children: MEDIA_SKELETON_CSS })
20792
21148
  }
20793
21149
  ),
20794
- activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
21150
+ activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20795
21151
  ItemInteractionLayer,
20796
21152
  {
20797
21153
  rect: activeItemRect,
20798
21154
  state: toolbarState,
20799
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
21155
+ toolbar: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20800
21156
  CarouselSlideToolbar,
20801
21157
  {
20802
21158
  pending,
@@ -21014,113 +21370,6 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
21014
21370
  // src/ui/link-modal/LinkPopover.tsx
21015
21371
  var import_react17 = require("react");
21016
21372
 
21017
- // src/ui/dialog.tsx
21018
- var React9 = __toESM(require("react"), 1);
21019
- var import_radix_ui6 = require("radix-ui");
21020
- var import_lucide_react12 = require("lucide-react");
21021
- var import_jsx_runtime24 = require("react/jsx-runtime");
21022
- function Dialog2({
21023
- ...props
21024
- }) {
21025
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_radix_ui6.Dialog.Root, { "data-slot": "dialog", ...props });
21026
- }
21027
- function DialogPortal({
21028
- ...props
21029
- }) {
21030
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_radix_ui6.Dialog.Portal, { ...props });
21031
- }
21032
- function DialogOverlay({
21033
- className,
21034
- ...props
21035
- }) {
21036
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21037
- import_radix_ui6.Dialog.Overlay,
21038
- {
21039
- "data-slot": "dialog-overlay",
21040
- "data-ohw-link-modal-root": "",
21041
- className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
21042
- ...props
21043
- }
21044
- );
21045
- }
21046
- var DialogContent = React9.forwardRef(
21047
- ({ className, children, showCloseButton = true, container, ...props }, ref) => {
21048
- const positionMode = container ? "absolute" : "fixed";
21049
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(DialogPortal, { container: container ?? void 0, children: [
21050
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
21051
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
21052
- import_radix_ui6.Dialog.Content,
21053
- {
21054
- ref,
21055
- "data-slot": "dialog-content",
21056
- className: cn(
21057
- positionMode,
21058
- "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",
21059
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
21060
- container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
21061
- className
21062
- ),
21063
- onMouseDown: (e) => e.stopPropagation(),
21064
- ...props,
21065
- children: [
21066
- children,
21067
- showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21068
- import_radix_ui6.Dialog.Close,
21069
- {
21070
- type: "button",
21071
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
21072
- "aria-label": "Close",
21073
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react12.X, { size: 16, "aria-hidden": true })
21074
- }
21075
- ) : null
21076
- ]
21077
- }
21078
- )
21079
- ] });
21080
- }
21081
- );
21082
- DialogContent.displayName = import_radix_ui6.Dialog.Content.displayName;
21083
- function DialogHeader({
21084
- className,
21085
- ...props
21086
- }) {
21087
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
21088
- }
21089
- function DialogFooter({
21090
- className,
21091
- ...props
21092
- }) {
21093
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21094
- "div",
21095
- {
21096
- className: cn("flex items-center justify-end gap-2", className),
21097
- ...props
21098
- }
21099
- );
21100
- }
21101
- var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21102
- import_radix_ui6.Dialog.Title,
21103
- {
21104
- ref,
21105
- className: cn(
21106
- "text-lg font-semibold leading-none tracking-tight text-card-foreground",
21107
- className
21108
- ),
21109
- ...props
21110
- }
21111
- ));
21112
- DialogTitle.displayName = import_radix_ui6.Dialog.Title.displayName;
21113
- var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21114
- import_radix_ui6.Dialog.Description,
21115
- {
21116
- ref,
21117
- className: cn("text-sm text-muted-foreground", className),
21118
- ...props
21119
- }
21120
- ));
21121
- DialogDescription.displayName = import_radix_ui6.Dialog.Description.displayName;
21122
- var DialogClose = import_radix_ui6.Dialog.Close;
21123
-
21124
21373
  // src/ui/link-modal/LinkEditorPanel.tsx
21125
21374
  var import_lucide_react16 = require("lucide-react");
21126
21375
 
@@ -22224,6 +22473,7 @@ function shouldUseDevFixtures() {
22224
22473
  function getLogoElement(el) {
22225
22474
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
22226
22475
  if (marked) return marked;
22476
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
22227
22477
  const root = el.closest("nav, [data-ohw-nav-root], footer");
22228
22478
  if (!root) return null;
22229
22479
  const anchor = el.closest("a");
@@ -23918,6 +24168,102 @@ function deleteSelectedNavFooterItem(deps) {
23918
24168
  return false;
23919
24169
  }
23920
24170
 
24171
+ // src/lib/nav-page-sync.ts
24172
+ function labelKeyFor(hrefKey) {
24173
+ return hrefKey.replace(/-href$/, "-label");
24174
+ }
24175
+ function isPageLinkKey(key) {
24176
+ return isNavbarHrefKey(key) || isFooterHrefKey(key);
24177
+ }
24178
+ function currentHref(key, anchor, content) {
24179
+ const stored = content[key];
24180
+ if (typeof stored === "string" && stored.length > 0) return stored;
24181
+ return anchor.getAttribute("href") ?? "";
24182
+ }
24183
+ function normalizePath2(value) {
24184
+ if (value.length > 1 && value.endsWith("/")) return value.slice(0, -1);
24185
+ return value;
24186
+ }
24187
+ function pointsAt(href, path) {
24188
+ const normalized = normalizePath2(href);
24189
+ const target = normalizePath2(path);
24190
+ if (normalized === target) return true;
24191
+ return (normalized.startsWith(`${target}#`) || normalized.startsWith(`${target}?`)) && target !== "/";
24192
+ }
24193
+ function retargetHref(href, oldPath, newPath) {
24194
+ const normalized = normalizePath2(href);
24195
+ const target = normalizePath2(oldPath);
24196
+ if (normalized === target) return newPath;
24197
+ return `${newPath}${normalized.slice(target.length)}`;
24198
+ }
24199
+ function setLabelText(key, text) {
24200
+ document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`).forEach((el) => {
24201
+ el.textContent = text;
24202
+ });
24203
+ }
24204
+ function commit(delta, deps) {
24205
+ if (Object.keys(delta).length === 0) return;
24206
+ deps.setEditContent({ ...deps.getEditContent(), ...delta });
24207
+ deps.postToParent({
24208
+ type: "ow:change",
24209
+ nodes: Object.entries(delta).map(([key, text]) => ({ key, text }))
24210
+ });
24211
+ }
24212
+ function navLinksTo(path, content) {
24213
+ return Array.from(document.querySelectorAll("[data-ohw-href-key]")).filter((anchor) => {
24214
+ const key = anchor.getAttribute("data-ohw-href-key");
24215
+ return isNavbarHrefKey(key) && pointsAt(currentHref(key, anchor, content), path);
24216
+ }).map((anchor) => anchor.getAttribute("data-ohw-href-key"));
24217
+ }
24218
+ function addNavLinkForPage(page, deps) {
24219
+ const content = deps.getEditContent();
24220
+ if (navLinksTo(page.path, content).length > 0) return false;
24221
+ const roots = listNavbarRootItems();
24222
+ const afterAnchor = roots[roots.length - 1] ?? null;
24223
+ let inserted;
24224
+ try {
24225
+ inserted = insertNavbarItem(page.path, page.title, afterAnchor);
24226
+ } catch {
24227
+ return false;
24228
+ }
24229
+ const { hrefKey, labelKey, index } = inserted;
24230
+ deps.applyLinkByKey(hrefKey, page.path);
24231
+ setLabelText(labelKey, page.title);
24232
+ commit(
24233
+ {
24234
+ [hrefKey]: page.path,
24235
+ [labelKey]: page.title,
24236
+ [NAV_COUNT_KEY]: String(index + 1),
24237
+ [NAV_ORDER_KEY]: getNavbarOrderJson()
24238
+ },
24239
+ deps
24240
+ );
24241
+ return true;
24242
+ }
24243
+ function retargetPageLinks(change, deps) {
24244
+ const { oldPath, newPath, oldTitle, newTitle } = change;
24245
+ if (!oldPath || !newPath || normalizePath2(oldPath) === normalizePath2(newPath)) return 0;
24246
+ const content = deps.getEditContent();
24247
+ const delta = {};
24248
+ for (const anchor of Array.from(document.querySelectorAll("[data-ohw-href-key]"))) {
24249
+ const hrefKey = anchor.getAttribute("data-ohw-href-key");
24250
+ if (!isPageLinkKey(hrefKey)) continue;
24251
+ const href = currentHref(hrefKey, anchor, content);
24252
+ if (!pointsAt(href, oldPath)) continue;
24253
+ const nextHref = retargetHref(href, oldPath, newPath);
24254
+ deps.applyLinkByKey(hrefKey, nextHref);
24255
+ delta[hrefKey] = nextHref;
24256
+ if (!oldTitle || !newTitle) continue;
24257
+ const labelKey = labelKeyFor(hrefKey);
24258
+ const label = content[labelKey] ?? anchor.querySelector('[data-ohw-editable="text"]')?.textContent ?? "";
24259
+ if (label.trim() !== oldTitle) continue;
24260
+ setLabelText(labelKey, newTitle);
24261
+ delta[labelKey] = newTitle;
24262
+ }
24263
+ commit(delta, deps);
24264
+ return Object.keys(delta).filter((key) => key.endsWith("-href")).length;
24265
+ }
24266
+
23921
24267
  // src/ui/navbar-container-chrome.tsx
23922
24268
  var import_lucide_react21 = require("lucide-react");
23923
24269
  var import_jsx_runtime37 = require("react/jsx-runtime");
@@ -24805,7 +25151,7 @@ function getNavigationSelectionParent(el) {
24805
25151
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
24806
25152
  return resolveFooterLinksContainerFrom(el);
24807
25153
  }
24808
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
25154
+ if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isFooterLinksContainer(el) || isInferredFooterGroup2(el)) {
24809
25155
  return getNavigationRoot(el);
24810
25156
  }
24811
25157
  return null;
@@ -25027,7 +25373,6 @@ var ICONS = {
25027
25373
  insertUnorderedList: '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>',
25028
25374
  insertOrderedList: '<line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/>'
25029
25375
  };
25030
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
25031
25376
  var SELECTION_CHROME_GAP2 = 4;
25032
25377
  var TOOLBAR_STROKE_GAP2 = 4;
25033
25378
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -25125,7 +25470,7 @@ function applyVisibleViewport(el, parentScroll) {
25125
25470
  el.style.top = `${top}px`;
25126
25471
  el.style.height = `${height}px`;
25127
25472
  }
25128
- var TOOLBAR_EDGE_MARGIN3 = 4;
25473
+ var TOOLBAR_EDGE_MARGIN4 = 4;
25129
25474
  function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
25130
25475
  const isAbove = transform.includes("translateY(-100%)");
25131
25476
  let visualTop = isAbove ? top - approxH : top;
@@ -25152,8 +25497,8 @@ function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
25152
25497
  function clampToolbarLeft(centerX, toolbarWidth) {
25153
25498
  const half = toolbarWidth / 2;
25154
25499
  return Math.max(
25155
- TOOLBAR_EDGE_MARGIN3 + half,
25156
- Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half)
25500
+ TOOLBAR_EDGE_MARGIN4 + half,
25501
+ Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN4 - half)
25157
25502
  );
25158
25503
  }
25159
25504
  function calcToolbarPos(rect, parentScroll, toolbarW = 306) {
@@ -26033,21 +26378,6 @@ function OhhwellsBridge() {
26033
26378
  const [isItemDragging, setIsItemDragging] = (0, import_react21.useState)(false);
26034
26379
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react21.useState)(false);
26035
26380
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
26036
- const [floatingPanel, setFloatingPanel] = (0, import_react21.useState)(null);
26037
- const floatingPanelOpenRef = (0, import_react21.useRef)(false);
26038
- floatingPanelOpenRef.current = floatingPanel !== null;
26039
- const floatingPanelRef = (0, import_react21.useRef)(null);
26040
- floatingPanelRef.current = floatingPanel;
26041
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react21.useState)(null);
26042
- const buttonCollectionDragRef = (0, import_react21.useRef)(null);
26043
- const buttonDropInsertIndexRef = (0, import_react21.useRef)(null);
26044
- const [buttonDropSlots, setButtonDropSlots] = (0, import_react21.useState)([]);
26045
- const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react21.useState)(null);
26046
- const buttonPointerDragRef = (0, import_react21.useRef)(null);
26047
- const [buttonStyleTick, setButtonStyleTick] = (0, import_react21.useState)(0);
26048
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react21.useState)(null);
26049
- const [editorViewport, setEditorViewport] = (0, import_react21.useState)("desktop");
26050
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react21.useState)(null);
26051
26381
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react21.useState)(null);
26052
26382
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react21.useState)(null);
26053
26383
  const footerDragRef = (0, import_react21.useRef)(null);
@@ -26073,6 +26403,21 @@ function OhhwellsBridge() {
26073
26403
  const stylesRef = (0, import_react21.useRef)("");
26074
26404
  const appliedDrawerRef = (0, import_react21.useRef)(null);
26075
26405
  const pendingDeleteUndoRef = (0, import_react21.useRef)(null);
26406
+ const [floatingPanel, setFloatingPanel] = (0, import_react21.useState)(null);
26407
+ const floatingPanelOpenRef = (0, import_react21.useRef)(false);
26408
+ const floatingPanelRef = (0, import_react21.useRef)(null);
26409
+ floatingPanelRef.current = floatingPanel;
26410
+ const setFloatingPanelRef = (0, import_react21.useRef)(setFloatingPanel);
26411
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react21.useState)(null);
26412
+ const buttonCollectionDragRef = (0, import_react21.useRef)(null);
26413
+ const buttonDropInsertIndexRef = (0, import_react21.useRef)(null);
26414
+ const [buttonDropSlots, setButtonDropSlots] = (0, import_react21.useState)([]);
26415
+ const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react21.useState)(null);
26416
+ const buttonPointerDragRef = (0, import_react21.useRef)(null);
26417
+ const [buttonStyleTick, setButtonStyleTick] = (0, import_react21.useState)(0);
26418
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react21.useState)(null);
26419
+ const [editorViewport, setEditorViewport] = (0, import_react21.useState)("desktop");
26420
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react21.useState)(null);
26076
26421
  const [sitePages, setSitePages] = (0, import_react21.useState)([]);
26077
26422
  const collectionItemsRef = (0, import_react21.useRef)(/* @__PURE__ */ new Map());
26078
26423
  const [sectionsByPath, setSectionsByPath] = (0, import_react21.useState)({});
@@ -26082,7 +26427,18 @@ function OhhwellsBridge() {
26082
26427
  const linkPopoverOpenRef = (0, import_react21.useRef)(false);
26083
26428
  const linkPopoverGraceUntilRef = (0, import_react21.useRef)(0);
26084
26429
  setLinkPopoverRef.current = setLinkPopover;
26430
+ setFloatingPanelRef.current = setFloatingPanel;
26085
26431
  linkPopoverSessionRef.current = linkPopover;
26432
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
26433
+ (0, import_react21.useEffect)(() => {
26434
+ const syncViewport = () => {
26435
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
26436
+ setEditorViewport((prev) => prev === next ? prev : next);
26437
+ };
26438
+ syncViewport();
26439
+ window.addEventListener("resize", syncViewport);
26440
+ return () => window.removeEventListener("resize", syncViewport);
26441
+ }, []);
26086
26442
  (0, import_react21.useEffect)(() => {
26087
26443
  setSectionOrderPersistHandler((entries) => {
26088
26444
  const currentPath = window.location.pathname;
@@ -26231,6 +26587,43 @@ function OhhwellsBridge() {
26231
26587
  if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
26232
26588
  return () => window.removeEventListener("message", onSitePages);
26233
26589
  }, [isEditMode, postToParent2]);
26590
+ (0, import_react21.useEffect)(() => {
26591
+ if (!isEditMode) return;
26592
+ const deps = {
26593
+ getEditContent: () => editContentRef.current,
26594
+ setEditContent: (next) => {
26595
+ editContentRef.current = next;
26596
+ },
26597
+ applyLinkByKey,
26598
+ postToParent: postToParent2
26599
+ };
26600
+ const onPageMessage = (e) => {
26601
+ if (e.data?.type === "ow:page-created") {
26602
+ const { path, title } = e.data;
26603
+ if (typeof path !== "string" || !path) return;
26604
+ if (addNavLinkForPage({ path, title: typeof title === "string" && title ? title : path }, deps)) {
26605
+ enforceLinkHrefs();
26606
+ }
26607
+ return;
26608
+ }
26609
+ if (e.data?.type === "ow:page-renamed") {
26610
+ const { oldPath, newPath, oldTitle, newTitle } = e.data;
26611
+ if (typeof oldPath !== "string" || typeof newPath !== "string") return;
26612
+ const moved = retargetPageLinks(
26613
+ {
26614
+ oldPath,
26615
+ newPath,
26616
+ oldTitle: typeof oldTitle === "string" ? oldTitle : void 0,
26617
+ newTitle: typeof newTitle === "string" ? newTitle : void 0
26618
+ },
26619
+ deps
26620
+ );
26621
+ if (moved > 0) enforceLinkHrefs();
26622
+ }
26623
+ };
26624
+ window.addEventListener("message", onPageMessage);
26625
+ return () => window.removeEventListener("message", onPageMessage);
26626
+ }, [isEditMode, postToParent2]);
26234
26627
  (0, import_react21.useEffect)(() => {
26235
26628
  if (!isEditMode || shouldUseDevFixtures()) return;
26236
26629
  void loadAllSectionsManifest().then((manifest) => {
@@ -27779,6 +28172,7 @@ function OhhwellsBridge() {
27779
28172
  applyAiFooterFromContent(content);
27780
28173
  if (typeof content[STYLE_STORE_KEY] === "string") {
27781
28174
  stylesRef.current = content[STYLE_STORE_KEY];
28175
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
27782
28176
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
27783
28177
  }
27784
28178
  applyBrandChrome(content);
@@ -27789,12 +28183,12 @@ function OhhwellsBridge() {
27789
28183
  if (key === "__ohw_sections") continue;
27790
28184
  if (key === AI_SECTIONS_KEY) continue;
27791
28185
  if (key === AI_FOOTER_KEY) continue;
28186
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
28187
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
27792
28188
  if (key === BRAND_KIT_KEY) continue;
27793
28189
  if (key === STYLE_STORE_KEY) continue;
27794
28190
  if (BRAND_CHROME_KEYS.has(key)) continue;
27795
- if (key === LOGO_PLACEHOLDER_KEY) continue;
27796
28191
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
27797
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
27798
28192
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
27799
28193
  continue;
27800
28194
  }
@@ -28025,17 +28419,18 @@ function OhhwellsBridge() {
28025
28419
  }
28026
28420
  applyAiFooterFromContent(content);
28027
28421
  if (typeof content[STYLE_STORE_KEY] === "string") {
28422
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
28028
28423
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
28029
28424
  }
28030
28425
  for (const [key, val] of Object.entries(content)) {
28031
28426
  if (key === "__ohw_sections") continue;
28032
28427
  if (key === AI_SECTIONS_KEY) continue;
28033
28428
  if (key === AI_FOOTER_KEY) continue;
28429
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
28430
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
28034
28431
  if (key === BRAND_KIT_KEY) continue;
28035
28432
  if (key === STYLE_STORE_KEY) continue;
28036
28433
  if (BRAND_CHROME_KEYS.has(key)) continue;
28037
- if (key === LOGO_PLACEHOLDER_KEY) continue;
28038
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
28039
28434
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
28040
28435
  continue;
28041
28436
  }
@@ -28302,26 +28697,11 @@ function OhhwellsBridge() {
28302
28697
  const t2 = setTimeout(measure, 500);
28303
28698
  const ro = new ResizeObserver(schedule);
28304
28699
  ro.observe(document.body);
28305
- let lastWidth = window.innerWidth;
28306
- let resizeTimers = [];
28307
- const clearResizeTimers = () => {
28308
- resizeTimers.forEach(clearTimeout);
28309
- resizeTimers = [];
28310
- };
28311
- const handleResize = () => {
28312
- if (window.innerWidth === lastWidth) return;
28313
- lastWidth = window.innerWidth;
28314
- clearResizeTimers();
28315
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
28316
- };
28317
- window.addEventListener("resize", handleResize);
28318
28700
  return () => {
28319
28701
  clearTimeout(t1);
28320
28702
  clearTimeout(t2);
28321
28703
  if (raf != null) cancelAnimationFrame(raf);
28322
28704
  ro.disconnect();
28323
- clearResizeTimers();
28324
- window.removeEventListener("resize", handleResize);
28325
28705
  };
28326
28706
  }, [pathname, isEditMode, postToParent2]);
28327
28707
  (0, import_react21.useEffect)(() => {
@@ -28618,6 +28998,13 @@ function OhhwellsBridge() {
28618
28998
  if (target.closest("[data-ohw-state-toggle]")) return;
28619
28999
  if (target.closest("[data-ohw-max-badge]")) return;
28620
29000
  if (isInsideLinkEditor(target)) return;
29001
+ if (isInsideFloatingPanel(target)) return;
29002
+ if (target.closest("[data-ohw-form-toolbar]")) return;
29003
+ if (target.closest(
29004
+ '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
29005
+ )) {
29006
+ return;
29007
+ }
28621
29008
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
28622
29009
  clearMediaSelectionRef.current();
28623
29010
  }
@@ -28641,8 +29028,6 @@ function OhhwellsBridge() {
28641
29028
  return;
28642
29029
  }
28643
29030
  }
28644
- if (isInsideFloatingPanel(target)) return;
28645
- if (target.closest("[data-ohw-form-toolbar]")) return;
28646
29031
  const collectionItem = matchCollectionItemClick(target, collectionItemsRef.current);
28647
29032
  if (collectionItem) {
28648
29033
  e.preventDefault();
@@ -28654,11 +29039,6 @@ function OhhwellsBridge() {
28654
29039
  });
28655
29040
  return;
28656
29041
  }
28657
- if (target.closest(
28658
- '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
28659
- )) {
28660
- return;
28661
- }
28662
29042
  {
28663
29043
  const formEl = getFormElement(target);
28664
29044
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -28812,9 +29192,13 @@ function OhhwellsBridge() {
28812
29192
  if (isIconEditable(editable) && !getSocialItem(editable)) {
28813
29193
  e.preventDefault();
28814
29194
  e.stopPropagation();
28815
- selectIconElementRef.current(editable);
28816
29195
  const aiIcon = editable.hasAttribute("data-ai-icon");
28817
29196
  const featureIcon = aiIcon && /\.features\.\d+\.icon$/.test(editable.dataset.ohwKey ?? "");
29197
+ if (aiIcon) {
29198
+ aiSectionApiRef.current?.selectFromElement(editable);
29199
+ } else {
29200
+ selectIconElementRef.current(editable);
29201
+ }
28818
29202
  postToParentRef.current({
28819
29203
  type: "ow:icon-pick",
28820
29204
  key: editable.dataset.ohwKey ?? "",
@@ -29042,6 +29426,9 @@ function OhhwellsBridge() {
29042
29426
  setHoveredItemRect(null);
29043
29427
  hoveredNavContainerRef.current = null;
29044
29428
  setHoveredNavContainerRect(null);
29429
+ siblingHintElRef.current = null;
29430
+ setSiblingHintRect(null);
29431
+ setSiblingHintRects([]);
29045
29432
  return;
29046
29433
  }
29047
29434
  {
@@ -29162,7 +29549,6 @@ function OhhwellsBridge() {
29162
29549
  hoveredNavContainerRef.current = null;
29163
29550
  setHoveredNavContainerRect(null);
29164
29551
  hoveredItemElRef.current = editable;
29165
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
29166
29552
  }
29167
29553
  }
29168
29554
  }
@@ -29872,7 +30258,11 @@ function OhhwellsBridge() {
29872
30258
  };
29873
30259
  const handleMouseMove = (e) => {
29874
30260
  const { clientX, clientY } = e;
29875
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
30261
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
30262
+ dismissImageHover();
30263
+ clearImageHover();
30264
+ return;
30265
+ }
29876
30266
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) {
29877
30267
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
29878
30268
  formHoverElRef.current = null;
@@ -29901,7 +30291,12 @@ function OhhwellsBridge() {
29901
30291
  setHoveredItemRect(null);
29902
30292
  hoveredNavContainerRef.current = null;
29903
30293
  setHoveredNavContainerRect(null);
30294
+ siblingHintElRef.current = null;
30295
+ setSiblingHintRect(null);
30296
+ setSiblingHintRects([]);
29904
30297
  dismissImageHover();
30298
+ clearImageHover();
30299
+ setSectionGap(null);
29905
30300
  return;
29906
30301
  }
29907
30302
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -29913,7 +30308,11 @@ function OhhwellsBridge() {
29913
30308
  if (e.data?.type !== "ow:pointer-sync") return;
29914
30309
  const { clientX, clientY } = e.data;
29915
30310
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
29916
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
30311
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
30312
+ dismissImageHover();
30313
+ clearImageHover();
30314
+ return;
30315
+ }
29917
30316
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) return;
29918
30317
  if (probeSocialsRowAt(clientX, clientY)) return;
29919
30318
  probeSectionGapAt(clientX, clientY);
@@ -30293,6 +30692,7 @@ function OhhwellsBridge() {
30293
30692
  applyAiFooterFromContent(content);
30294
30693
  if (typeof content[STYLE_STORE_KEY] === "string") {
30295
30694
  stylesRef.current = content[STYLE_STORE_KEY];
30695
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
30296
30696
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
30297
30697
  }
30298
30698
  applyBrandChrome(content);
@@ -30307,12 +30707,12 @@ function OhhwellsBridge() {
30307
30707
  }
30308
30708
  if (key === AI_SECTIONS_KEY) continue;
30309
30709
  if (key === AI_FOOTER_KEY) continue;
30710
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
30711
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
30310
30712
  if (key === BRAND_KIT_KEY) continue;
30311
30713
  if (key === STYLE_STORE_KEY) continue;
30312
30714
  if (BRAND_CHROME_KEYS.has(key)) continue;
30313
- if (key === LOGO_PLACEHOLDER_KEY) continue;
30314
30715
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
30315
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
30316
30716
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
30317
30717
  continue;
30318
30718
  }
@@ -30948,6 +31348,12 @@ function OhhwellsBridge() {
30948
31348
  closeLinkPopoverRef.current();
30949
31349
  return;
30950
31350
  }
31351
+ if (floatingPanelOpenRef.current) {
31352
+ setFloatingPanelRef.current(null);
31353
+ deselectRef.current();
31354
+ deactivateRef.current();
31355
+ return;
31356
+ }
30951
31357
  deselectRef.current();
30952
31358
  deactivateRef.current();
30953
31359
  clearMediaSelectionRef.current();
@@ -31328,13 +31734,15 @@ function OhhwellsBridge() {
31328
31734
  if (e.data?.type !== "ow:collect-section") return;
31329
31735
  const sectionId = e.data.sectionId;
31330
31736
  if (typeof sectionId !== "string" || !sectionId) return;
31331
- const foundEl = document.querySelector(
31332
- `[data-ohw-section="${CSS.escape(sectionId)}"]`
31737
+ const escapedSectionId = CSS.escape(sectionId);
31738
+ const instanceEl = document.querySelector(
31739
+ `[data-ohw-instance="${escapedSectionId}"]`
31333
31740
  );
31741
+ const foundEl = instanceEl ? sectionElementOf(instanceEl) : document.querySelector(`[data-ohw-section="${escapedSectionId}"]`);
31334
31742
  const sectionEl = foundEl && !foundEl.hasAttribute("data-ohw-ai-template-hidden") && !foundEl.hasAttribute("data-ohw-ai-removed") && !foundEl.hasAttribute("data-ohw-ai-replaced-by") ? foundEl : null;
31335
31743
  const nodes = sectionEl ? collectEditableNodes(void 0, sectionEl).filter(
31336
31744
  (n) => n.key && !n.key.startsWith("__ohw_") && !["meta", "sections", "video-setting"].includes(n.type)
31337
- ).map((n) => ({ ...n, text: n.text ?? "", type: normalizeAiNodeType(n.type) })) : [];
31745
+ ).map((n) => ({ ...n, text: n.text ?? "", type: normalizeAiNodeType(n.type) })).map((n) => withNodeFontSize(n, sectionEl)) : [];
31338
31746
  const items = sectionEl ? collectItemGroups(sectionEl) : [];
31339
31747
  const box = sectionEl?.getBoundingClientRect();
31340
31748
  const rect = box ? {
@@ -31343,7 +31751,11 @@ function OhhwellsBridge() {
31343
31751
  width: box.width,
31344
31752
  height: box.height
31345
31753
  } : null;
31346
- const settings = resolveSectionSettingsContext(sectionId, aiSectionsRef.current, stylesRef.current);
31754
+ const settings = resolveSectionSettingsContext(
31755
+ sectionId,
31756
+ aiSectionsRef.current,
31757
+ stylesRef.current
31758
+ );
31347
31759
  const tree = resolveSectionTree(sectionId, aiSectionsRef.current);
31348
31760
  postToParentRef.current({
31349
31761
  type: "ow:section-nodes",
@@ -31777,10 +32189,10 @@ function OhhwellsBridge() {
31777
32189
  window.removeEventListener("message", handleDuplicateSection);
31778
32190
  window.removeEventListener("message", handleDeactivate);
31779
32191
  window.removeEventListener("message", handleSelectNav);
31780
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
31781
32192
  window.removeEventListener("message", handleToastAction);
31782
32193
  window.removeEventListener("message", handleFormCount);
31783
32194
  window.removeEventListener("message", handleUiEscape);
32195
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
31784
32196
  autoSaveTimers.current.forEach(clearTimeout);
31785
32197
  autoSaveTimers.current.clear();
31786
32198
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -32074,7 +32486,7 @@ function OhhwellsBridge() {
32074
32486
  postToParent2({
32075
32487
  type: "ow:ready",
32076
32488
  version: "1",
32077
- bridgeVersion: "0.1.158",
32489
+ bridgeVersion: "0.1.160-next.427",
32078
32490
  path: pathname,
32079
32491
  nodes: collectEditableNodes(editContentRef.current),
32080
32492
  sections