@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.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/OhhwellsBridge.tsx
4
- import React12, { useCallback as useCallback12, useEffect as useEffect17, useLayoutEffect as useLayoutEffect6, useRef as useRef14, useState as useState18 } from "react";
4
+ import React12, { useCallback as useCallback12, useEffect as useEffect17, useLayoutEffect as useLayoutEffect7, useRef as useRef14, useState as useState18 } from "react";
5
5
  import { createRoot as createRoot3 } from "react-dom/client";
6
6
  import { flushSync as flushSync3 } from "react-dom";
7
7
 
@@ -106,6 +106,56 @@ function isRenderableTree(value) {
106
106
  return AI_TREE_SCHEMA_VERSIONS.includes(tree.schemaVersion) && tree.type === "section" && Array.isArray(tree.rows);
107
107
  }
108
108
 
109
+ // src/lib/headline-size.ts
110
+ var HEADLINE_SIZE_MULTIPLIERS = {
111
+ xs: 0.7,
112
+ sm: 0.85,
113
+ base: 1,
114
+ lg: 1.2,
115
+ xl: 1.45,
116
+ "2xl": 1.75
117
+ };
118
+ var HEADLINE_SIZE_ATTR = "data-ohw-style-headlinesize";
119
+ var PREV_ATTR = "data-ohw-style-prev-fsmul";
120
+ var NODE_FONT_SIZE_ATTR = "data-ohw-style-prev-font-size";
121
+ function headingsIn(section) {
122
+ return Array.from(section.querySelectorAll("h1, h2, h3"));
123
+ }
124
+ function clearHeadlineSizes(root = document) {
125
+ for (const el of Array.from(root.querySelectorAll(`[${PREV_ATTR}]`))) {
126
+ const prev = el.getAttribute(PREV_ATTR) ?? "";
127
+ if (prev) el.style.setProperty("font-size", prev);
128
+ else el.style.removeProperty("font-size");
129
+ el.removeAttribute(PREV_ATTR);
130
+ }
131
+ }
132
+ function applyHeadlineSizes(root = document) {
133
+ clearHeadlineSizes(root);
134
+ for (const section of Array.from(
135
+ root.querySelectorAll(`[${HEADLINE_SIZE_ATTR}]`)
136
+ )) {
137
+ const multiplier = HEADLINE_SIZE_MULTIPLIERS[section.getAttribute(HEADLINE_SIZE_ATTR) ?? ""];
138
+ if (!multiplier || multiplier === 1) continue;
139
+ for (const heading of headingsIn(section)) {
140
+ if (heading.hasAttribute(NODE_FONT_SIZE_ATTR)) continue;
141
+ const base = Number.parseFloat(window.getComputedStyle(heading).fontSize);
142
+ if (!Number.isFinite(base) || base <= 0) continue;
143
+ heading.setAttribute(PREV_ATTR, heading.style.getPropertyValue("font-size"));
144
+ heading.style.setProperty("font-size", `${Math.round(base * multiplier * 100) / 100}px`, "important");
145
+ }
146
+ }
147
+ }
148
+ var resizeInstalled = false;
149
+ function installHeadlineSizeResize() {
150
+ if (resizeInstalled || typeof window === "undefined") return;
151
+ resizeInstalled = true;
152
+ let timer;
153
+ window.addEventListener("resize", () => {
154
+ if (timer) clearTimeout(timer);
155
+ timer = setTimeout(() => applyHeadlineSizes(), 120);
156
+ });
157
+ }
158
+
109
159
  // src/lib/sections.ts
110
160
  var LINK_PICKER_EXCLUDED_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
111
161
  function isChromeSection(el) {
@@ -5897,6 +5947,7 @@ var SECTION_ATTRS = {
5897
5947
  sectionBackground: "data-ohw-style-bg",
5898
5948
  textDistribution: "data-ohw-style-distribution",
5899
5949
  headlineScale: "data-ohw-style-headline",
5950
+ headlineSize: HEADLINE_SIZE_ATTR,
5900
5951
  imageAspect: "data-ohw-style-aspect",
5901
5952
  spacing: "data-ohw-style-spacing",
5902
5953
  verticalPadding: "data-ohw-style-vpadding",
@@ -5965,6 +6016,15 @@ function clearSectionAttrs(root) {
5965
6016
  }
5966
6017
  }
5967
6018
  var ITEM_WROTE_ATTR = "data-ohw-style-item";
6019
+ function paintIcon(slot, color) {
6020
+ for (const el of [slot, ...Array.from(slot.querySelectorAll("svg, svg *"))]) {
6021
+ const styled = el;
6022
+ if (!styled.style) continue;
6023
+ saveInline(styled, "color");
6024
+ styled.style.setProperty("color", color, "important");
6025
+ styled.setAttribute(NODE_WROTE_ATTR, "");
6026
+ }
6027
+ }
5968
6028
  function clearNodeProps(root) {
5969
6029
  for (const el of Array.from(root.querySelectorAll(`[${NODE_WROTE_ATTR}]`))) {
5970
6030
  const h = el;
@@ -6002,6 +6062,7 @@ function applyStylesToDom(store) {
6002
6062
  ensureStyleSheet();
6003
6063
  clearSectionAttrs(document);
6004
6064
  clearNodeProps(document);
6065
+ clearHeadlineSizes(document);
6005
6066
  loadStyleFonts(
6006
6067
  store ? [
6007
6068
  ...Object.values(store.nodes).flatMap((n) => n.fontFamily ? [n.fontFamily] : []),
@@ -6075,9 +6136,13 @@ function applyStylesToDom(store) {
6075
6136
  const nodes = document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`);
6076
6137
  for (const el of Array.from(nodes)) {
6077
6138
  if (override.color !== void 0) {
6078
- saveInline(el, "color");
6079
- el.style.setProperty("color", override.color, "important");
6080
- el.setAttribute(NODE_WROTE_ATTR, "");
6139
+ if (el.dataset.ohwEditable === "icon") {
6140
+ paintIcon(el, override.color);
6141
+ } else {
6142
+ saveInline(el, "color");
6143
+ el.style.setProperty("color", override.color, "important");
6144
+ el.setAttribute(NODE_WROTE_ATTR, "");
6145
+ }
6081
6146
  }
6082
6147
  if (override.fontFamily !== void 0) {
6083
6148
  saveInline(el, "font-family");
@@ -6134,6 +6199,18 @@ function applyStylesToDom(store) {
6134
6199
  }
6135
6200
  }
6136
6201
  }
6202
+ for (const [sectionId, override] of Object.entries(store.sections)) {
6203
+ if (override.iconColor === void 0) continue;
6204
+ const escaped = CSS.escape(sectionId);
6205
+ const byInstance = document.querySelectorAll(`[data-ohw-instance="${escaped}"]`);
6206
+ const sections = byInstance.length ? byInstance : document.querySelectorAll(`[data-ohw-section="${escaped}"]`);
6207
+ for (const marker of Array.from(sections)) {
6208
+ const icons = marker.querySelectorAll('[data-ohw-editable="icon"]');
6209
+ for (const el of Array.from(icons)) paintIcon(el, override.iconColor);
6210
+ }
6211
+ }
6212
+ applyHeadlineSizes(document);
6213
+ installHeadlineSizeResize();
6137
6214
  }
6138
6215
 
6139
6216
  // src/lib/ai-sections-store.ts
@@ -6173,17 +6250,21 @@ function resolveSectionTree(sectionId, aiSectionsRaw) {
6173
6250
  return parseAiSectionsState(aiSectionsRaw).sections.find((entry) => entry.id === sectionId)?.tree;
6174
6251
  }
6175
6252
  function resolveSectionSettingsContext(sectionId, aiSectionsRaw, stylesRaw) {
6253
+ const styleOverride = parseStyleStore(stylesRaw)?.sections[sectionId];
6254
+ const headlineSize = styleOverride?.headlineSize;
6176
6255
  const genSettings = parseAiSectionsState(aiSectionsRaw).sections.find(
6177
6256
  (entry) => entry.id === sectionId
6178
6257
  )?.tree.settings;
6179
- if (genSettings) return genSettings;
6180
- const styleOverride = parseStyleStore(stylesRaw)?.sections[sectionId];
6181
- if (!styleOverride?.verticalPadding && !styleOverride?.minHeight && !styleOverride?.spacing) {
6258
+ if (genSettings) {
6259
+ return { ...genSettings, ...headlineSize ? { headlineSize } : {} };
6260
+ }
6261
+ if (!styleOverride?.verticalPadding && !styleOverride?.minHeight && !styleOverride?.spacing && !headlineSize) {
6182
6262
  return void 0;
6183
6263
  }
6184
6264
  return {
6185
- verticalPadding: styleOverride.verticalPadding ?? legacyVerticalPaddingAlias(styleOverride.spacing),
6186
- minHeight: styleOverride.minHeight
6265
+ verticalPadding: styleOverride?.verticalPadding ?? legacyVerticalPaddingAlias(styleOverride?.spacing),
6266
+ minHeight: styleOverride?.minHeight,
6267
+ ...headlineSize ? { headlineSize } : {}
6187
6268
  };
6188
6269
  }
6189
6270
  function applyTreeToState(state, payload) {
@@ -6568,6 +6649,21 @@ function pricingCardFeatureCount(tree, index) {
6568
6649
  const target = findPricingCollection(tree)?.children?.[index];
6569
6650
  return target ? pricingFeatureList(target).length : 0;
6570
6651
  }
6652
+ function prevailingFeatureIcon(features) {
6653
+ const counts = /* @__PURE__ */ new Map();
6654
+ for (const row of features) {
6655
+ const key = `${row.icon ?? ""}|${row.iconColor ?? ""}`;
6656
+ const entry = counts.get(key) ?? { count: 0, ...row.icon ? { icon: row.icon } : {}, ...row.iconColor ? { iconColor: row.iconColor } : {} };
6657
+ entry.count += 1;
6658
+ counts.set(key, entry);
6659
+ }
6660
+ let best = null;
6661
+ for (const entry of counts.values()) {
6662
+ if (!best || entry.count > best.count) best = entry;
6663
+ }
6664
+ if (!best) return {};
6665
+ return { ...best.icon ? { icon: best.icon } : {}, ...best.iconColor ? { iconColor: best.iconColor } : {} };
6666
+ }
6571
6667
  function addPricingCardFeature(tree, index, text) {
6572
6668
  return withPricingCollection(tree, (collection) => {
6573
6669
  const children = collection.children;
@@ -6576,7 +6672,7 @@ function addPricingCardFeature(tree, index, text) {
6576
6672
  const features = pricingFeatureList(target);
6577
6673
  if (features.length >= PRICING_FEATURES_MAX) return false;
6578
6674
  target.slots = target.slots ?? {};
6579
- target.slots.features = [...features, { text }];
6675
+ target.slots.features = [...features, { text, ...prevailingFeatureIcon(features) }];
6580
6676
  return true;
6581
6677
  });
6582
6678
  }
@@ -7188,6 +7284,23 @@ function applyCustomCodeToHead(content) {
7188
7284
  if (beforeNodes.length > 0) document.head.append(...beforeNodes);
7189
7285
  }
7190
7286
 
7287
+ // src/lib/node-font-size.ts
7288
+ var TEXT_NODE_TYPES = /* @__PURE__ */ new Set(["text", "text/rich", "plain", "link"]);
7289
+ function escapeKey(key) {
7290
+ return typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(key) : key.replace(/["\\]/g, "\\$&");
7291
+ }
7292
+ function measureNodeFontSize(key, type, root) {
7293
+ if (!TEXT_NODE_TYPES.has(type)) return void 0;
7294
+ const el = root.querySelector(`[data-ohw-editable][data-ohw-key="${escapeKey(key)}"]`);
7295
+ if (!el) return void 0;
7296
+ const size = Number.parseFloat(window.getComputedStyle(el).fontSize);
7297
+ return Number.isFinite(size) && size > 0 ? Math.round(size) : void 0;
7298
+ }
7299
+ function withNodeFontSize(node, root) {
7300
+ const fontSize = measureNodeFontSize(node.key, node.type, root);
7301
+ return fontSize === void 0 ? node : { ...node, fontSize };
7302
+ }
7303
+
7191
7304
  // src/lib/selectNavContainer.ts
7192
7305
  function isOnScreen(el) {
7193
7306
  const rect = el.getBoundingClientRect();
@@ -7776,7 +7889,7 @@ var AI_MOBILE_CSS = [
7776
7889
  // page's first section. Top only (bottom needs no nav clearance); ~96px approximates the
7777
7890
  // template nav's own height (measured ~94px) — there's no shared `--ohw-header-h` var to read
7778
7891
  // an exact figure from (no live measurement exists), so this is a safe approximation, not a
7779
- // pixel-exact fit. Horizontal spacing already comes from `data-ai-container`.
7892
+ // pixel-exact fit. Horizontal spacing already comes from `data-ai-section-inner`.
7780
7893
  '[data-ai-overlay-clearance] [data-ai-overlap-layer="overlay"]{padding-top:96px}',
7781
7894
  // A full-bleed carousel's height is pure arithmetic: it spans the whole window, so 16/9 turns
7782
7895
  // window width straight into height. Measured at 16/9 it came out ~90% of the viewport at EVERY
@@ -7792,8 +7905,10 @@ var AI_MOBILE_CSS = [
7792
7905
  "[data-ai-section]{overflow-x:hidden}",
7793
7906
  // `:not([data-ai-full-bleed])`: this `!important` gutter beat the inline `padding: 0` that a
7794
7907
  // full-bleed section's container sets, so a carousel/media hero that is edge-to-edge on desktop
7795
- // still got a 20px band of section background down both sides on a phone.
7796
- "[data-ai-container]:not([data-ai-full-bleed]){padding:0 20px !important}",
7908
+ // still got a 20px band of section background down both sides on a phone. The footer's own
7909
+ // container keeps the unguarded rule below — `data-ai-full-bleed` can never appear on it.
7910
+ "[data-ai-section-inner]:not([data-ai-full-bleed]){padding:0 20px !important}",
7911
+ "[data-ai-container]{padding:0 20px !important}",
7797
7912
  "[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
7798
7913
  "[data-ai-cell]{width:100%;min-width:0}",
7799
7914
  // The rule above turns every row into a COLUMN flex container on phones, which silently breaks
@@ -7998,6 +8113,31 @@ function pricingCardEmphasis(featured, ctx, hasBg) {
7998
8113
  function textAttrs(ctx, path) {
7999
8114
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
8000
8115
  }
8116
+ var AI_RESPONSIVE_CSS = [
8117
+ "@media (max-width: 960px) {",
8118
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
8119
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
8120
+ "}",
8121
+ "@media (max-width: 640px) {",
8122
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
8123
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
8124
+ // Group containers flatten to a column on phones; span placements come along for free.
8125
+ // `:not([data-ai-group="overlap"])` for the same reason as the twin pair in AI_MOBILE_CSS — see
8126
+ // the comment there. This sheet's copy is the narrower selector, so without the exemption it
8127
+ // flattens an `overlap` hero below 640px even though the other rule stands down: measured a 390px
8128
+ // viewport with the heading/CTA in flow *under* the image, 219px of section background where the
8129
+ // layered text used to be.
8130
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) { display: flex !important; flex-direction: column !important; }',
8131
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) > * { grid-column: auto !important; }',
8132
+ // `:not([data-ai-full-bleed])` for the same reason as the twin rule in AI_MOBILE_CSS: this one
8133
+ // is narrower (0,2,0 vs 0,1,0) and also `!important`, so without the guard it wins outright and
8134
+ // a full-bleed hero gets its 20px side band back below 640px only — the one width where the
8135
+ // other rule had already been taught to stand down.
8136
+ " [data-ai-responsive] [data-ai-section-inner]:not([data-ai-full-bleed]) { padding-left: 20px !important; padding-right: 20px !important; }",
8137
+ " [data-ai-responsive] { overflow-x: hidden; }",
8138
+ " [data-ai-responsive] img { max-width: 100%; }",
8139
+ "}"
8140
+ ].join("\n");
8001
8141
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
8002
8142
  function MediaBox({
8003
8143
  refValue,
@@ -8052,7 +8192,7 @@ function MediaBox({
8052
8192
  position: fill ? "absolute" : void 0,
8053
8193
  inset: fill ? 0 : void 0,
8054
8194
  width: fill ? "100%" : fixedSize ? "auto" : "100%",
8055
- height: fill ? "100%" : void 0,
8195
+ height: fill ? "100%" : fixedSize ? height : void 0,
8056
8196
  aspectRatio: fill ? void 0 : aspectRatio,
8057
8197
  minHeight: fill || fixedSize ? void 0 : height ?? (aspectRatio ? void 0 : 180),
8058
8198
  borderRadius: radius,
@@ -9548,7 +9688,7 @@ function CollectionBlock({ node, ctx, path }) {
9548
9688
  return /* @__PURE__ */ jsx(
9549
9689
  "div",
9550
9690
  {
9551
- "data-ai-grid": "",
9691
+ "data-ai-grid": String(itemsPerRow),
9552
9692
  "data-ohw-pricing-collection": isPricing ? "" : void 0,
9553
9693
  style: {
9554
9694
  display: "grid",
@@ -9850,7 +9990,7 @@ function renderNode(node, ctx, path, ambientAlign) {
9850
9990
  },
9851
9991
  children: [
9852
9992
  i > 0 && ctx.heroOverlapFullBleed ? (
9853
- // The section's `data-ai-container` gave up its max-width/gutter so the base
9993
+ // The section's `data-ai-section-inner` gave up its max-width/gutter so the base
9854
9994
  // layer (this group's i===0 sibling) can span edge-to-edge — reintroduced here,
9855
9995
  // scoped to just the overlay text, so a heading/copy/CTA stack doesn't stretch
9856
9996
  // the full section width instead of reading at the usual content width.
@@ -10269,6 +10409,7 @@ function AiTreeRenderer({
10269
10409
  ...fillHeight ? { "data-ai-fill-height": "" } : {},
10270
10410
  ...needsOverlayClearance ? { "data-ai-overlay-clearance": "" } : {},
10271
10411
  ...bgAttrs,
10412
+ "data-ai-responsive": "",
10272
10413
  style: {
10273
10414
  position: "relative",
10274
10415
  padding: `${padCss} 0`,
@@ -10282,12 +10423,13 @@ function AiTreeRenderer({
10282
10423
  ...fillHeight ? { display: "grid" } : {}
10283
10424
  },
10284
10425
  children: [
10285
- isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10426
+ /* @__PURE__ */ jsx("style", { children: AI_RESPONSIVE_CSS }),
10286
10427
  /* @__PURE__ */ jsx("style", { children: AI_MOBILE_CSS }),
10428
+ isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10287
10429
  /* @__PURE__ */ jsx(
10288
10430
  "div",
10289
10431
  {
10290
- "data-ai-container": "",
10432
+ "data-ai-section-inner": "",
10291
10433
  ...fullBleedMedia ? { "data-ai-full-bleed": "" } : {},
10292
10434
  style: {
10293
10435
  position: "relative",
@@ -10304,7 +10446,7 @@ function AiTreeRenderer({
10304
10446
  children: tree.rows.map((row, r2) => /* @__PURE__ */ jsx(
10305
10447
  "div",
10306
10448
  {
10307
- "data-ai-row": "",
10449
+ "data-ai-columns": "",
10308
10450
  style: {
10309
10451
  display: "grid",
10310
10452
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -10966,9 +11108,10 @@ function applyAiSectionsToDom(state, options) {
10966
11108
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
10967
11109
  continue;
10968
11110
  }
10969
- const isNewMount = !existing || !existing.container.isConnected;
10970
11111
  let container = existing?.container;
11112
+ let isNewMount = false;
10971
11113
  if (!container || !container.isConnected) {
11114
+ isNewMount = true;
10972
11115
  existing?.root.unmount();
10973
11116
  container?.remove();
10974
11117
  container = document.createElement("div");
@@ -17127,7 +17270,7 @@ function apiBase() {
17127
17270
  function bridgeVersion() {
17128
17271
  try {
17129
17272
  if (true) {
17130
- return "0.1.158";
17273
+ return "0.1.160-next.427";
17131
17274
  }
17132
17275
  } catch {
17133
17276
  }
@@ -17141,7 +17284,7 @@ function stampBridgeVersionOnDocument() {
17141
17284
  return;
17142
17285
  }
17143
17286
  try {
17144
- const v = true ? "0.1.158" : "";
17287
+ const v = true ? "0.1.160-next.427" : "";
17145
17288
  if (v) {
17146
17289
  document.documentElement.setAttribute("data-ohw-bridge-version", v);
17147
17290
  window.__OHW_BRIDGE_VERSION__ = v;
@@ -17819,6 +17962,7 @@ function MediaOverlay({
17819
17962
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
17820
17963
  );
17821
17964
  }, [isVideo]);
17965
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
17822
17966
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
17823
17967
  const box = {
17824
17968
  position: "fixed",
@@ -17917,11 +18061,10 @@ function MediaOverlay({
17917
18061
  className: "flex items-center justify-center cursor-pointer",
17918
18062
  style: {
17919
18063
  ...box,
17920
- // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
17921
- // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
17922
- // in-document, pointer-events does it natively. The button below opts back in, so
17923
- // Replace still works.
17924
- pointerEvents: hover.hasTextOverlap || cueOnly ? "none" : "auto",
18064
+ // Visual only. `pointer-events: auto` on this full-bleed rect covered the navbar
18065
+ // (hero bg includes the absolute TopNav) and swallowed CTA press-drag. Text overlay
18066
+ // and cueOnly also need clicks to fall through — Replace / video controls opt back in.
18067
+ pointerEvents: "none",
17925
18068
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
17926
18069
  // than hovered. Hover keeps the existing tinted preview.
17927
18070
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -17948,17 +18091,17 @@ function MediaOverlay({
17948
18091
  },
17949
18092
  children: [
17950
18093
  isVideo ? /* @__PURE__ */ jsx17(Film, { size: 14 }) : /* @__PURE__ */ jsx17(ImageIcon, { size: 14 }),
17951
- isVideo ? "Replace video" : "Replace image"
18094
+ replaceLabel
17952
18095
  ]
17953
18096
  }
17954
18097
  ),
17955
- replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ jsxs8(
18098
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ jsxs8(
17956
18099
  Button,
17957
18100
  {
17958
18101
  "data-ohw-media-overlay": "",
17959
18102
  variant: "outline",
17960
18103
  size: "sm",
17961
- "aria-label": isVideo ? "Replace video" : "Replace image",
18104
+ "aria-label": replaceLabel,
17962
18105
  className: "gap-1.5 cursor-pointer hover:bg-background",
17963
18106
  style: {
17964
18107
  ...OVERLAY_BUTTON_STYLE,
@@ -17981,7 +18124,7 @@ function MediaOverlay({
17981
18124
  },
17982
18125
  children: [
17983
18126
  isVideo ? /* @__PURE__ */ jsx17(Film, { size: 14 }) : /* @__PURE__ */ jsx17(ImageIcon, { size: 14 }),
17984
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
18127
+ replaceMode === "full" ? replaceLabel : null
17985
18128
  ]
17986
18129
  }
17987
18130
  )
@@ -18050,6 +18193,89 @@ function CarouselOverlay({
18050
18193
  // src/ui/ai-section/AiSectionOverlay.tsx
18051
18194
  import { useCallback as useCallback2, useEffect as useEffect5, useRef as useRef4, useState as useState7 } from "react";
18052
18195
  import { Check, X } from "lucide-react";
18196
+
18197
+ // src/lib/ai-change-highlight.ts
18198
+ var STYLE_ID = "ohw-ai-changed-style";
18199
+ var MARK_ATTR = "data-ohw-ai-changed";
18200
+ var HIGHLIGHT_MS = 2200;
18201
+ var HIGHLIGHT_CSS = `
18202
+ @keyframes ohw-ai-changed-pulse {
18203
+ 0% { outline-color: rgba(0, 120, 229, 0.95); box-shadow: 0 0 0 5px rgba(0, 120, 229, 0.20); }
18204
+ 72% { outline-color: rgba(0, 120, 229, 0.95); box-shadow: 0 0 0 5px rgba(0, 120, 229, 0.12); }
18205
+ 100% { outline-color: rgba(0, 120, 229, 0); box-shadow: 0 0 0 5px rgba(0, 120, 229, 0); }
18206
+ }
18207
+ [${MARK_ATTR}] {
18208
+ outline: 2px solid rgba(0, 120, 229, 0.95) !important;
18209
+ outline-offset: 3px !important;
18210
+ animation: ohw-ai-changed-pulse ${HIGHLIGHT_MS}ms ease-out forwards !important;
18211
+ }
18212
+ @media (prefers-reduced-motion: reduce) {
18213
+ [${MARK_ATTR}] { animation: none !important; }
18214
+ }
18215
+ `;
18216
+ function ensureStyleSheet2() {
18217
+ if (document.getElementById(STYLE_ID)) return;
18218
+ const el = document.createElement("style");
18219
+ el.id = STYLE_ID;
18220
+ el.textContent = HIGHLIGHT_CSS;
18221
+ document.head.appendChild(el);
18222
+ }
18223
+ function targetsFor(keys, item) {
18224
+ const out = /* @__PURE__ */ new Set();
18225
+ for (const key of keys) {
18226
+ if (!key) continue;
18227
+ for (const el of Array.from(
18228
+ document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`)
18229
+ )) {
18230
+ out.add(el);
18231
+ }
18232
+ }
18233
+ if (item) {
18234
+ const section = document.querySelector(`[data-ohw-instance="${CSS.escape(item.sectionId)}"]`) ?? document.querySelector(`[data-ohw-section="${CSS.escape(item.sectionId)}"]`);
18235
+ const stamp = `${item.group}:${item.index}`;
18236
+ const el = section?.querySelector(`[data-ohw-item="${CSS.escape(stamp)}"]`);
18237
+ if (el) out.add(el);
18238
+ }
18239
+ return [...out];
18240
+ }
18241
+ var clearTimer = null;
18242
+ function clearChangeHighlight() {
18243
+ if (clearTimer !== null) {
18244
+ clearTimeout(clearTimer);
18245
+ clearTimer = null;
18246
+ }
18247
+ for (const el of Array.from(document.querySelectorAll(`[${MARK_ATTR}]`))) {
18248
+ el.removeAttribute(MARK_ATTR);
18249
+ }
18250
+ }
18251
+ function highlightChangedElements(keys, item) {
18252
+ clearChangeHighlight();
18253
+ const targets = targetsFor(keys, item);
18254
+ if (targets.length === 0) return 0;
18255
+ ensureStyleSheet2();
18256
+ for (const el of targets) el.setAttribute(MARK_ATTR, "");
18257
+ const rect = targets[0].getBoundingClientRect();
18258
+ if (rect.bottom < 0 || rect.top > window.innerHeight) {
18259
+ targets[0].scrollIntoView({ behavior: "smooth", block: "center" });
18260
+ }
18261
+ clearTimer = setTimeout(clearChangeHighlight, HIGHLIGHT_MS);
18262
+ return targets.length;
18263
+ }
18264
+ function installAiChangeHighlight() {
18265
+ const onMessage = (e) => {
18266
+ const data = e.data;
18267
+ if (data?.type !== "ow:ai-highlight") return;
18268
+ const keys = Array.isArray(data.keys) ? data.keys.filter((k) => typeof k === "string") : [];
18269
+ const raw = data.item;
18270
+ 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;
18271
+ const found = highlightChangedElements(keys, item);
18272
+ window.parent?.postMessage({ type: "ow:ai-highlight-done", found }, "*");
18273
+ };
18274
+ window.addEventListener("message", onMessage);
18275
+ return () => window.removeEventListener("message", onMessage);
18276
+ }
18277
+
18278
+ // src/ui/ai-section/AiSectionOverlay.tsx
18053
18279
  import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs10 } from "react/jsx-runtime";
18054
18280
  var findSectionElement = findByInstanceId;
18055
18281
  function readRect(instanceId) {
@@ -18301,6 +18527,7 @@ function AiSectionOverlay({
18301
18527
  window.addEventListener("message", onMessage);
18302
18528
  return () => window.removeEventListener("message", onMessage);
18303
18529
  }, [postToParent2]);
18530
+ useEffect5(() => installAiChangeHighlight(), []);
18304
18531
  const [hoveredId, setHoveredId] = useState7(null);
18305
18532
  useEffect5(() => {
18306
18533
  const onMove = (e) => {
@@ -18490,8 +18717,24 @@ function AiSectionOverlay({
18490
18717
  }
18491
18718
 
18492
18719
  // src/ui/ai-section/AiMediaStripOverlay.tsx
18493
- import { useCallback as useCallback3, useEffect as useEffect6, useRef as useRef5, useState as useState8 } from "react";
18494
- import { Star } from "lucide-react";
18720
+ import { useCallback as useCallback3, useEffect as useEffect6, useLayoutEffect as useLayoutEffect4, useRef as useRef5, useState as useState8 } from "react";
18721
+ import {
18722
+ AlignCenter,
18723
+ AlignLeft,
18724
+ AlignRight,
18725
+ ArrowLeft as ArrowLeft2,
18726
+ ArrowRight as ArrowRight2,
18727
+ Check as Check2,
18728
+ ChevronDown as ChevronDown3,
18729
+ Image as ImageIcon2,
18730
+ ImageMinus,
18731
+ ImagePlus,
18732
+ Link as LinkIcon,
18733
+ Minus,
18734
+ Palette,
18735
+ Plus as Plus2,
18736
+ Star
18737
+ } from "lucide-react";
18495
18738
 
18496
18739
  // src/lib/ai-media-strip-tree.ts
18497
18740
  var MEDIA_STRIP_ITEM_MIN = 1;
@@ -18605,14 +18848,6 @@ function setMediaStripItemAsset(tree, index, assetId) {
18605
18848
  return true;
18606
18849
  });
18607
18850
  }
18608
- function setMediaStripItemAlt(tree, index, alt) {
18609
- return withMediaStrip(tree, (block) => {
18610
- const items = block.items;
18611
- if (index < 0 || index >= items.length) return false;
18612
- items[index] = { ...items[index], alt };
18613
- return true;
18614
- });
18615
- }
18616
18851
  function setMediaStripItemLink(tree, index, link) {
18617
18852
  return withMediaStrip(tree, (block) => {
18618
18853
  const items = block.items;
@@ -18630,11 +18865,117 @@ function setMediaStripItemLink(tree, index, link) {
18630
18865
  });
18631
18866
  }
18632
18867
 
18868
+ // src/ui/dialog.tsx
18869
+ import * as React9 from "react";
18870
+ import { Dialog as DialogPrimitive } from "radix-ui";
18871
+ import { X as X2 } from "lucide-react";
18872
+ import { jsx as jsx20, jsxs as jsxs11 } from "react/jsx-runtime";
18873
+ function Dialog2({
18874
+ ...props
18875
+ }) {
18876
+ return /* @__PURE__ */ jsx20(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
18877
+ }
18878
+ function DialogPortal({
18879
+ ...props
18880
+ }) {
18881
+ return /* @__PURE__ */ jsx20(DialogPrimitive.Portal, { ...props });
18882
+ }
18883
+ function DialogOverlay({
18884
+ className,
18885
+ ...props
18886
+ }) {
18887
+ return /* @__PURE__ */ jsx20(
18888
+ DialogPrimitive.Overlay,
18889
+ {
18890
+ "data-slot": "dialog-overlay",
18891
+ "data-ohw-link-modal-root": "",
18892
+ className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
18893
+ ...props
18894
+ }
18895
+ );
18896
+ }
18897
+ var DialogContent = React9.forwardRef(
18898
+ ({ className, children, showCloseButton = true, container, ...props }, ref) => {
18899
+ const positionMode = container ? "absolute" : "fixed";
18900
+ return /* @__PURE__ */ jsxs11(DialogPortal, { container: container ?? void 0, children: [
18901
+ /* @__PURE__ */ jsx20(DialogOverlay, { className: cn(positionMode, "inset-0") }),
18902
+ /* @__PURE__ */ jsxs11(
18903
+ DialogPrimitive.Content,
18904
+ {
18905
+ ref,
18906
+ "data-slot": "dialog-content",
18907
+ className: cn(
18908
+ positionMode,
18909
+ "left-1/2 top-1/2 z-[2147483647] flex w-full max-w-[448px] -translate-x-1/2 -translate-y-1/2 flex-col overflow-visible",
18910
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
18911
+ container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
18912
+ className
18913
+ ),
18914
+ onMouseDown: (e) => e.stopPropagation(),
18915
+ ...props,
18916
+ children: [
18917
+ children,
18918
+ showCloseButton ? /* @__PURE__ */ jsx20(
18919
+ DialogPrimitive.Close,
18920
+ {
18921
+ type: "button",
18922
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
18923
+ "aria-label": "Close",
18924
+ children: /* @__PURE__ */ jsx20(X2, { size: 16, "aria-hidden": true })
18925
+ }
18926
+ ) : null
18927
+ ]
18928
+ }
18929
+ )
18930
+ ] });
18931
+ }
18932
+ );
18933
+ DialogContent.displayName = DialogPrimitive.Content.displayName;
18934
+ function DialogHeader({
18935
+ className,
18936
+ ...props
18937
+ }) {
18938
+ return /* @__PURE__ */ jsx20("div", { className: cn("flex flex-col gap-1.5", className), ...props });
18939
+ }
18940
+ function DialogFooter({
18941
+ className,
18942
+ ...props
18943
+ }) {
18944
+ return /* @__PURE__ */ jsx20(
18945
+ "div",
18946
+ {
18947
+ className: cn("flex items-center justify-end gap-2", className),
18948
+ ...props
18949
+ }
18950
+ );
18951
+ }
18952
+ var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx20(
18953
+ DialogPrimitive.Title,
18954
+ {
18955
+ ref,
18956
+ className: cn(
18957
+ "text-lg font-semibold leading-none tracking-tight text-card-foreground",
18958
+ className
18959
+ ),
18960
+ ...props
18961
+ }
18962
+ ));
18963
+ DialogTitle.displayName = DialogPrimitive.Title.displayName;
18964
+ var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx20(
18965
+ DialogPrimitive.Description,
18966
+ {
18967
+ ref,
18968
+ className: cn("text-sm text-muted-foreground", className),
18969
+ ...props
18970
+ }
18971
+ ));
18972
+ DialogDescription.displayName = DialogPrimitive.Description.displayName;
18973
+ var DialogClose = DialogPrimitive.Close;
18974
+
18633
18975
  // src/ui/ai-section/AiMediaStripOverlay.tsx
18634
- import { Fragment as Fragment7, jsx as jsx20, jsxs as jsxs11 } from "react/jsx-runtime";
18635
- var PRIMARY3 = "#0885FE";
18636
- var OHW_BLUE = `var(--ohw-primary, ${PRIMARY3})`;
18637
- var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY3}) 60%, #000000)`;
18976
+ import { Fragment as Fragment7, jsx as jsx21, jsxs as jsxs12 } from "react/jsx-runtime";
18977
+ var PRIMARY3 = "var(--ohw-primary, #0885FE)";
18978
+ var CHROME_SELECTOR = "[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-link-modal-root], [data-ohw-section-picker], [data-ohw-ai-review], [data-ohw-more-menu]";
18638
18979
  function findMediaStripItemElement(sectionId, index) {
18639
18980
  const sectionEl = findByInstanceId(sectionId);
18640
18981
  if (!sectionEl) return null;
@@ -18644,14 +18985,35 @@ function findMediaStripItemElement(sectionId, index) {
18644
18985
  }
18645
18986
  return null;
18646
18987
  }
18647
- function readRect2(ref) {
18648
- const el = ref && findMediaStripItemElement(ref.sectionId, ref.index);
18988
+ function itemRefFromTarget(target) {
18989
+ if (!(target instanceof Element)) return null;
18990
+ if (target.closest(CHROME_SELECTOR)) return null;
18991
+ const itemEl = target.closest("[data-ohw-media-strip-item]");
18992
+ if (!itemEl) return null;
18993
+ const sectionEl = itemEl.closest("[data-ohw-section]");
18994
+ const unit = sectionEl ? movableUnit(sectionEl) : null;
18995
+ const sectionId = unit ? instanceIdOf(unit) || null : null;
18996
+ const index = Number(itemEl.dataset.ohwMediaStripIndex);
18997
+ if (!sectionId || Number.isNaN(index)) return null;
18998
+ return { sectionId, index };
18999
+ }
19000
+ function sameRef(a, b) {
19001
+ return a?.sectionId === b?.sectionId && a?.index === b?.index;
19002
+ }
19003
+ function rectOf(el) {
18649
19004
  if (!el) return null;
18650
19005
  const r2 = el.getBoundingClientRect();
18651
19006
  if (r2.width <= 0 || r2.height <= 0) return null;
18652
19007
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
18653
19008
  }
18654
- function useLiveItemRect(ref) {
19009
+ function readRect2(ref) {
19010
+ return rectOf(ref && findMediaStripItemElement(ref.sectionId, ref.index));
19011
+ }
19012
+ function readStripRect(ref) {
19013
+ const item = ref && findMediaStripItemElement(ref.sectionId, ref.index);
19014
+ return rectOf(item?.closest("[data-ohw-media-strip]") ?? item);
19015
+ }
19016
+ function useLiveItemRect(ref, strip = false) {
18655
19017
  const [rect, setRect] = useState8(null);
18656
19018
  useEffect6(() => {
18657
19019
  if (!ref) {
@@ -18659,7 +19021,7 @@ function useLiveItemRect(ref) {
18659
19021
  return;
18660
19022
  }
18661
19023
  const update = () => {
18662
- const next = readRect2(ref);
19024
+ const next = strip ? readStripRect(ref) : readRect2(ref);
18663
19025
  setRect(
18664
19026
  (prev) => prev && next && prev.top === next.top && prev.left === next.left && prev.width === next.width && prev.height === next.height ? prev : next
18665
19027
  );
@@ -18674,9 +19036,49 @@ function useLiveItemRect(ref) {
18674
19036
  window.removeEventListener("resize", update);
18675
19037
  clearInterval(interval);
18676
19038
  };
18677
- }, [ref?.sectionId, ref?.index]);
19039
+ }, [ref?.sectionId, ref?.index, strip]);
18678
19040
  return rect;
18679
19041
  }
19042
+ var TOOLBAR_STRIP_GAP = 52;
19043
+ var TOOLBAR_STRIP_GAP_MOBILE = 42;
19044
+ var TOOLBAR_EDGE_MARGIN2 = 4;
19045
+ function StripToolbarSlot({ stripRect, children }) {
19046
+ const slotRef = useRef5(null);
19047
+ const [shift, setShift] = useState8(0);
19048
+ const centerX = stripRect.left + stripRect.width / 2;
19049
+ useLayoutEffect4(() => {
19050
+ const slot = slotRef.current;
19051
+ if (!slot) return;
19052
+ const clamp = () => {
19053
+ const w = slot.offsetWidth;
19054
+ if (w <= 0) return;
19055
+ const half = w / 2;
19056
+ const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
19057
+ setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
19058
+ };
19059
+ clamp();
19060
+ const ro = new ResizeObserver(clamp);
19061
+ ro.observe(slot);
19062
+ return () => ro.disconnect();
19063
+ }, [centerX, children]);
19064
+ const mobile = window.innerWidth <= 480;
19065
+ return /* @__PURE__ */ jsx21(
19066
+ "div",
19067
+ {
19068
+ ref: slotRef,
19069
+ "data-ohw-bridge": "",
19070
+ "data-ohw-item-interaction": "",
19071
+ className: "pointer-events-auto fixed",
19072
+ style: {
19073
+ top: stripRect.top - (mobile ? TOOLBAR_STRIP_GAP_MOBILE : TOOLBAR_STRIP_GAP),
19074
+ left: centerX + shift,
19075
+ transform: "translate(-50%, -100%)",
19076
+ zIndex: 2147483645
19077
+ },
19078
+ children
19079
+ }
19080
+ );
19081
+ }
18680
19082
  function requestValidateTree(postToParent2, tree) {
18681
19083
  return new Promise((resolve) => {
18682
19084
  const id = `ai-validate-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -18713,116 +19115,72 @@ function requestAssetPick(postToParent2) {
18713
19115
  postToParent2({ type: "ow:asset-pick", id });
18714
19116
  });
18715
19117
  }
18716
- function Pill({
19118
+ function ToolbarAction({
18717
19119
  label,
18718
19120
  onClick,
18719
- disabled,
18720
- active
19121
+ disabled = false,
19122
+ active = false,
19123
+ children
18721
19124
  }) {
18722
- return /* @__PURE__ */ jsx20(
18723
- "button",
18724
- {
18725
- type: "button",
18726
- onClick,
18727
- disabled,
18728
- style: {
18729
- height: 28,
18730
- padding: "0 10px",
18731
- display: "inline-flex",
18732
- alignItems: "center",
18733
- gap: 4,
18734
- borderRadius: 999,
18735
- border: "none",
18736
- cursor: disabled ? "default" : "pointer",
18737
- background: active ? OHW_BLUE_DARK : "#ffffff",
18738
- color: active ? "#ffffff" : OHW_BLUE_DARK,
18739
- opacity: disabled ? 0.4 : 1,
18740
- fontSize: 12,
18741
- lineHeight: "16px",
18742
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18743
- whiteSpace: "nowrap",
18744
- flex: "0 0 auto"
18745
- },
18746
- children: label
18747
- }
18748
- );
19125
+ const button = /* @__PURE__ */ jsx21(CustomToolbarButton, { type: "button", "aria-label": label, disabled, active, onClick, className: "size-8", children });
19126
+ return /* @__PURE__ */ jsxs12(Tooltip, { children: [
19127
+ /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ jsx21("span", { className: "inline-flex", children: button }) : button }),
19128
+ /* @__PURE__ */ jsx21(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19129
+ ] });
18749
19130
  }
18750
- function AltInput({ value, onCommit }) {
18751
- const [draft, setDraft] = useState8(value);
18752
- useEffect6(() => setDraft(value), [value]);
18753
- return /* @__PURE__ */ jsxs11(
18754
- "div",
18755
- {
18756
- style: {
18757
- display: "flex",
18758
- alignItems: "center",
18759
- gap: 6,
18760
- height: 28,
18761
- padding: "0 12px",
18762
- borderRadius: 999,
18763
- background: "#ffffff",
18764
- width: "100%",
18765
- maxWidth: 260,
18766
- boxSizing: "border-box"
18767
- },
18768
- children: [
18769
- /* @__PURE__ */ jsx20(
18770
- "span",
18771
- {
18772
- style: {
18773
- flex: "0 0 auto",
18774
- color: OHW_BLUE_DARK,
18775
- fontSize: 10,
18776
- fontWeight: 700,
18777
- textTransform: "uppercase",
18778
- letterSpacing: 0.4
18779
- },
18780
- children: "Alt"
18781
- }
18782
- ),
18783
- /* @__PURE__ */ jsx20(
18784
- "input",
18785
- {
18786
- type: "text",
18787
- value: draft,
18788
- onChange: (e) => setDraft(e.target.value),
18789
- onBlur: () => {
18790
- if (draft !== value) onCommit(draft);
18791
- },
18792
- onKeyDown: (e) => {
18793
- if (e.key === "Enter") e.currentTarget.blur();
18794
- if (e.key === "Escape") {
18795
- setDraft(value);
18796
- e.currentTarget.blur();
18797
- }
18798
- },
18799
- placeholder: "Describe this image",
18800
- "aria-label": "Image alt text",
18801
- style: {
18802
- flex: 1,
18803
- minWidth: 0,
18804
- border: "none",
18805
- outline: "none",
18806
- background: "transparent",
18807
- fontSize: 12,
18808
- lineHeight: "16px",
18809
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18810
- color: "#1f2937"
18811
- }
18812
- }
18813
- )
18814
- ]
18815
- }
18816
- );
19131
+ function ToolbarSeparator() {
19132
+ return /* @__PURE__ */ jsx21("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19133
+ }
19134
+ function OptionMenu({
19135
+ label,
19136
+ value,
19137
+ options,
19138
+ onChange
19139
+ }) {
19140
+ const current = options.find((o) => o.value === value) ?? options[0];
19141
+ return /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19142
+ /* @__PURE__ */ jsx21("span", { className: "text-xs text-muted-foreground", children: label }),
19143
+ /* @__PURE__ */ jsxs12(DropdownMenu, { modal: false, children: [
19144
+ /* @__PURE__ */ jsx21(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs12(
19145
+ "button",
19146
+ {
19147
+ type: "button",
19148
+ "aria-label": label,
19149
+ className: "inline-flex h-8 min-w-[76px] items-center gap-[7px] rounded-[6px] bg-transparent px-2 text-[13px] font-medium text-foreground transition-colors hover:bg-muted data-[state=open]:bg-muted",
19150
+ children: [
19151
+ /* @__PURE__ */ jsx21("span", { className: "flex-1 text-left", children: current.label }),
19152
+ /* @__PURE__ */ jsx21(ChevronDown3, { size: 16, style: { pointerEvents: "none" } })
19153
+ ]
19154
+ }
19155
+ ) }),
19156
+ /* @__PURE__ */ jsx21(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[132px] min-w-[132px] p-1", children: options.map((option) => {
19157
+ const selected = option.value === value;
19158
+ return /* @__PURE__ */ jsxs12(
19159
+ DropdownMenuItem,
19160
+ {
19161
+ role: "menuitemradio",
19162
+ "aria-checked": selected,
19163
+ onSelect: () => onChange(option.value),
19164
+ className: cn("grid grid-cols-[1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
19165
+ children: [
19166
+ /* @__PURE__ */ jsx21("span", { children: option.label }),
19167
+ selected && /* @__PURE__ */ jsx21(Check2, { size: 16, className: "text-bridge-primary" })
19168
+ ]
19169
+ },
19170
+ option.value
19171
+ );
19172
+ }) })
19173
+ ] })
19174
+ ] });
18817
19175
  }
18818
19176
  var VARIANTS = [
18819
19177
  { value: "logos", label: "Logos" },
18820
19178
  { value: "reel", label: "Reel" }
18821
19179
  ];
18822
19180
  var ALIGNS = [
18823
- { value: "left", label: "Left" },
18824
- { value: "center", label: "Center" },
18825
- { value: "right", label: "Right" }
19181
+ { value: "left", label: "Align left", Icon: AlignLeft },
19182
+ { value: "center", label: "Align center", Icon: AlignCenter },
19183
+ { value: "right", label: "Align right", Icon: AlignRight }
18826
19184
  ];
18827
19185
  var HOVERS = [
18828
19186
  { value: "none", label: "None" },
@@ -18830,104 +19188,104 @@ var HOVERS = [
18830
19188
  { value: "zoom", label: "Zoom" },
18831
19189
  { value: "fade", label: "Fade" }
18832
19190
  ];
18833
- var TREATMENTS = [
18834
- { value: "color", label: "Color" },
18835
- { value: "grayscale", label: "Grayscale" }
18836
- ];
18837
19191
  var HTTP_URL_RE = /^https?:\/\/\S+$/i;
18838
- function LinkInput({ value, onCommit }) {
18839
- const [draft, setDraft] = useState8(value);
19192
+ function LinkModal({
19193
+ href,
19194
+ linkTarget,
19195
+ onClose,
19196
+ onSave
19197
+ }) {
19198
+ const [draft, setDraft] = useState8(href);
19199
+ const [newTab, setNewTab] = useState8(linkTarget !== "same-tab");
18840
19200
  const [invalid, setInvalid] = useState8(false);
18841
- useEffect6(() => {
18842
- setDraft(value);
18843
- setInvalid(false);
18844
- }, [value]);
18845
- const commit = () => {
19201
+ const save = () => {
18846
19202
  const next = draft.trim();
18847
- if (next === value) return setInvalid(false);
18848
- if (next && !HTTP_URL_RE.test(next)) return setInvalid(true);
18849
- setInvalid(false);
18850
- onCommit(next);
18851
- };
18852
- return /* @__PURE__ */ jsxs11(
18853
- "div",
18854
- {
18855
- style: {
18856
- display: "flex",
18857
- alignItems: "center",
18858
- gap: 6,
18859
- height: 28,
18860
- padding: "0 12px",
18861
- borderRadius: 999,
18862
- background: "#ffffff",
18863
- width: "100%",
18864
- maxWidth: 260,
18865
- boxSizing: "border-box",
18866
- boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
18867
- },
18868
- children: [
18869
- /* @__PURE__ */ jsx20(
18870
- "span",
18871
- {
18872
- style: {
18873
- flex: "0 0 auto",
18874
- color: OHW_BLUE_DARK,
18875
- fontSize: 10,
18876
- fontWeight: 700,
18877
- textTransform: "uppercase",
18878
- letterSpacing: 0.4
18879
- },
18880
- children: "Link"
18881
- }
18882
- ),
18883
- /* @__PURE__ */ jsx20(
18884
- "input",
18885
- {
18886
- type: "text",
18887
- inputMode: "url",
18888
- value: draft,
18889
- onChange: (e) => setDraft(e.target.value),
18890
- onBlur: commit,
18891
- onKeyDown: (e) => {
18892
- if (e.key === "Enter") e.currentTarget.blur();
18893
- if (e.key === "Escape") {
18894
- setDraft(value);
18895
- setInvalid(false);
18896
- e.currentTarget.blur();
18897
- }
18898
- },
18899
- placeholder: "https://example.com",
18900
- "aria-label": "Image link URL",
18901
- "aria-invalid": invalid,
18902
- style: {
18903
- flex: 1,
18904
- minWidth: 0,
18905
- border: "none",
18906
- outline: "none",
18907
- background: "transparent",
18908
- fontSize: 12,
18909
- lineHeight: "16px",
18910
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
18911
- color: "#1f2937"
18912
- }
18913
- }
18914
- )
18915
- ]
19203
+ if (next && !HTTP_URL_RE.test(next)) {
19204
+ setInvalid(true);
19205
+ return;
18916
19206
  }
18917
- );
19207
+ onSave(next, newTab ? "new-tab" : "same-tab");
19208
+ onClose();
19209
+ };
19210
+ return /* @__PURE__ */ jsx21(Dialog2, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs12(DialogContent, { "data-ohw-link-modal-root": "", className: "max-w-[400px] gap-4 p-5", children: [
19211
+ /* @__PURE__ */ jsx21(DialogHeader, { children: /* @__PURE__ */ jsx21(DialogTitle, { className: "text-base", children: "Image link" }) }),
19212
+ /* @__PURE__ */ jsxs12("div", { className: "flex flex-col gap-3", children: [
19213
+ /* @__PURE__ */ jsx21(
19214
+ "input",
19215
+ {
19216
+ autoFocus: true,
19217
+ type: "text",
19218
+ inputMode: "url",
19219
+ value: draft,
19220
+ onChange: (e) => {
19221
+ setDraft(e.target.value);
19222
+ setInvalid(false);
19223
+ },
19224
+ onKeyDown: (e) => {
19225
+ if (e.key === "Enter") save();
19226
+ },
19227
+ placeholder: "https://example.com",
19228
+ "aria-label": "Image link URL",
19229
+ "aria-invalid": invalid,
19230
+ className: cn(
19231
+ "h-9 w-full rounded-[6px] border border-border bg-background px-3 text-[13px] text-foreground outline-none placeholder:text-muted-foreground/70 focus:border-bridge-primary",
19232
+ invalid && "border-destructive focus:border-destructive"
19233
+ )
19234
+ }
19235
+ ),
19236
+ invalid && /* @__PURE__ */ jsx21("p", { className: "text-xs text-destructive", children: "Enter a full URL starting with http:// or https://" }),
19237
+ /* @__PURE__ */ jsxs12("label", { className: "flex items-center gap-2 text-[13px] text-foreground", children: [
19238
+ /* @__PURE__ */ jsx21("input", { type: "checkbox", checked: newTab, onChange: (e) => setNewTab(e.target.checked) }),
19239
+ "Open in new tab"
19240
+ ] })
19241
+ ] }),
19242
+ /* @__PURE__ */ jsxs12(DialogFooter, { children: [
19243
+ href && /* @__PURE__ */ jsx21(
19244
+ "button",
19245
+ {
19246
+ type: "button",
19247
+ onClick: () => {
19248
+ onSave("", "new-tab");
19249
+ onClose();
19250
+ },
19251
+ className: "mr-auto rounded-[6px] px-3 py-2 text-[13px] font-medium text-destructive transition-colors hover:bg-muted",
19252
+ children: "Remove link"
19253
+ }
19254
+ ),
19255
+ /* @__PURE__ */ jsx21(
19256
+ "button",
19257
+ {
19258
+ type: "button",
19259
+ onClick: onClose,
19260
+ className: "rounded-[6px] px-3 py-2 text-[13px] font-medium text-foreground transition-colors hover:bg-muted",
19261
+ children: "Cancel"
19262
+ }
19263
+ ),
19264
+ /* @__PURE__ */ jsx21(
19265
+ "button",
19266
+ {
19267
+ type: "button",
19268
+ onClick: save,
19269
+ className: "rounded-[6px] bg-bridge-primary px-3 py-2 text-[13px] font-medium text-primary-foreground transition-opacity hover:opacity-90",
19270
+ children: "Save"
19271
+ }
19272
+ )
19273
+ ] })
19274
+ ] }) });
18918
19275
  }
18919
19276
  function MediaStripToolbar({
18920
19277
  entry,
18921
19278
  index,
18922
19279
  pending,
18923
19280
  error,
19281
+ itemEditOn,
19282
+ onToggleItemEdit,
18924
19283
  onSetVariant,
18925
19284
  onSetAlign,
18926
19285
  onSetHover,
18927
19286
  onSetTreatment,
18928
19287
  onResize,
18929
- onSetAlt,
18930
- onSetLink,
19288
+ onOpenLink,
18931
19289
  onAddItem,
18932
19290
  onRemoveItem,
18933
19291
  onChangeImage,
@@ -18936,81 +19294,102 @@ function MediaStripToolbar({
18936
19294
  }) {
18937
19295
  const items = mediaStripItems(entry.tree);
18938
19296
  const itemCount = items.length;
18939
- const currentAlt = items[index]?.alt ?? "";
18940
- const currentHref = items[index]?.href ?? "";
18941
- const currentTarget = items[index]?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
18942
19297
  const slots = findMediaStripSlots(entry.tree);
18943
19298
  const variant = slots?.variant === "reel" ? "reel" : "logos";
18944
19299
  const align = slots?.align === "left" || slots?.align === "right" ? slots.align : "center";
18945
19300
  const hover = slots?.hover === "none" || slots?.hover === "lift" || slots?.hover === "zoom" || slots?.hover === "fade" ? slots.hover : "lift";
18946
19301
  const treatment = slots?.treatment === "color" || slots?.treatment === "grayscale" ? slots.treatment : variant === "logos" ? "grayscale" : "color";
18947
19302
  const sizeValue = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
18948
- const isMobile = window.innerWidth <= 480;
18949
- const rowStyle = isMobile ? {
18950
- display: "flex",
18951
- gap: 6,
18952
- flexWrap: "nowrap",
18953
- justifyContent: "center",
18954
- overflowX: "auto",
18955
- WebkitOverflowScrolling: "touch",
18956
- width: "100%",
18957
- scrollbarWidth: "none"
18958
- } : { display: "flex", gap: 6, flexWrap: "nowrap", justifyContent: "center" };
18959
- return /* @__PURE__ */ jsxs11(
19303
+ const mobile = window.innerWidth <= 480;
19304
+ const gatedLabel = (label) => itemEditOn ? label : "Turn on the star to edit this image";
19305
+ return /* @__PURE__ */ jsx21(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsxs12(
18960
19306
  "div",
18961
19307
  {
18962
19308
  "data-ohw-bridge": "",
18963
19309
  "data-ohw-toolbar": "",
18964
19310
  onMouseDown: (e) => e.stopPropagation(),
18965
- style: {
18966
- display: "flex",
18967
- flexDirection: "column",
18968
- gap: 6,
18969
- alignItems: "center",
18970
- background: OHW_BLUE,
18971
- padding: 8,
18972
- borderRadius: 12,
18973
- boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
18974
- pointerEvents: pending ? "none" : "auto",
18975
- opacity: pending ? 0.6 : 1,
18976
- maxWidth: isMobile ? "calc(100vw - 24px)" : "none"
18977
- },
19311
+ className: cn("flex flex-col items-center gap-1.5 font-sans", pending && "pointer-events-none opacity-60"),
19312
+ style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
18978
19313
  children: [
18979
- /* @__PURE__ */ jsxs11("div", { style: rowStyle, children: [
18980
- VARIANTS.map((v) => /* @__PURE__ */ jsx20(Pill, { label: v.label, active: variant === v.value, onClick: () => onSetVariant(v.value) }, v.value)),
18981
- ALIGNS.map((a) => /* @__PURE__ */ jsx20(Pill, { label: a.label, active: align === a.value, onClick: () => onSetAlign(a.value) }, a.value))
18982
- ] }),
18983
- /* @__PURE__ */ jsxs11("div", { style: rowStyle, children: [
18984
- HOVERS.map((h) => /* @__PURE__ */ jsx20(Pill, { label: h.label, active: hover === h.value, onClick: () => onSetHover(h.value) }, h.value)),
18985
- TREATMENTS.map((t) => /* @__PURE__ */ jsx20(Pill, { label: t.label, active: treatment === t.value, onClick: () => onSetTreatment(t.value) }, t.value))
18986
- ] }),
18987
- /* @__PURE__ */ jsxs11("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
18988
- /* @__PURE__ */ jsx20(Pill, { label: "\u2212", onClick: () => onResize(-8) }),
18989
- /* @__PURE__ */ jsxs11("span", { style: { color: "#ffffff", fontSize: 12, minWidth: 34, textAlign: "center" }, children: [
18990
- sizeValue,
18991
- "px"
18992
- ] }),
18993
- /* @__PURE__ */ jsx20(Pill, { label: "+", onClick: () => onResize(8) })
18994
- ] }),
18995
- /* @__PURE__ */ jsx20("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ jsx20(AltInput, { value: currentAlt, onCommit: onSetAlt }) }),
18996
- /* @__PURE__ */ jsx20("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ jsx20(LinkInput, { value: currentHref, onCommit: (href) => onSetLink(href, currentTarget) }) }),
18997
- currentHref && /* @__PURE__ */ jsxs11("div", { style: rowStyle, children: [
18998
- /* @__PURE__ */ jsx20(Pill, { label: "New tab", active: currentTarget === "new-tab", onClick: () => onSetLink(currentHref, "new-tab") }),
18999
- /* @__PURE__ */ jsx20(Pill, { label: "Same tab", active: currentTarget === "same-tab", onClick: () => onSetLink(currentHref, "same-tab") })
19000
- ] }),
19001
- /* @__PURE__ */ jsxs11("div", { style: rowStyle, children: [
19002
- /* @__PURE__ */ jsx20(Pill, { label: "Change Image", onClick: onChangeImage }),
19003
- /* @__PURE__ */ jsx20(Pill, { label: "+ Item", onClick: onAddItem }),
19004
- /* @__PURE__ */ jsx20(Pill, { label: "\u2212 Item", onClick: onRemoveItem, disabled: itemCount <= 1 })
19005
- ] }),
19006
- /* @__PURE__ */ jsxs11("div", { style: rowStyle, children: [
19007
- /* @__PURE__ */ jsx20(Pill, { label: "\u2190 Move", onClick: onMoveLeft, disabled: index <= 0 }),
19008
- /* @__PURE__ */ jsx20(Pill, { label: "\u2192 Move", onClick: onMoveRight, disabled: index >= itemCount - 1 })
19009
- ] }),
19010
- error && /* @__PURE__ */ jsx20("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
19314
+ /* @__PURE__ */ jsxs12(
19315
+ "div",
19316
+ {
19317
+ className: cn(
19318
+ "flex items-center gap-1 rounded-[10px] border border-border bg-background p-1 whitespace-nowrap",
19319
+ "shadow-[0px_10px_15px_-3px_rgba(0,0,0,0.1),0px_4px_6px_-4px_rgba(0,0,0,0.1)]",
19320
+ mobile ? "flex-wrap justify-center" : "flex-nowrap"
19321
+ ),
19322
+ children: [
19323
+ /* @__PURE__ */ jsxs12(Tooltip, { children: [
19324
+ /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx21(
19325
+ CustomToolbarButton,
19326
+ {
19327
+ type: "button",
19328
+ "aria-label": itemEditOn ? "Done editing this image" : "Edit this image",
19329
+ "aria-pressed": itemEditOn,
19330
+ onClick: onToggleItemEdit,
19331
+ className: "group size-8",
19332
+ children: /* @__PURE__ */ jsx21(
19333
+ Star,
19334
+ {
19335
+ size: 16,
19336
+ fill: "currentColor",
19337
+ strokeWidth: 0,
19338
+ className: cn(
19339
+ "transition-colors",
19340
+ itemEditOn ? "text-[#f59e0b] group-hover:text-[#d97706]" : "text-[#d6d3d1] group-hover:text-[#fbbf24]"
19341
+ ),
19342
+ style: { pointerEvents: "none" }
19343
+ }
19344
+ )
19345
+ }
19346
+ ) }),
19347
+ /* @__PURE__ */ jsx21(TooltipContent, { side: "bottom", sideOffset: 8, children: itemEditOn ? "Done editing this image" : "Edit this image" })
19348
+ ] }),
19349
+ /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19350
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: gatedLabel("Change image"), onClick: onChangeImage, disabled: !itemEditOn, children: /* @__PURE__ */ jsx21(ImageIcon2, { size: 16, style: { pointerEvents: "none" } }) }),
19351
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: gatedLabel("Link"), onClick: onOpenLink, disabled: !itemEditOn, children: /* @__PURE__ */ jsx21(LinkIcon, { size: 16, style: { pointerEvents: "none" } }) }),
19352
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: gatedLabel("Move left"), onClick: onMoveLeft, disabled: !itemEditOn || index <= 0, children: /* @__PURE__ */ jsx21(ArrowLeft2, { size: 16, style: { pointerEvents: "none" } }) }),
19353
+ /* @__PURE__ */ jsx21(
19354
+ ToolbarAction,
19355
+ {
19356
+ label: gatedLabel("Move right"),
19357
+ onClick: onMoveRight,
19358
+ disabled: !itemEditOn || index >= itemCount - 1,
19359
+ children: /* @__PURE__ */ jsx21(ArrowRight2, { size: 16, style: { pointerEvents: "none" } })
19360
+ }
19361
+ ),
19362
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: gatedLabel("Remove image"), onClick: onRemoveItem, disabled: !itemEditOn || itemCount <= 1, children: /* @__PURE__ */ jsx21(ImageMinus, { size: 16, style: { pointerEvents: "none" } }) }),
19363
+ /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19364
+ /* @__PURE__ */ jsx21(OptionMenu, { label: "Layout", value: variant, options: VARIANTS, onChange: onSetVariant }),
19365
+ /* @__PURE__ */ jsx21(OptionMenu, { label: "Hover", value: hover, options: HOVERS, onChange: onSetHover }),
19366
+ /* @__PURE__ */ jsx21(
19367
+ ToolbarAction,
19368
+ {
19369
+ label: treatment === "grayscale" ? "Show in color" : "Show grayscale",
19370
+ active: treatment === "grayscale",
19371
+ onClick: () => onSetTreatment(treatment === "grayscale" ? "color" : "grayscale"),
19372
+ children: /* @__PURE__ */ jsx21(Palette, { size: 16, style: { pointerEvents: "none" } })
19373
+ }
19374
+ ),
19375
+ /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19376
+ ALIGNS.map(({ value, label, Icon }) => /* @__PURE__ */ jsx21(ToolbarAction, { label, active: align === value, onClick: () => onSetAlign(value), children: /* @__PURE__ */ jsx21(Icon, { size: 16, style: { pointerEvents: "none" } }) }, value)),
19377
+ /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19378
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: "Smaller", onClick: () => onResize(-8), children: /* @__PURE__ */ jsx21(Minus, { size: 16, style: { pointerEvents: "none" } }) }),
19379
+ /* @__PURE__ */ jsxs12("span", { className: "min-w-[40px] text-center text-xs tabular-nums text-muted-foreground", children: [
19380
+ sizeValue,
19381
+ "px"
19382
+ ] }),
19383
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: "Larger", onClick: () => onResize(8), children: /* @__PURE__ */ jsx21(Plus2, { size: 16, style: { pointerEvents: "none" } }) }),
19384
+ /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19385
+ /* @__PURE__ */ jsx21(ToolbarAction, { label: "Add image", onClick: onAddItem, children: /* @__PURE__ */ jsx21(ImagePlus, { size: 16, style: { pointerEvents: "none" } }) })
19386
+ ]
19387
+ }
19388
+ ),
19389
+ error && /* @__PURE__ */ jsx21("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19011
19390
  ]
19012
19391
  }
19013
- );
19392
+ ) });
19014
19393
  }
19015
19394
  function findMediaStripSlots(tree) {
19016
19395
  const stack = tree.rows.flatMap((row) => row.blocks);
@@ -19029,31 +19408,23 @@ function AiMediaStripOverlay({
19029
19408
  }) {
19030
19409
  const [hover, setHover] = useState8(null);
19031
19410
  const [active, setActive] = useState8(null);
19411
+ const [itemEditOn, setItemEditOn] = useState8(false);
19412
+ const [linkOpen, setLinkOpen] = useState8(false);
19032
19413
  const [pending, setPending] = useState8(false);
19033
19414
  const [error, setError] = useState8(null);
19034
19415
  const activeRef = useRef5(null);
19035
19416
  activeRef.current = active;
19417
+ const linkOpenRef = useRef5(false);
19418
+ linkOpenRef.current = linkOpen;
19036
19419
  useEffect6(() => {
19037
19420
  const onMove = (e) => {
19421
+ const ref = itemRefFromTarget(e.target);
19038
19422
  const t = e.target;
19039
- if (!(t instanceof HTMLElement)) return;
19040
- 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]")) {
19041
- return;
19042
- }
19043
- const itemEl = t.closest("[data-ohw-media-strip-item]");
19044
- if (!itemEl) {
19045
- setHover(null);
19046
- return;
19047
- }
19048
- const sectionEl = itemEl.closest("[data-ohw-section]");
19049
- const unit = sectionEl ? movableUnit(sectionEl) : null;
19050
- const sectionId = unit ? instanceIdOf(unit) || null : null;
19051
- const index = Number(itemEl.dataset.ohwMediaStripIndex);
19052
- if (!sectionId || Number.isNaN(index)) {
19053
- setHover(null);
19054
- return;
19055
- }
19056
- setHover({ sectionId, index });
19423
+ const overEditable = Boolean(t?.closest?.("[data-ohw-editable]"));
19424
+ setHover((prev) => {
19425
+ const next = ref && !overEditable ? ref : null;
19426
+ return sameRef(prev, next) ? prev : next;
19427
+ });
19057
19428
  };
19058
19429
  const onLeave = () => setHover(null);
19059
19430
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -19065,23 +19436,25 @@ function AiMediaStripOverlay({
19065
19436
  }, []);
19066
19437
  useEffect6(() => {
19067
19438
  const onClick = (e) => {
19068
- const current = activeRef.current;
19069
- if (!current) return;
19070
19439
  const t = e.target;
19071
- if (!(t instanceof HTMLElement)) return;
19072
- if (t.closest("[data-ohw-item-interaction]")) return;
19073
- const itemEl = findMediaStripItemElement(current.sectionId, current.index);
19074
- if (itemEl && itemEl.contains(t)) return;
19075
- setActive(null);
19440
+ if (!(t instanceof Element)) return;
19441
+ if (t.closest(CHROME_SELECTOR)) return;
19442
+ const ref = itemRefFromTarget(t);
19443
+ if (sameRef(activeRef.current, ref)) return;
19444
+ if (ref) clearInteractionChromeRef.current();
19445
+ setItemEditOn(false);
19076
19446
  setError(null);
19447
+ setActive(ref);
19077
19448
  };
19078
19449
  document.addEventListener("click", onClick, true);
19079
19450
  return () => document.removeEventListener("click", onClick, true);
19080
- }, []);
19451
+ }, [clearInteractionChromeRef]);
19081
19452
  useEffect6(() => {
19082
19453
  const onBlur = () => {
19083
19454
  if (!activeRef.current) return;
19084
19455
  setActive(null);
19456
+ setItemEditOn(false);
19457
+ setLinkOpen(false);
19085
19458
  setError(null);
19086
19459
  };
19087
19460
  window.addEventListener("blur", onBlur);
@@ -19091,7 +19464,10 @@ function AiMediaStripOverlay({
19091
19464
  const onKeyDown = (e) => {
19092
19465
  if (e.key !== "Escape") return;
19093
19466
  if (!activeRef.current) return;
19467
+ if (linkOpenRef.current) return;
19468
+ if (document.querySelector('[data-ohw-more-menu][data-state="open"]')) return;
19094
19469
  setActive(null);
19470
+ setItemEditOn(false);
19095
19471
  setError(null);
19096
19472
  };
19097
19473
  document.addEventListener("keydown", onKeyDown);
@@ -19125,137 +19501,133 @@ function AiMediaStripOverlay({
19125
19501
  tree: nextTree,
19126
19502
  label: entry.label,
19127
19503
  media: extraMedia ? { ...entry.media, ...extraMedia } : entry.media,
19128
- ...entry.brand ? { brand: entry.brand } : {}
19504
+ ...entry.brand ? { brand: entry.brand } : {},
19505
+ // The user is already looking at the strip they just edited — recentering the viewport
19506
+ // on every edit reads as the section jumping around.
19507
+ skipScrollIntoView: true
19129
19508
  });
19130
19509
  return true;
19131
19510
  },
19132
19511
  [findEntry, postToParent2, applyTreeLocalRef]
19133
19512
  );
19134
19513
  const activeEntry = active ? findEntry(active.sectionId) : null;
19135
- const hoverHandleRect = useLiveItemRect(!active && hover ? hover : null);
19136
19514
  const activeItemRect = useLiveItemRect(active);
19137
- const TOOLBAR_APPROX_H = 170;
19138
- const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
19139
- return /* @__PURE__ */ jsxs11(Fragment7, { children: [
19140
- hoverHandleRect && hover && /* @__PURE__ */ jsx20(
19141
- "button",
19142
- {
19143
- type: "button",
19144
- "data-ohw-toolbar": "",
19145
- onClick: () => {
19146
- clearInteractionChromeRef.current();
19147
- setActive(hover);
19148
- },
19149
- "aria-label": "Configure media item",
19150
- style: {
19151
- position: "fixed",
19152
- top: hoverHandleRect.top + 8,
19153
- left: hoverHandleRect.left + hoverHandleRect.width - 36,
19154
- width: 28,
19155
- height: 28,
19156
- borderRadius: 8,
19157
- border: `1.5px dashed ${PRIMARY3}`,
19158
- background: "#ffffff",
19159
- color: PRIMARY3,
19160
- display: "flex",
19161
- alignItems: "center",
19162
- justifyContent: "center",
19163
- cursor: "pointer",
19164
- zIndex: 2147483643,
19165
- fontSize: 14,
19166
- fontWeight: 700
19167
- },
19168
- children: /* @__PURE__ */ jsx20(Star, { size: 14, style: { pointerEvents: "none" } })
19169
- }
19170
- ),
19171
- activeItemRect && active && activeEntry && /* @__PURE__ */ jsx20(
19172
- ItemInteractionLayer,
19173
- {
19174
- rect: activeItemRect,
19175
- state: toolbarState,
19176
- toolbar: /* @__PURE__ */ jsx20(
19177
- MediaStripToolbar,
19178
- {
19179
- entry: activeEntry,
19180
- index: active.index,
19181
- pending,
19182
- error,
19183
- onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
19184
- onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
19185
- onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
19186
- onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
19187
- onResize: (delta) => {
19188
- const slots = findMediaStripSlots(activeEntry.tree);
19189
- const variant = slots?.variant === "reel" ? "reel" : "logos";
19190
- const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19191
- void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
19192
- },
19193
- onSetAlt: (alt) => {
19194
- void runMutation(active.sectionId, (tree) => setMediaStripItemAlt(tree, active.index, alt));
19195
- },
19196
- onSetLink: (href, linkTarget) => {
19515
+ const activeStripRect = useLiveItemRect(active, true);
19516
+ const hoverRect = useLiveItemRect(hover && !sameRef(hover, active) ? hover : null);
19517
+ const activeItems = activeEntry ? mediaStripItems(activeEntry.tree) : [];
19518
+ const activeItem = active ? activeItems[active.index] : void 0;
19519
+ const activeHref = activeItem?.href ?? "";
19520
+ const activeTarget = activeItem?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
19521
+ return /* @__PURE__ */ jsxs12(Fragment7, { children: [
19522
+ hoverRect && /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19523
+ activeItemRect && activeStripRect && active && activeEntry && /* @__PURE__ */ jsxs12(Fragment7, { children: [
19524
+ /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: activeItemRect, state: "active-top" }),
19525
+ /* @__PURE__ */ jsx21(StripToolbarSlot, { stripRect: activeStripRect, children: /* @__PURE__ */ jsx21(
19526
+ MediaStripToolbar,
19527
+ {
19528
+ entry: activeEntry,
19529
+ index: active.index,
19530
+ pending,
19531
+ error,
19532
+ itemEditOn,
19533
+ onToggleItemEdit: () => setItemEditOn((on) => !on),
19534
+ onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
19535
+ onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
19536
+ onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
19537
+ onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
19538
+ onResize: (delta) => {
19539
+ const slots = findMediaStripSlots(activeEntry.tree);
19540
+ const variant = slots?.variant === "reel" ? "reel" : "logos";
19541
+ const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19542
+ void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
19543
+ },
19544
+ onOpenLink: () => setLinkOpen(true),
19545
+ onAddItem: () => {
19546
+ void requestAssetPick(postToParent2).then((picked) => {
19547
+ if (!picked) return;
19548
+ const item = { assetId: picked.assetId, alt: "Logo" };
19197
19549
  void runMutation(
19198
19550
  active.sectionId,
19199
- (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
19200
- );
19201
- },
19202
- onAddItem: () => {
19203
- void requestAssetPick(postToParent2).then((picked) => {
19204
- if (!picked) return;
19205
- const item = { assetId: picked.assetId, alt: "Logo" };
19206
- void runMutation(
19207
- active.sectionId,
19208
- (tree) => addMediaStripItem(tree, item, active.index),
19209
- { [picked.assetId]: picked.url }
19210
- ).then((applied) => {
19211
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19212
- });
19551
+ (tree) => addMediaStripItem(tree, item, active.index),
19552
+ { [picked.assetId]: picked.url }
19553
+ ).then((applied) => {
19554
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19213
19555
  });
19214
- },
19215
- onChangeImage: () => {
19216
- void requestAssetPick(postToParent2).then((picked) => {
19217
- if (!picked) return;
19218
- void runMutation(
19219
- active.sectionId,
19220
- (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
19221
- { [picked.assetId]: picked.url }
19222
- );
19223
- });
19224
- },
19225
- onRemoveItem: () => {
19226
- void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
19227
- (applied) => {
19228
- if (applied) setActive(null);
19229
- }
19230
- );
19231
- },
19232
- onMoveLeft: () => {
19233
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
19234
- (applied) => {
19235
- if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
19236
- }
19237
- );
19238
- },
19239
- onMoveRight: () => {
19240
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
19241
- (applied) => {
19242
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19243
- }
19556
+ });
19557
+ },
19558
+ onChangeImage: () => {
19559
+ void requestAssetPick(postToParent2).then((picked) => {
19560
+ if (!picked) return;
19561
+ void runMutation(
19562
+ active.sectionId,
19563
+ (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
19564
+ { [picked.assetId]: picked.url }
19244
19565
  );
19245
- }
19566
+ });
19567
+ },
19568
+ onRemoveItem: () => {
19569
+ void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
19570
+ (applied) => {
19571
+ if (applied) setActive(null);
19572
+ }
19573
+ );
19574
+ },
19575
+ onMoveLeft: () => {
19576
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
19577
+ (applied) => {
19578
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
19579
+ }
19580
+ );
19581
+ },
19582
+ onMoveRight: () => {
19583
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
19584
+ (applied) => {
19585
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19586
+ }
19587
+ );
19246
19588
  }
19247
- )
19248
- }
19249
- )
19589
+ }
19590
+ ) }),
19591
+ /* @__PURE__ */ jsx21(
19592
+ "div",
19593
+ {
19594
+ "data-ohw-bridge": "",
19595
+ "data-ohw-item-interaction": "",
19596
+ "aria-hidden": true,
19597
+ className: "pointer-events-none fixed rounded-[4px] px-[7px] py-[3px] font-sans text-[11px] font-medium leading-none text-primary-foreground whitespace-nowrap",
19598
+ style: {
19599
+ top: activeItemRect.top + activeItemRect.height + 10,
19600
+ left: activeItemRect.left - 5,
19601
+ background: PRIMARY3,
19602
+ zIndex: 2147483644
19603
+ },
19604
+ children: "Media item"
19605
+ }
19606
+ ),
19607
+ linkOpen && /* @__PURE__ */ jsx21(
19608
+ LinkModal,
19609
+ {
19610
+ href: activeHref,
19611
+ linkTarget: activeTarget,
19612
+ onClose: () => setLinkOpen(false),
19613
+ onSave: (href, linkTarget) => {
19614
+ void runMutation(
19615
+ active.sectionId,
19616
+ (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
19617
+ );
19618
+ }
19619
+ }
19620
+ )
19621
+ ] })
19250
19622
  ] });
19251
19623
  }
19252
19624
 
19253
19625
  // src/ui/ai-section/AiPricingCardOverlay.tsx
19254
- import { useCallback as useCallback4, useEffect as useEffect7, useLayoutEffect as useLayoutEffect4, useRef as useRef6, useState as useState9 } from "react";
19255
- import { Check as Check2, ChevronDown as ChevronDown3, Minus, Plus as Plus2, Star as Star2 } from "lucide-react";
19256
- import { Fragment as Fragment8, jsx as jsx21, jsxs as jsxs12 } from "react/jsx-runtime";
19626
+ import { useCallback as useCallback4, useEffect as useEffect7, useLayoutEffect as useLayoutEffect5, useRef as useRef6, useState as useState9 } from "react";
19627
+ import { Check as Check3, ChevronDown as ChevronDown4, Minus as Minus2, Plus as Plus3, Star as Star2 } from "lucide-react";
19628
+ import { Fragment as Fragment8, jsx as jsx22, jsxs as jsxs13 } from "react/jsx-runtime";
19257
19629
  var PRIMARY4 = "var(--ohw-primary, #0885FE)";
19258
- var CHROME_SELECTOR = "[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-link-modal-root], [data-ohw-section-picker], [data-ohw-ai-review], [data-ohw-more-menu]";
19630
+ var CHROME_SELECTOR2 = "[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-link-modal-root], [data-ohw-section-picker], [data-ohw-ai-review], [data-ohw-more-menu]";
19259
19631
  function findPricingCardElement(sectionId, index) {
19260
19632
  const sectionEl = findByInstanceId(sectionId);
19261
19633
  if (!sectionEl) return null;
@@ -19267,7 +19639,7 @@ function findPricingCardElement(sectionId, index) {
19267
19639
  }
19268
19640
  function cardRefFromTarget(target) {
19269
19641
  if (!(target instanceof Element)) return null;
19270
- if (target.closest(CHROME_SELECTOR)) return null;
19642
+ if (target.closest(CHROME_SELECTOR2)) return null;
19271
19643
  const cardEl = target.closest("[data-ohw-pricing-card]");
19272
19644
  if (!cardEl) return null;
19273
19645
  const sectionEl = cardEl.closest("[data-ohw-section]");
@@ -19277,21 +19649,21 @@ function cardRefFromTarget(target) {
19277
19649
  if (!sectionId || Number.isNaN(index)) return null;
19278
19650
  return { sectionId, index };
19279
19651
  }
19280
- function sameRef(a, b) {
19652
+ function sameRef2(a, b) {
19281
19653
  return a?.sectionId === b?.sectionId && a?.index === b?.index;
19282
19654
  }
19283
- function rectOf(el) {
19655
+ function rectOf2(el) {
19284
19656
  if (!el) return null;
19285
19657
  const r2 = el.getBoundingClientRect();
19286
19658
  if (r2.width <= 0 || r2.height <= 0) return null;
19287
19659
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
19288
19660
  }
19289
19661
  function readRect3(ref) {
19290
- return rectOf(ref && findPricingCardElement(ref.sectionId, ref.index));
19662
+ return rectOf2(ref && findPricingCardElement(ref.sectionId, ref.index));
19291
19663
  }
19292
19664
  function readGridRect(ref) {
19293
19665
  const card = ref && findPricingCardElement(ref.sectionId, ref.index);
19294
- return rectOf(card?.closest("[data-ohw-pricing-collection]") ?? card);
19666
+ return rectOf2(card?.closest("[data-ohw-pricing-collection]") ?? card);
19295
19667
  }
19296
19668
  function useLiveCardRect(ref, grid = false) {
19297
19669
  const [rect, setRect] = useState9(null);
@@ -19321,19 +19693,19 @@ function useLiveCardRect(ref, grid = false) {
19321
19693
  }
19322
19694
  var TOOLBAR_GRID_GAP = 52;
19323
19695
  var TOOLBAR_GRID_GAP_MOBILE = 42;
19324
- var TOOLBAR_EDGE_MARGIN2 = 4;
19696
+ var TOOLBAR_EDGE_MARGIN3 = 4;
19325
19697
  function GridToolbarSlot({ gridRect, children }) {
19326
19698
  const slotRef = useRef6(null);
19327
19699
  const [shift, setShift] = useState9(0);
19328
19700
  const centerX = gridRect.left + gridRect.width / 2;
19329
- useLayoutEffect4(() => {
19701
+ useLayoutEffect5(() => {
19330
19702
  const slot = slotRef.current;
19331
19703
  if (!slot) return;
19332
19704
  const clamp = () => {
19333
19705
  const w = slot.offsetWidth;
19334
19706
  if (w <= 0) return;
19335
19707
  const half = w / 2;
19336
- const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
19708
+ const clamped = Math.max(TOOLBAR_EDGE_MARGIN3 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half));
19337
19709
  setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
19338
19710
  };
19339
19711
  clamp();
@@ -19342,7 +19714,7 @@ function GridToolbarSlot({ gridRect, children }) {
19342
19714
  return () => ro.disconnect();
19343
19715
  }, [centerX, children]);
19344
19716
  const mobile = window.innerWidth <= 480;
19345
- return /* @__PURE__ */ jsx21(
19717
+ return /* @__PURE__ */ jsx22(
19346
19718
  "div",
19347
19719
  {
19348
19720
  ref: slotRef,
@@ -19377,7 +19749,7 @@ function requestValidateTree2(postToParent2, tree) {
19377
19749
  });
19378
19750
  }
19379
19751
  function StyleSwatch({ style }) {
19380
- return /* @__PURE__ */ jsx21(
19752
+ return /* @__PURE__ */ jsx22(
19381
19753
  "span",
19382
19754
  {
19383
19755
  "aria-hidden": true,
@@ -19391,35 +19763,35 @@ function StyleSwatch({ style }) {
19391
19763
  );
19392
19764
  }
19393
19765
  function ButtonGlyph() {
19394
- return /* @__PURE__ */ jsxs12("svg", { width: "18", height: "12", viewBox: "0 0 18 12", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19395
- /* @__PURE__ */ jsx21("rect", { x: "0.75", y: "0.75", width: "16.5", height: "10.5", rx: "3", stroke: "currentColor", strokeWidth: "1.5" }),
19396
- /* @__PURE__ */ jsx21("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
19766
+ return /* @__PURE__ */ jsxs13("svg", { width: "18", height: "12", viewBox: "0 0 18 12", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19767
+ /* @__PURE__ */ jsx22("rect", { x: "0.75", y: "0.75", width: "16.5", height: "10.5", rx: "3", stroke: "currentColor", strokeWidth: "1.5" }),
19768
+ /* @__PURE__ */ jsx22("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
19397
19769
  ] });
19398
19770
  }
19399
19771
  function FeatureGlyph({ mode }) {
19400
- return /* @__PURE__ */ jsxs12("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19401
- /* @__PURE__ */ jsx21("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
19402
- /* @__PURE__ */ jsx21("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
19403
- /* @__PURE__ */ jsx21("circle", { cx: "13.5", cy: "13.5", r: "4.25", fill: "var(--ohw-background, #ffffff)", stroke: "var(--ohw-background, #ffffff)", strokeWidth: "1" }),
19404
- /* @__PURE__ */ jsx21("circle", { cx: "13.5", cy: "13.5", r: "3.75", fill: "var(--ohw-background, #ffffff)", stroke: PRIMARY4, strokeWidth: "1" }),
19405
- /* @__PURE__ */ jsx21("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
19406
- mode === "add" && /* @__PURE__ */ jsx21("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
19772
+ return /* @__PURE__ */ jsxs13("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19773
+ /* @__PURE__ */ jsx22("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
19774
+ /* @__PURE__ */ jsx22("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
19775
+ /* @__PURE__ */ jsx22("circle", { cx: "13.5", cy: "13.5", r: "4.25", fill: "var(--ohw-background, #ffffff)", stroke: "var(--ohw-background, #ffffff)", strokeWidth: "1" }),
19776
+ /* @__PURE__ */ jsx22("circle", { cx: "13.5", cy: "13.5", r: "3.75", fill: "var(--ohw-background, #ffffff)", stroke: PRIMARY4, strokeWidth: "1" }),
19777
+ /* @__PURE__ */ jsx22("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
19778
+ mode === "add" && /* @__PURE__ */ jsx22("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
19407
19779
  ] });
19408
19780
  }
19409
- function ToolbarAction({
19781
+ function ToolbarAction2({
19410
19782
  label,
19411
19783
  onClick,
19412
19784
  disabled = false,
19413
19785
  children
19414
19786
  }) {
19415
- const button = /* @__PURE__ */ jsx21(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
19416
- return /* @__PURE__ */ jsxs12(Tooltip, { children: [
19417
- /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ jsx21("span", { className: "inline-flex", children: button }) : button }),
19418
- /* @__PURE__ */ jsx21(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19787
+ const button = /* @__PURE__ */ jsx22(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
19788
+ return /* @__PURE__ */ jsxs13(Tooltip, { children: [
19789
+ /* @__PURE__ */ jsx22(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ jsx22("span", { className: "inline-flex", children: button }) : button }),
19790
+ /* @__PURE__ */ jsx22(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19419
19791
  ] });
19420
19792
  }
19421
- function ToolbarSeparator() {
19422
- return /* @__PURE__ */ jsx21("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19793
+ function ToolbarSeparator2() {
19794
+ return /* @__PURE__ */ jsx22("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19423
19795
  }
19424
19796
  function CardStyleMenu({
19425
19797
  value,
@@ -19427,7 +19799,7 @@ function CardStyleMenu({
19427
19799
  disabled = false
19428
19800
  }) {
19429
19801
  const current = PRICING_CARD_STYLES.find((s) => s.value === value) ?? PRICING_CARD_STYLES[0];
19430
- const trigger = /* @__PURE__ */ jsxs12(
19802
+ const trigger = /* @__PURE__ */ jsxs13(
19431
19803
  "button",
19432
19804
  {
19433
19805
  type: "button",
@@ -19438,28 +19810,28 @@ function CardStyleMenu({
19438
19810
  disabled && "cursor-not-allowed opacity-40 hover:bg-transparent"
19439
19811
  ),
19440
19812
  children: [
19441
- /* @__PURE__ */ jsx21(StyleSwatch, { style: current.value }),
19442
- /* @__PURE__ */ jsx21("span", { className: "flex-1 text-left", children: current.label }),
19443
- /* @__PURE__ */ jsx21(ChevronDown3, { size: 16, style: { pointerEvents: "none" } })
19813
+ /* @__PURE__ */ jsx22(StyleSwatch, { style: current.value }),
19814
+ /* @__PURE__ */ jsx22("span", { className: "flex-1 text-left", children: current.label }),
19815
+ /* @__PURE__ */ jsx22(ChevronDown4, { size: 16, style: { pointerEvents: "none" } })
19444
19816
  ]
19445
19817
  }
19446
19818
  );
19447
19819
  if (disabled) {
19448
- return /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19449
- /* @__PURE__ */ jsx21("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
19450
- /* @__PURE__ */ jsxs12(Tooltip, { children: [
19451
- /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx21("span", { className: "inline-flex", children: trigger }) }),
19452
- /* @__PURE__ */ jsx21(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
19820
+ return /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19821
+ /* @__PURE__ */ jsx22("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
19822
+ /* @__PURE__ */ jsxs13(Tooltip, { children: [
19823
+ /* @__PURE__ */ jsx22(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx22("span", { className: "inline-flex", children: trigger }) }),
19824
+ /* @__PURE__ */ jsx22(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
19453
19825
  ] })
19454
19826
  ] });
19455
19827
  }
19456
- return /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19457
- /* @__PURE__ */ jsx21("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
19458
- /* @__PURE__ */ jsxs12(DropdownMenu, { modal: false, children: [
19459
- /* @__PURE__ */ jsx21(DropdownMenuTrigger, { asChild: true, children: trigger }),
19460
- /* @__PURE__ */ jsx21(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[152px] min-w-[152px] p-1", children: PRICING_CARD_STYLES.map((style) => {
19828
+ return /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19829
+ /* @__PURE__ */ jsx22("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
19830
+ /* @__PURE__ */ jsxs13(DropdownMenu, { modal: false, children: [
19831
+ /* @__PURE__ */ jsx22(DropdownMenuTrigger, { asChild: true, children: trigger }),
19832
+ /* @__PURE__ */ jsx22(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[152px] min-w-[152px] p-1", children: PRICING_CARD_STYLES.map((style) => {
19461
19833
  const selected = style.value === value;
19462
- return /* @__PURE__ */ jsxs12(
19834
+ return /* @__PURE__ */ jsxs13(
19463
19835
  DropdownMenuItem,
19464
19836
  {
19465
19837
  role: "menuitemradio",
@@ -19467,9 +19839,9 @@ function CardStyleMenu({
19467
19839
  onSelect: () => onChange(style.value),
19468
19840
  className: cn("grid grid-cols-[18px_1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
19469
19841
  children: [
19470
- /* @__PURE__ */ jsx21(StyleSwatch, { style: style.value }),
19471
- /* @__PURE__ */ jsx21("span", { children: style.label }),
19472
- selected && /* @__PURE__ */ jsx21(Check2, { size: 16, className: "text-bridge-primary" })
19842
+ /* @__PURE__ */ jsx22(StyleSwatch, { style: style.value }),
19843
+ /* @__PURE__ */ jsx22("span", { children: style.label }),
19844
+ selected && /* @__PURE__ */ jsx22(Check3, { size: 16, className: "text-bridge-primary" })
19473
19845
  ]
19474
19846
  },
19475
19847
  style.value
@@ -19499,7 +19871,7 @@ function PricingCardToolbar({
19499
19871
  const style = pricingCardStyle(entry.tree, index);
19500
19872
  const featuredTag = pricingCardFeaturedTag(entry.tree, index);
19501
19873
  const mobile = window.innerWidth <= 480;
19502
- return /* @__PURE__ */ jsx21(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsxs12(
19874
+ return /* @__PURE__ */ jsx22(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsxs13(
19503
19875
  "div",
19504
19876
  {
19505
19877
  "data-ohw-bridge": "",
@@ -19511,7 +19883,7 @@ function PricingCardToolbar({
19511
19883
  ),
19512
19884
  style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
19513
19885
  children: [
19514
- /* @__PURE__ */ jsxs12(
19886
+ /* @__PURE__ */ jsxs13(
19515
19887
  "div",
19516
19888
  {
19517
19889
  className: cn(
@@ -19520,8 +19892,8 @@ function PricingCardToolbar({
19520
19892
  mobile ? "flex-wrap justify-center" : "flex-nowrap"
19521
19893
  ),
19522
19894
  children: [
19523
- /* @__PURE__ */ jsxs12(Tooltip, { children: [
19524
- /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx21(
19895
+ /* @__PURE__ */ jsxs13(Tooltip, { children: [
19896
+ /* @__PURE__ */ jsx22(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx22(
19525
19897
  CustomToolbarButton,
19526
19898
  {
19527
19899
  type: "button",
@@ -19529,7 +19901,7 @@ function PricingCardToolbar({
19529
19901
  "aria-pressed": featuredTag,
19530
19902
  onClick: () => onSetFeaturedTag(!featuredTag),
19531
19903
  className: "group size-8",
19532
- children: /* @__PURE__ */ jsx21(
19904
+ children: /* @__PURE__ */ jsx22(
19533
19905
  Star2,
19534
19906
  {
19535
19907
  size: 16,
@@ -19544,25 +19916,25 @@ function PricingCardToolbar({
19544
19916
  )
19545
19917
  }
19546
19918
  ) }),
19547
- /* @__PURE__ */ jsx21(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
19919
+ /* @__PURE__ */ jsx22(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
19548
19920
  ] }),
19549
- /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19550
- /* @__PURE__ */ jsx21(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
19551
- /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19552
- /* @__PURE__ */ jsx21(ToolbarAction, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ jsx21(ButtonGlyph, {}) }),
19553
- /* @__PURE__ */ jsx21(ToolbarAction, { label: "Remove button", onClick: onRemoveButton, disabled: buttonCount === 0, children: /* @__PURE__ */ jsxs12("span", { className: "relative inline-flex", children: [
19554
- /* @__PURE__ */ jsx21(ButtonGlyph, {}),
19555
- /* @__PURE__ */ jsx21(Minus, { size: 10, strokeWidth: 2.5, className: "absolute -right-1.5 -top-1.5 rounded-full bg-background text-bridge-primary" })
19921
+ /* @__PURE__ */ jsx22(ToolbarSeparator2, {}),
19922
+ /* @__PURE__ */ jsx22(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
19923
+ /* @__PURE__ */ jsx22(ToolbarSeparator2, {}),
19924
+ /* @__PURE__ */ jsx22(ToolbarAction2, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ jsx22(ButtonGlyph, {}) }),
19925
+ /* @__PURE__ */ jsx22(ToolbarAction2, { label: "Remove button", onClick: onRemoveButton, disabled: buttonCount === 0, children: /* @__PURE__ */ jsxs13("span", { className: "relative inline-flex", children: [
19926
+ /* @__PURE__ */ jsx22(ButtonGlyph, {}),
19927
+ /* @__PURE__ */ jsx22(Minus2, { size: 10, strokeWidth: 2.5, className: "absolute -right-1.5 -top-1.5 rounded-full bg-background text-bridge-primary" })
19556
19928
  ] }) }),
19557
- /* @__PURE__ */ jsx21(ToolbarAction, { label: "Add feature", onClick: onAddFeature, disabled: featureCount >= 10, children: /* @__PURE__ */ jsx21(FeatureGlyph, { mode: "add" }) }),
19558
- /* @__PURE__ */ jsx21(ToolbarAction, { label: "Remove feature", onClick: onRemoveFeature, disabled: featureCount <= 1, children: /* @__PURE__ */ jsx21(FeatureGlyph, { mode: "remove" }) }),
19559
- /* @__PURE__ */ jsx21(ToolbarSeparator, {}),
19560
- /* @__PURE__ */ jsx21(ToolbarAction, { label: "Add tier", onClick: onAddTier, disabled: collectionCount >= 4, children: /* @__PURE__ */ jsx21(Plus2, { size: 16, style: { pointerEvents: "none" } }) }),
19561
- /* @__PURE__ */ jsx21(ToolbarAction, { label: "Remove tier", onClick: onRemoveTier, disabled: collectionCount <= 1, children: /* @__PURE__ */ jsx21(Minus, { size: 16, style: { pointerEvents: "none" } }) })
19929
+ /* @__PURE__ */ jsx22(ToolbarAction2, { label: "Add feature", onClick: onAddFeature, disabled: featureCount >= 10, children: /* @__PURE__ */ jsx22(FeatureGlyph, { mode: "add" }) }),
19930
+ /* @__PURE__ */ jsx22(ToolbarAction2, { label: "Remove feature", onClick: onRemoveFeature, disabled: featureCount <= 1, children: /* @__PURE__ */ jsx22(FeatureGlyph, { mode: "remove" }) }),
19931
+ /* @__PURE__ */ jsx22(ToolbarSeparator2, {}),
19932
+ /* @__PURE__ */ jsx22(ToolbarAction2, { label: "Add tier", onClick: onAddTier, disabled: collectionCount >= 4, children: /* @__PURE__ */ jsx22(Plus3, { size: 16, style: { pointerEvents: "none" } }) }),
19933
+ /* @__PURE__ */ jsx22(ToolbarAction2, { label: "Remove tier", onClick: onRemoveTier, disabled: collectionCount <= 1, children: /* @__PURE__ */ jsx22(Minus2, { size: 16, style: { pointerEvents: "none" } }) })
19562
19934
  ]
19563
19935
  }
19564
19936
  ),
19565
- error && /* @__PURE__ */ jsx21("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19937
+ error && /* @__PURE__ */ jsx22("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19566
19938
  ]
19567
19939
  }
19568
19940
  ) });
@@ -19596,7 +19968,7 @@ function AiPricingCardOverlay({
19596
19968
  const overEditable = Boolean(t?.closest?.("[data-ohw-editable], [data-ohw-role], a, button"));
19597
19969
  setHover((prev) => {
19598
19970
  const next = ref && !overEditable ? ref : null;
19599
- return sameRef(prev, next) ? prev : next;
19971
+ return sameRef2(prev, next) ? prev : next;
19600
19972
  });
19601
19973
  };
19602
19974
  const onLeave = () => setHover(null);
@@ -19611,10 +19983,10 @@ function AiPricingCardOverlay({
19611
19983
  const onClick = (e) => {
19612
19984
  const t = e.target;
19613
19985
  if (!(t instanceof Element)) return;
19614
- if (t.closest(CHROME_SELECTOR)) return;
19986
+ if (t.closest(CHROME_SELECTOR2)) return;
19615
19987
  const ref = cardRefFromTarget(t);
19616
- setActive((prev) => sameRef(prev, ref) ? prev : ref);
19617
- if (!ref || !sameRef(activeRef.current, ref)) setError(null);
19988
+ setActive((prev) => sameRef2(prev, ref) ? prev : ref);
19989
+ if (!ref || !sameRef2(activeRef.current, ref)) setError(null);
19618
19990
  };
19619
19991
  document.addEventListener("click", onClick, true);
19620
19992
  return () => document.removeEventListener("click", onClick, true);
@@ -19679,12 +20051,12 @@ function AiPricingCardOverlay({
19679
20051
  const activeEntry = active ? findEntry(active.sectionId) : null;
19680
20052
  const activeCardRect = useLiveCardRect(active);
19681
20053
  const activeGridRect = useLiveCardRect(active, true);
19682
- const hoverRect = useLiveCardRect(hover && !sameRef(hover, active) ? hover : null);
19683
- return /* @__PURE__ */ jsxs12(Fragment8, { children: [
19684
- hoverRect && /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19685
- activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ jsxs12(Fragment8, { children: [
19686
- /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
19687
- /* @__PURE__ */ jsx21(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ jsx21(
20054
+ const hoverRect = useLiveCardRect(hover && !sameRef2(hover, active) ? hover : null);
20055
+ return /* @__PURE__ */ jsxs13(Fragment8, { children: [
20056
+ hoverRect && /* @__PURE__ */ jsx22(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
20057
+ activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ jsxs13(Fragment8, { children: [
20058
+ /* @__PURE__ */ jsx22(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
20059
+ /* @__PURE__ */ jsx22(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ jsx22(
19688
20060
  PricingCardToolbar,
19689
20061
  {
19690
20062
  entry: activeEntry,
@@ -19712,7 +20084,7 @@ function AiPricingCardOverlay({
19712
20084
  onRemoveFeature: () => runMutation(active.sectionId, (tree) => removePricingCardFeature(tree, active.index))
19713
20085
  }
19714
20086
  ) }),
19715
- /* @__PURE__ */ jsx21(
20087
+ /* @__PURE__ */ jsx22(
19716
20088
  "div",
19717
20089
  {
19718
20090
  "data-ohw-bridge": "",
@@ -19805,10 +20177,10 @@ function extractYouTubeId(input) {
19805
20177
  }
19806
20178
 
19807
20179
  // src/ui/ai-section/AiVideoOverlay.tsx
19808
- import { Fragment as Fragment9, jsx as jsx22, jsxs as jsxs13 } from "react/jsx-runtime";
20180
+ import { Fragment as Fragment9, jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
19809
20181
  var PRIMARY5 = "#0885FE";
19810
- var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY5})`;
19811
- var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
20182
+ var OHW_BLUE = `var(--ohw-primary, ${PRIMARY5})`;
20183
+ var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
19812
20184
  function findVideoBlockElement(sectionId, path) {
19813
20185
  const sectionEl = findByInstanceId(sectionId);
19814
20186
  if (!sectionEl) return null;
@@ -19868,12 +20240,12 @@ function requestValidateTree3(postToParent2, tree) {
19868
20240
  postToParent2({ type: "ow:ai-validate-tree-request", id, tree });
19869
20241
  });
19870
20242
  }
19871
- function Pill2({
20243
+ function Pill({
19872
20244
  label,
19873
20245
  onClick,
19874
20246
  disabled
19875
20247
  }) {
19876
- return /* @__PURE__ */ jsx22(
20248
+ return /* @__PURE__ */ jsx23(
19877
20249
  "button",
19878
20250
  {
19879
20251
  type: "button",
@@ -19889,7 +20261,7 @@ function Pill2({
19889
20261
  border: "none",
19890
20262
  cursor: disabled ? "default" : "pointer",
19891
20263
  background: "#ffffff",
19892
- color: OHW_BLUE_DARK2,
20264
+ color: OHW_BLUE_DARK,
19893
20265
  opacity: disabled ? 0.4 : 1,
19894
20266
  fontSize: 12,
19895
20267
  lineHeight: "16px",
@@ -19911,7 +20283,7 @@ function VideoUrlInput({ value, onCommit }) {
19911
20283
  setDraft(value ? watchUrl(value) : "");
19912
20284
  setInvalid(false);
19913
20285
  }, [value]);
19914
- const commit = () => {
20286
+ const commit2 = () => {
19915
20287
  const next = draft.trim();
19916
20288
  if (!next) {
19917
20289
  setInvalid(false);
@@ -19932,8 +20304,8 @@ function VideoUrlInput({ value, onCommit }) {
19932
20304
  onCommit(id);
19933
20305
  }
19934
20306
  };
19935
- return /* @__PURE__ */ jsxs13("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
19936
- /* @__PURE__ */ jsxs13(
20307
+ return /* @__PURE__ */ jsxs14("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
20308
+ /* @__PURE__ */ jsxs14(
19937
20309
  "div",
19938
20310
  {
19939
20311
  style: {
@@ -19949,12 +20321,12 @@ function VideoUrlInput({ value, onCommit }) {
19949
20321
  boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
19950
20322
  },
19951
20323
  children: [
19952
- /* @__PURE__ */ jsx22(
20324
+ /* @__PURE__ */ jsx23(
19953
20325
  "span",
19954
20326
  {
19955
20327
  style: {
19956
20328
  flex: "0 0 auto",
19957
- color: OHW_BLUE_DARK2,
20329
+ color: OHW_BLUE_DARK,
19958
20330
  fontSize: 10,
19959
20331
  lineHeight: "16px",
19960
20332
  fontWeight: 700,
@@ -19964,14 +20336,14 @@ function VideoUrlInput({ value, onCommit }) {
19964
20336
  children: "Video"
19965
20337
  }
19966
20338
  ),
19967
- /* @__PURE__ */ jsx22(
20339
+ /* @__PURE__ */ jsx23(
19968
20340
  "input",
19969
20341
  {
19970
20342
  type: "text",
19971
20343
  inputMode: "url",
19972
20344
  value: draft,
19973
20345
  onChange: (e) => handleChange(e.target.value),
19974
- onBlur: commit,
20346
+ onBlur: commit2,
19975
20347
  onKeyDown: (e) => {
19976
20348
  if (e.key === "Enter") e.currentTarget.blur();
19977
20349
  if (e.key === "Escape") {
@@ -20001,7 +20373,7 @@ function VideoUrlInput({ value, onCommit }) {
20001
20373
  ]
20002
20374
  }
20003
20375
  ),
20004
- invalid && /* @__PURE__ */ jsx22("div", { style: { color: "#FCA5A5", fontSize: 11, textAlign: "center" }, children: "That doesn't look like a YouTube link \u2014 try pasting the video URL again." })
20376
+ invalid && /* @__PURE__ */ jsx23("div", { style: { color: "#FCA5A5", fontSize: 11, textAlign: "center" }, children: "That doesn't look like a YouTube link \u2014 try pasting the video URL again." })
20005
20377
  ] });
20006
20378
  }
20007
20379
  function VideoToolbar({
@@ -20011,7 +20383,7 @@ function VideoToolbar({
20011
20383
  onSetVideoId,
20012
20384
  onRemove
20013
20385
  }) {
20014
- return /* @__PURE__ */ jsxs13(
20386
+ return /* @__PURE__ */ jsxs14(
20015
20387
  "div",
20016
20388
  {
20017
20389
  "data-ohw-bridge": "",
@@ -20023,7 +20395,7 @@ function VideoToolbar({
20023
20395
  gap: 6,
20024
20396
  alignItems: "center",
20025
20397
  width: 280,
20026
- background: OHW_BLUE2,
20398
+ background: OHW_BLUE,
20027
20399
  padding: 8,
20028
20400
  borderRadius: 12,
20029
20401
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -20031,9 +20403,9 @@ function VideoToolbar({
20031
20403
  opacity: pending ? 0.6 : 1
20032
20404
  },
20033
20405
  children: [
20034
- /* @__PURE__ */ jsx22(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
20035
- /* @__PURE__ */ jsx22(Pill2, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
20036
- error && /* @__PURE__ */ jsx22("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20406
+ /* @__PURE__ */ jsx23(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
20407
+ /* @__PURE__ */ jsx23(Pill, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
20408
+ error && /* @__PURE__ */ jsx23("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20037
20409
  ]
20038
20410
  }
20039
20411
  );
@@ -20153,8 +20525,8 @@ function AiVideoOverlay({
20153
20525
  const activeBlockRect = useLiveBlockRect(active);
20154
20526
  const TOOLBAR_APPROX_H = 90;
20155
20527
  const toolbarState = activeBlockRect && activeBlockRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
20156
- return /* @__PURE__ */ jsxs13(Fragment9, { children: [
20157
- hoverHandleRect && hover && /* @__PURE__ */ jsx22(
20528
+ return /* @__PURE__ */ jsxs14(Fragment9, { children: [
20529
+ hoverHandleRect && hover && /* @__PURE__ */ jsx23(
20158
20530
  "button",
20159
20531
  {
20160
20532
  type: "button",
@@ -20182,15 +20554,15 @@ function AiVideoOverlay({
20182
20554
  fontSize: 14,
20183
20555
  fontWeight: 700
20184
20556
  },
20185
- children: /* @__PURE__ */ jsx22(Star3, { size: 14, style: { pointerEvents: "none" } })
20557
+ children: /* @__PURE__ */ jsx23(Star3, { size: 14, style: { pointerEvents: "none" } })
20186
20558
  }
20187
20559
  ),
20188
- activeBlockRect && active && activeEntry && /* @__PURE__ */ jsx22(
20560
+ activeBlockRect && active && activeEntry && /* @__PURE__ */ jsx23(
20189
20561
  ItemInteractionLayer,
20190
20562
  {
20191
20563
  rect: activeBlockRect,
20192
20564
  state: toolbarState,
20193
- toolbar: /* @__PURE__ */ jsx22(
20565
+ toolbar: /* @__PURE__ */ jsx23(
20194
20566
  VideoToolbar,
20195
20567
  {
20196
20568
  videoId: videoIdAt(activeEntry.tree, active.path),
@@ -20265,10 +20637,10 @@ function setCarouselAutoplay(tree, on) {
20265
20637
  }
20266
20638
 
20267
20639
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
20268
- import { Fragment as Fragment10, jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
20640
+ import { Fragment as Fragment10, jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
20269
20641
  var PRIMARY6 = "#0885FE";
20270
- var OHW_BLUE3 = `var(--ohw-primary, ${PRIMARY6})`;
20271
- var OHW_BLUE_DARK3 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
20642
+ var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY6})`;
20643
+ var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
20272
20644
  function findCarouselItemElement(sectionId, index) {
20273
20645
  const sectionEl = findByInstanceId(sectionId);
20274
20646
  if (!sectionEl) return null;
@@ -20347,8 +20719,8 @@ function requestAssetPick2(postToParent2) {
20347
20719
  postToParent2({ type: "ow:asset-pick", id });
20348
20720
  });
20349
20721
  }
20350
- function Pill3({ label, onClick, disabled }) {
20351
- return /* @__PURE__ */ jsx23(
20722
+ function Pill2({ label, onClick, disabled }) {
20723
+ return /* @__PURE__ */ jsx24(
20352
20724
  "button",
20353
20725
  {
20354
20726
  type: "button",
@@ -20364,7 +20736,7 @@ function Pill3({ label, onClick, disabled }) {
20364
20736
  border: "none",
20365
20737
  cursor: disabled ? "default" : "pointer",
20366
20738
  background: "#ffffff",
20367
- color: OHW_BLUE_DARK3,
20739
+ color: OHW_BLUE_DARK2,
20368
20740
  opacity: disabled ? 0.4 : 1,
20369
20741
  fontSize: 12,
20370
20742
  lineHeight: "16px",
@@ -20382,7 +20754,7 @@ function TogglePill({
20382
20754
  onToggle,
20383
20755
  disabled
20384
20756
  }) {
20385
- return /* @__PURE__ */ jsxs14(
20757
+ return /* @__PURE__ */ jsxs15(
20386
20758
  "button",
20387
20759
  {
20388
20760
  type: "button",
@@ -20401,7 +20773,7 @@ function TogglePill({
20401
20773
  border: "none",
20402
20774
  cursor: disabled ? "default" : "pointer",
20403
20775
  background: "#ffffff",
20404
- color: OHW_BLUE_DARK3,
20776
+ color: OHW_BLUE_DARK2,
20405
20777
  opacity: disabled ? 0.4 : 1,
20406
20778
  fontSize: 12,
20407
20779
  lineHeight: "16px",
@@ -20411,7 +20783,7 @@ function TogglePill({
20411
20783
  },
20412
20784
  children: [
20413
20785
  label,
20414
- /* @__PURE__ */ jsx23(
20786
+ /* @__PURE__ */ jsx24(
20415
20787
  "span",
20416
20788
  {
20417
20789
  "aria-hidden": true,
@@ -20424,10 +20796,10 @@ function TogglePill({
20424
20796
  // Off is a grey track, not a washed-out blue: the pill's own white background already
20425
20797
  // carries the brand colour in the text, and a pale blue track next to it read as
20426
20798
  // "disabled control" rather than "switch that is off".
20427
- background: on ? OHW_BLUE_DARK3 : "#CBD5E1",
20799
+ background: on ? OHW_BLUE_DARK2 : "#CBD5E1",
20428
20800
  transition: "background 120ms ease"
20429
20801
  },
20430
- children: /* @__PURE__ */ jsx23(
20802
+ children: /* @__PURE__ */ jsx24(
20431
20803
  "span",
20432
20804
  {
20433
20805
  style: {
@@ -20456,7 +20828,7 @@ function CarouselSlideToolbar({
20456
20828
  onChangeImage,
20457
20829
  onToggleAutoplay
20458
20830
  }) {
20459
- return /* @__PURE__ */ jsxs14(
20831
+ return /* @__PURE__ */ jsxs15(
20460
20832
  "div",
20461
20833
  {
20462
20834
  "data-ohw-bridge": "",
@@ -20467,7 +20839,7 @@ function CarouselSlideToolbar({
20467
20839
  flexDirection: "column",
20468
20840
  gap: 6,
20469
20841
  alignItems: "center",
20470
- background: OHW_BLUE3,
20842
+ background: OHW_BLUE2,
20471
20843
  padding: 8,
20472
20844
  borderRadius: 12,
20473
20845
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -20479,11 +20851,11 @@ function CarouselSlideToolbar({
20479
20851
  pointerEvents: pending ? "none" : "auto"
20480
20852
  },
20481
20853
  children: [
20482
- /* @__PURE__ */ jsxs14("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20483
- /* @__PURE__ */ jsx23(Pill3, { label: "Change Image", onClick: onChangeImage }),
20484
- /* @__PURE__ */ jsx23(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
20854
+ /* @__PURE__ */ jsxs15("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20855
+ /* @__PURE__ */ jsx24(Pill2, { label: "Change Image", onClick: onChangeImage }),
20856
+ /* @__PURE__ */ jsx24(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
20485
20857
  ] }),
20486
- error && /* @__PURE__ */ jsx23("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20858
+ error && /* @__PURE__ */ jsx24("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20487
20859
  ]
20488
20860
  }
20489
20861
  );
@@ -20623,8 +20995,8 @@ function AiFadeCarouselOverlay({
20623
20995
  const activeItemRect = useLiveItemRect2(active);
20624
20996
  const TOOLBAR_APPROX_H = 90;
20625
20997
  const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
20626
- return /* @__PURE__ */ jsxs14(Fragment10, { children: [
20627
- hoverHandleRect && hover && /* @__PURE__ */ jsx23(
20998
+ return /* @__PURE__ */ jsxs15(Fragment10, { children: [
20999
+ hoverHandleRect && hover && /* @__PURE__ */ jsx24(
20628
21000
  "button",
20629
21001
  {
20630
21002
  type: "button",
@@ -20653,10 +21025,10 @@ function AiFadeCarouselOverlay({
20653
21025
  fontSize: 14,
20654
21026
  fontWeight: 700
20655
21027
  },
20656
- children: /* @__PURE__ */ jsx23(Star4, { size: 14, style: { pointerEvents: "none" } })
21028
+ children: /* @__PURE__ */ jsx24(Star4, { size: 14, style: { pointerEvents: "none" } })
20657
21029
  }
20658
21030
  ),
20659
- swapping && /* @__PURE__ */ jsx23(
21031
+ swapping && /* @__PURE__ */ jsx24(
20660
21032
  "div",
20661
21033
  {
20662
21034
  ref: skeletonRef,
@@ -20674,15 +21046,15 @@ function AiFadeCarouselOverlay({
20674
21046
  zIndex: 2147483646,
20675
21047
  pointerEvents: "none"
20676
21048
  },
20677
- children: /* @__PURE__ */ jsx23("style", { children: MEDIA_SKELETON_CSS })
21049
+ children: /* @__PURE__ */ jsx24("style", { children: MEDIA_SKELETON_CSS })
20678
21050
  }
20679
21051
  ),
20680
- activeItemRect && active && activeEntry && /* @__PURE__ */ jsx23(
21052
+ activeItemRect && active && activeEntry && /* @__PURE__ */ jsx24(
20681
21053
  ItemInteractionLayer,
20682
21054
  {
20683
21055
  rect: activeItemRect,
20684
21056
  state: toolbarState,
20685
- toolbar: /* @__PURE__ */ jsx23(
21057
+ toolbar: /* @__PURE__ */ jsx24(
20686
21058
  CarouselSlideToolbar,
20687
21059
  {
20688
21060
  pending,
@@ -20900,118 +21272,11 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
20900
21272
  // src/ui/link-modal/LinkPopover.tsx
20901
21273
  import { useEffect as useEffect13 } from "react";
20902
21274
 
20903
- // src/ui/dialog.tsx
20904
- import * as React9 from "react";
20905
- import { Dialog as DialogPrimitive } from "radix-ui";
20906
- import { X as X2 } from "lucide-react";
20907
- import { jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
20908
- function Dialog2({
20909
- ...props
20910
- }) {
20911
- return /* @__PURE__ */ jsx24(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
20912
- }
20913
- function DialogPortal({
20914
- ...props
20915
- }) {
20916
- return /* @__PURE__ */ jsx24(DialogPrimitive.Portal, { ...props });
20917
- }
20918
- function DialogOverlay({
20919
- className,
20920
- ...props
20921
- }) {
20922
- return /* @__PURE__ */ jsx24(
20923
- DialogPrimitive.Overlay,
20924
- {
20925
- "data-slot": "dialog-overlay",
20926
- "data-ohw-link-modal-root": "",
20927
- className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
20928
- ...props
20929
- }
20930
- );
20931
- }
20932
- var DialogContent = React9.forwardRef(
20933
- ({ className, children, showCloseButton = true, container, ...props }, ref) => {
20934
- const positionMode = container ? "absolute" : "fixed";
20935
- return /* @__PURE__ */ jsxs15(DialogPortal, { container: container ?? void 0, children: [
20936
- /* @__PURE__ */ jsx24(DialogOverlay, { className: cn(positionMode, "inset-0") }),
20937
- /* @__PURE__ */ jsxs15(
20938
- DialogPrimitive.Content,
20939
- {
20940
- ref,
20941
- "data-slot": "dialog-content",
20942
- className: cn(
20943
- positionMode,
20944
- "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",
20945
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
20946
- container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
20947
- className
20948
- ),
20949
- onMouseDown: (e) => e.stopPropagation(),
20950
- ...props,
20951
- children: [
20952
- children,
20953
- showCloseButton ? /* @__PURE__ */ jsx24(
20954
- DialogPrimitive.Close,
20955
- {
20956
- type: "button",
20957
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
20958
- "aria-label": "Close",
20959
- children: /* @__PURE__ */ jsx24(X2, { size: 16, "aria-hidden": true })
20960
- }
20961
- ) : null
20962
- ]
20963
- }
20964
- )
20965
- ] });
20966
- }
20967
- );
20968
- DialogContent.displayName = DialogPrimitive.Content.displayName;
20969
- function DialogHeader({
20970
- className,
20971
- ...props
20972
- }) {
20973
- return /* @__PURE__ */ jsx24("div", { className: cn("flex flex-col gap-1.5", className), ...props });
20974
- }
20975
- function DialogFooter({
20976
- className,
20977
- ...props
20978
- }) {
20979
- return /* @__PURE__ */ jsx24(
20980
- "div",
20981
- {
20982
- className: cn("flex items-center justify-end gap-2", className),
20983
- ...props
20984
- }
20985
- );
20986
- }
20987
- var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx24(
20988
- DialogPrimitive.Title,
20989
- {
20990
- ref,
20991
- className: cn(
20992
- "text-lg font-semibold leading-none tracking-tight text-card-foreground",
20993
- className
20994
- ),
20995
- ...props
20996
- }
20997
- ));
20998
- DialogTitle.displayName = DialogPrimitive.Title.displayName;
20999
- var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx24(
21000
- DialogPrimitive.Description,
21001
- {
21002
- ref,
21003
- className: cn("text-sm text-muted-foreground", className),
21004
- ...props
21005
- }
21006
- ));
21007
- DialogDescription.displayName = DialogPrimitive.Description.displayName;
21008
- var DialogClose = DialogPrimitive.Close;
21009
-
21010
21275
  // src/ui/link-modal/LinkEditorPanel.tsx
21011
21276
  import { Info, X as X4 } from "lucide-react";
21012
21277
 
21013
21278
  // src/ui/link-modal/DestinationBreadcrumb.tsx
21014
- import { ArrowRight as ArrowRight2, File, GalleryVertical } from "lucide-react";
21279
+ import { ArrowRight as ArrowRight3, File, GalleryVertical } from "lucide-react";
21015
21280
  import { jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
21016
21281
  function DestinationBreadcrumb({
21017
21282
  pageTitle,
@@ -21025,7 +21290,7 @@ function DestinationBreadcrumb({
21025
21290
  /* @__PURE__ */ jsx25("span", { className: "text-sm font-medium leading-none text-foreground!", children: pageTitle })
21026
21291
  ] }),
21027
21292
  /* @__PURE__ */ jsx25(
21028
- ArrowRight2,
21293
+ ArrowRight3,
21029
21294
  {
21030
21295
  size: 16,
21031
21296
  className: "shrink-0 text-muted-foreground",
@@ -21137,7 +21402,7 @@ function Label({ className, ...props }) {
21137
21402
  }
21138
21403
 
21139
21404
  // src/ui/link-modal/UrlOrPageInput.tsx
21140
- import { ChevronDown as ChevronDown4, File as File2, X as X3 } from "lucide-react";
21405
+ import { ChevronDown as ChevronDown5, File as File2, X as X3 } from "lucide-react";
21141
21406
  import { jsx as jsx29, jsxs as jsxs18 } from "react/jsx-runtime";
21142
21407
  function FieldChevron({
21143
21408
  onClick
@@ -21150,7 +21415,7 @@ function FieldChevron({
21150
21415
  onClick,
21151
21416
  "aria-label": "Open page list",
21152
21417
  tabIndex: -1,
21153
- children: /* @__PURE__ */ jsx29(ChevronDown4, { size: 16 })
21418
+ children: /* @__PURE__ */ jsx29(ChevronDown5, { size: 16 })
21154
21419
  }
21155
21420
  );
21156
21421
  }
@@ -21380,7 +21645,7 @@ function LinkEditorPanel({ state, onClose }) {
21380
21645
  // src/ui/link-modal/SectionPickerOverlay.tsx
21381
21646
  import { useCallback as useCallback7, useEffect as useEffect11, useMemo as useMemo2, useRef as useRef10, useState as useState13 } from "react";
21382
21647
  import { createPortal as createPortal2 } from "react-dom";
21383
- import { ArrowLeft as ArrowLeft2, Check as Check3 } from "lucide-react";
21648
+ import { ArrowLeft as ArrowLeft3, Check as Check4 } from "lucide-react";
21384
21649
  import { usePathname, useRouter as useRouter2 } from "next/navigation";
21385
21650
 
21386
21651
  // src/lib/parent-scroll.ts
@@ -21657,7 +21922,7 @@ function SectionPickerOverlay({
21657
21922
  className: "h-8 min-w-0 gap-1 border-border bg-background px-2 py-1.5 shadow-sm hover:bg-muted cursor-pointer",
21658
21923
  onClick: onBack,
21659
21924
  children: [
21660
- /* @__PURE__ */ jsx31(ArrowLeft2, { className: "size-4 shrink-0", "aria-hidden": true }),
21925
+ /* @__PURE__ */ jsx31(ArrowLeft3, { className: "size-4 shrink-0", "aria-hidden": true }),
21661
21926
  "Back"
21662
21927
  ]
21663
21928
  }
@@ -21726,7 +21991,7 @@ function SectionPickerOverlay({
21726
21991
  className: "absolute right-3 top-3 flex size-8 items-center justify-center rounded-full text-white",
21727
21992
  style: { backgroundColor: "var(--ohw-primary, #0885fe)" },
21728
21993
  "aria-hidden": true,
21729
- children: /* @__PURE__ */ jsx31(Check3, { className: "size-5" })
21994
+ children: /* @__PURE__ */ jsx31(Check4, { className: "size-5" })
21730
21995
  }
21731
21996
  ) : null
21732
21997
  ]
@@ -22110,6 +22375,7 @@ function shouldUseDevFixtures() {
22110
22375
  function getLogoElement(el) {
22111
22376
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
22112
22377
  if (marked) return marked;
22378
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
22113
22379
  const root = el.closest("nav, [data-ohw-nav-root], footer");
22114
22380
  if (!root) return null;
22115
22381
  const anchor = el.closest("a");
@@ -22179,7 +22445,7 @@ function addFooterColumnWithPersist({
22179
22445
  import {
22180
22446
  useCallback as useCallback9,
22181
22447
  useEffect as useEffect14,
22182
- useLayoutEffect as useLayoutEffect5,
22448
+ useLayoutEffect as useLayoutEffect6,
22183
22449
  useRef as useRef11,
22184
22450
  useState as useState15
22185
22451
  } from "react";
@@ -22276,7 +22542,7 @@ function FloatingPanel({
22276
22542
  return () => window.removeEventListener("message", onParentScroll);
22277
22543
  }, [open]);
22278
22544
  const positionRef = useRef11(position);
22279
- useLayoutEffect5(() => {
22545
+ useLayoutEffect6(() => {
22280
22546
  if (!open) {
22281
22547
  positionRef.current = position;
22282
22548
  return;
@@ -22286,7 +22552,7 @@ function FloatingPanel({
22286
22552
  }
22287
22553
  positionRef.current = position;
22288
22554
  }, [open, position, liveScroll, parentScroll]);
22289
- useLayoutEffect5(() => {
22555
+ useLayoutEffect6(() => {
22290
22556
  if (!open || !panelRef.current) return;
22291
22557
  const el = panelRef.current;
22292
22558
  const header = el.querySelector("[data-ohw-floating-panel-header]");
@@ -22495,7 +22761,7 @@ function SocialsDisplayPanel({ display, onChange, className }) {
22495
22761
  }
22496
22762
 
22497
22763
  // src/ui/button-style-panel.tsx
22498
- import { Check as Check4 } from "lucide-react";
22764
+ import { Check as Check5 } from "lucide-react";
22499
22765
  import { jsx as jsx35, jsxs as jsxs24 } from "react/jsx-runtime";
22500
22766
  var SIZE_LABEL = { s: "S", m: "M", l: "L" };
22501
22767
  function ButtonStylePanel({
@@ -22566,7 +22832,7 @@ function ButtonStylePanel({
22566
22832
  {
22567
22833
  className: "absolute right-0.5 top-0.5 flex size-5 items-center justify-center rounded-full bg-primary text-primary-foreground",
22568
22834
  "aria-hidden": true,
22569
- children: /* @__PURE__ */ jsx35(Check4, { className: "size-3", strokeWidth: 3 })
22835
+ children: /* @__PURE__ */ jsx35(Check5, { className: "size-3", strokeWidth: 3 })
22570
22836
  }
22571
22837
  ) : null
22572
22838
  ]
@@ -23591,7 +23857,7 @@ function useSectionDrag({
23591
23857
  }
23592
23858
 
23593
23859
  // src/ui/footer-container-chrome.tsx
23594
- import { Plus as Plus3 } from "lucide-react";
23860
+ import { Plus as Plus4 } from "lucide-react";
23595
23861
  import { jsx as jsx36, jsxs as jsxs25 } from "react/jsx-runtime";
23596
23862
  function FooterContainerChrome({
23597
23863
  rect,
@@ -23632,7 +23898,7 @@ function FooterContainerChrome({
23632
23898
  if (addDisabled) return;
23633
23899
  onAdd();
23634
23900
  },
23635
- children: /* @__PURE__ */ jsx36(Plus3, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
23901
+ children: /* @__PURE__ */ jsx36(Plus4, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
23636
23902
  }
23637
23903
  ) }),
23638
23904
  /* @__PURE__ */ jsx36(TooltipContent, { side: "bottom", sideOffset: 9, children: addDisabled ? "Maximum columns reached" : "Add item" })
@@ -23810,8 +24076,104 @@ function deleteSelectedNavFooterItem(deps) {
23810
24076
  return false;
23811
24077
  }
23812
24078
 
24079
+ // src/lib/nav-page-sync.ts
24080
+ function labelKeyFor(hrefKey) {
24081
+ return hrefKey.replace(/-href$/, "-label");
24082
+ }
24083
+ function isPageLinkKey(key) {
24084
+ return isNavbarHrefKey(key) || isFooterHrefKey(key);
24085
+ }
24086
+ function currentHref(key, anchor, content) {
24087
+ const stored = content[key];
24088
+ if (typeof stored === "string" && stored.length > 0) return stored;
24089
+ return anchor.getAttribute("href") ?? "";
24090
+ }
24091
+ function normalizePath2(value) {
24092
+ if (value.length > 1 && value.endsWith("/")) return value.slice(0, -1);
24093
+ return value;
24094
+ }
24095
+ function pointsAt(href, path) {
24096
+ const normalized = normalizePath2(href);
24097
+ const target = normalizePath2(path);
24098
+ if (normalized === target) return true;
24099
+ return (normalized.startsWith(`${target}#`) || normalized.startsWith(`${target}?`)) && target !== "/";
24100
+ }
24101
+ function retargetHref(href, oldPath, newPath) {
24102
+ const normalized = normalizePath2(href);
24103
+ const target = normalizePath2(oldPath);
24104
+ if (normalized === target) return newPath;
24105
+ return `${newPath}${normalized.slice(target.length)}`;
24106
+ }
24107
+ function setLabelText(key, text) {
24108
+ document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`).forEach((el) => {
24109
+ el.textContent = text;
24110
+ });
24111
+ }
24112
+ function commit(delta, deps) {
24113
+ if (Object.keys(delta).length === 0) return;
24114
+ deps.setEditContent({ ...deps.getEditContent(), ...delta });
24115
+ deps.postToParent({
24116
+ type: "ow:change",
24117
+ nodes: Object.entries(delta).map(([key, text]) => ({ key, text }))
24118
+ });
24119
+ }
24120
+ function navLinksTo(path, content) {
24121
+ return Array.from(document.querySelectorAll("[data-ohw-href-key]")).filter((anchor) => {
24122
+ const key = anchor.getAttribute("data-ohw-href-key");
24123
+ return isNavbarHrefKey(key) && pointsAt(currentHref(key, anchor, content), path);
24124
+ }).map((anchor) => anchor.getAttribute("data-ohw-href-key"));
24125
+ }
24126
+ function addNavLinkForPage(page, deps) {
24127
+ const content = deps.getEditContent();
24128
+ if (navLinksTo(page.path, content).length > 0) return false;
24129
+ const roots = listNavbarRootItems();
24130
+ const afterAnchor = roots[roots.length - 1] ?? null;
24131
+ let inserted;
24132
+ try {
24133
+ inserted = insertNavbarItem(page.path, page.title, afterAnchor);
24134
+ } catch {
24135
+ return false;
24136
+ }
24137
+ const { hrefKey, labelKey, index } = inserted;
24138
+ deps.applyLinkByKey(hrefKey, page.path);
24139
+ setLabelText(labelKey, page.title);
24140
+ commit(
24141
+ {
24142
+ [hrefKey]: page.path,
24143
+ [labelKey]: page.title,
24144
+ [NAV_COUNT_KEY]: String(index + 1),
24145
+ [NAV_ORDER_KEY]: getNavbarOrderJson()
24146
+ },
24147
+ deps
24148
+ );
24149
+ return true;
24150
+ }
24151
+ function retargetPageLinks(change, deps) {
24152
+ const { oldPath, newPath, oldTitle, newTitle } = change;
24153
+ if (!oldPath || !newPath || normalizePath2(oldPath) === normalizePath2(newPath)) return 0;
24154
+ const content = deps.getEditContent();
24155
+ const delta = {};
24156
+ for (const anchor of Array.from(document.querySelectorAll("[data-ohw-href-key]"))) {
24157
+ const hrefKey = anchor.getAttribute("data-ohw-href-key");
24158
+ if (!isPageLinkKey(hrefKey)) continue;
24159
+ const href = currentHref(hrefKey, anchor, content);
24160
+ if (!pointsAt(href, oldPath)) continue;
24161
+ const nextHref = retargetHref(href, oldPath, newPath);
24162
+ deps.applyLinkByKey(hrefKey, nextHref);
24163
+ delta[hrefKey] = nextHref;
24164
+ if (!oldTitle || !newTitle) continue;
24165
+ const labelKey = labelKeyFor(hrefKey);
24166
+ const label = content[labelKey] ?? anchor.querySelector('[data-ohw-editable="text"]')?.textContent ?? "";
24167
+ if (label.trim() !== oldTitle) continue;
24168
+ setLabelText(labelKey, newTitle);
24169
+ delta[labelKey] = newTitle;
24170
+ }
24171
+ commit(delta, deps);
24172
+ return Object.keys(delta).filter((key) => key.endsWith("-href")).length;
24173
+ }
24174
+
23813
24175
  // src/ui/navbar-container-chrome.tsx
23814
- import { Plus as Plus4 } from "lucide-react";
24176
+ import { Plus as Plus5 } from "lucide-react";
23815
24177
  import { jsx as jsx37 } from "react/jsx-runtime";
23816
24178
  function NavbarContainerChrome({
23817
24179
  rect,
@@ -23847,7 +24209,7 @@ function NavbarContainerChrome({
23847
24209
  e.stopPropagation();
23848
24210
  onAdd();
23849
24211
  },
23850
- children: /* @__PURE__ */ jsx37(Plus4, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
24212
+ children: /* @__PURE__ */ jsx37(Plus5, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
23851
24213
  }
23852
24214
  )
23853
24215
  }
@@ -23919,7 +24281,7 @@ function Badge({ className, variant, ...props }) {
23919
24281
  }
23920
24282
 
23921
24283
  // src/OhhwellsBridge.tsx
23922
- import { Brush as Brush2, Link as Link2, Plus as Plus5, Settings } from "lucide-react";
24284
+ import { Brush as Brush2, Link as Link2, Plus as Plus6, Settings } from "lucide-react";
23923
24285
  import { Fragment as Fragment13, jsx as jsx40, jsxs as jsxs26 } from "react/jsx-runtime";
23924
24286
  var PRIMARY7 = "#0885FE";
23925
24287
  var IMAGE_FADE_MS = 300;
@@ -24697,7 +25059,7 @@ function getNavigationSelectionParent(el) {
24697
25059
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
24698
25060
  return resolveFooterLinksContainerFrom(el);
24699
25061
  }
24700
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
25062
+ if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isFooterLinksContainer(el) || isInferredFooterGroup2(el)) {
24701
25063
  return getNavigationRoot(el);
24702
25064
  }
24703
25065
  return null;
@@ -24919,7 +25281,6 @@ var ICONS = {
24919
25281
  insertUnorderedList: '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>',
24920
25282
  insertOrderedList: '<line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/>'
24921
25283
  };
24922
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
24923
25284
  var SELECTION_CHROME_GAP2 = 4;
24924
25285
  var TOOLBAR_STROKE_GAP2 = 4;
24925
25286
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -25017,7 +25378,7 @@ function applyVisibleViewport(el, parentScroll) {
25017
25378
  el.style.top = `${top}px`;
25018
25379
  el.style.height = `${height}px`;
25019
25380
  }
25020
- var TOOLBAR_EDGE_MARGIN3 = 4;
25381
+ var TOOLBAR_EDGE_MARGIN4 = 4;
25021
25382
  function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
25022
25383
  const isAbove = transform.includes("translateY(-100%)");
25023
25384
  let visualTop = isAbove ? top - approxH : top;
@@ -25044,8 +25405,8 @@ function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
25044
25405
  function clampToolbarLeft(centerX, toolbarWidth) {
25045
25406
  const half = toolbarWidth / 2;
25046
25407
  return Math.max(
25047
- TOOLBAR_EDGE_MARGIN3 + half,
25048
- Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half)
25408
+ TOOLBAR_EDGE_MARGIN4 + half,
25409
+ Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN4 - half)
25049
25410
  );
25050
25411
  }
25051
25412
  function calcToolbarPos(rect, parentScroll, toolbarW = 306) {
@@ -25446,7 +25807,7 @@ function OhhwellsBridge() {
25446
25807
  }, []);
25447
25808
  const subdomainFromQuery = searchParams.get("subdomain");
25448
25809
  const [subdomain, setSubdomain] = useState18(() => resolveSubdomain(subdomainFromQuery));
25449
- useLayoutEffect6(() => {
25810
+ useLayoutEffect7(() => {
25450
25811
  const next = resolveSubdomain(subdomainFromQuery);
25451
25812
  if (next !== subdomain) setSubdomain(next);
25452
25813
  if (next) return;
@@ -25925,21 +26286,6 @@ function OhhwellsBridge() {
25925
26286
  const [isItemDragging, setIsItemDragging] = useState18(false);
25926
26287
  const [isFooterFrameSelection, setIsFooterFrameSelection] = useState18(false);
25927
26288
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
25928
- const [floatingPanel, setFloatingPanel] = useState18(null);
25929
- const floatingPanelOpenRef = useRef14(false);
25930
- floatingPanelOpenRef.current = floatingPanel !== null;
25931
- const floatingPanelRef = useRef14(null);
25932
- floatingPanelRef.current = floatingPanel;
25933
- const [floatingPanelPos, setFloatingPanelPos] = useState18(null);
25934
- const buttonCollectionDragRef = useRef14(null);
25935
- const buttonDropInsertIndexRef = useRef14(null);
25936
- const [buttonDropSlots, setButtonDropSlots] = useState18([]);
25937
- const [activeButtonDropIndex, setActiveButtonDropIndex] = useState18(null);
25938
- const buttonPointerDragRef = useRef14(null);
25939
- const [buttonStyleTick, setButtonStyleTick] = useState18(0);
25940
- const [logoSizeDraft, setLogoSizeDraft] = useState18(null);
25941
- const [editorViewport, setEditorViewport] = useState18("desktop");
25942
- const [parentScrollSnap, setParentScrollSnap] = useState18(null);
25943
26289
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = useState18(null);
25944
26290
  const [footerHeadingVisible, setFooterHeadingVisible] = useState18(null);
25945
26291
  const footerDragRef = useRef14(null);
@@ -25965,6 +26311,21 @@ function OhhwellsBridge() {
25965
26311
  const stylesRef = useRef14("");
25966
26312
  const appliedDrawerRef = useRef14(null);
25967
26313
  const pendingDeleteUndoRef = useRef14(null);
26314
+ const [floatingPanel, setFloatingPanel] = useState18(null);
26315
+ const floatingPanelOpenRef = useRef14(false);
26316
+ const floatingPanelRef = useRef14(null);
26317
+ floatingPanelRef.current = floatingPanel;
26318
+ const setFloatingPanelRef = useRef14(setFloatingPanel);
26319
+ const [floatingPanelPos, setFloatingPanelPos] = useState18(null);
26320
+ const buttonCollectionDragRef = useRef14(null);
26321
+ const buttonDropInsertIndexRef = useRef14(null);
26322
+ const [buttonDropSlots, setButtonDropSlots] = useState18([]);
26323
+ const [activeButtonDropIndex, setActiveButtonDropIndex] = useState18(null);
26324
+ const buttonPointerDragRef = useRef14(null);
26325
+ const [buttonStyleTick, setButtonStyleTick] = useState18(0);
26326
+ const [logoSizeDraft, setLogoSizeDraft] = useState18(null);
26327
+ const [editorViewport, setEditorViewport] = useState18("desktop");
26328
+ const [parentScrollSnap, setParentScrollSnap] = useState18(null);
25968
26329
  const [sitePages, setSitePages] = useState18([]);
25969
26330
  const collectionItemsRef = useRef14(/* @__PURE__ */ new Map());
25970
26331
  const [sectionsByPath, setSectionsByPath] = useState18({});
@@ -25974,7 +26335,18 @@ function OhhwellsBridge() {
25974
26335
  const linkPopoverOpenRef = useRef14(false);
25975
26336
  const linkPopoverGraceUntilRef = useRef14(0);
25976
26337
  setLinkPopoverRef.current = setLinkPopover;
26338
+ setFloatingPanelRef.current = setFloatingPanel;
25977
26339
  linkPopoverSessionRef.current = linkPopover;
26340
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
26341
+ useEffect17(() => {
26342
+ const syncViewport = () => {
26343
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
26344
+ setEditorViewport((prev) => prev === next ? prev : next);
26345
+ };
26346
+ syncViewport();
26347
+ window.addEventListener("resize", syncViewport);
26348
+ return () => window.removeEventListener("resize", syncViewport);
26349
+ }, []);
25978
26350
  useEffect17(() => {
25979
26351
  setSectionOrderPersistHandler((entries) => {
25980
26352
  const currentPath = window.location.pathname;
@@ -26123,6 +26495,43 @@ function OhhwellsBridge() {
26123
26495
  if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
26124
26496
  return () => window.removeEventListener("message", onSitePages);
26125
26497
  }, [isEditMode, postToParent2]);
26498
+ useEffect17(() => {
26499
+ if (!isEditMode) return;
26500
+ const deps = {
26501
+ getEditContent: () => editContentRef.current,
26502
+ setEditContent: (next) => {
26503
+ editContentRef.current = next;
26504
+ },
26505
+ applyLinkByKey,
26506
+ postToParent: postToParent2
26507
+ };
26508
+ const onPageMessage = (e) => {
26509
+ if (e.data?.type === "ow:page-created") {
26510
+ const { path, title } = e.data;
26511
+ if (typeof path !== "string" || !path) return;
26512
+ if (addNavLinkForPage({ path, title: typeof title === "string" && title ? title : path }, deps)) {
26513
+ enforceLinkHrefs();
26514
+ }
26515
+ return;
26516
+ }
26517
+ if (e.data?.type === "ow:page-renamed") {
26518
+ const { oldPath, newPath, oldTitle, newTitle } = e.data;
26519
+ if (typeof oldPath !== "string" || typeof newPath !== "string") return;
26520
+ const moved = retargetPageLinks(
26521
+ {
26522
+ oldPath,
26523
+ newPath,
26524
+ oldTitle: typeof oldTitle === "string" ? oldTitle : void 0,
26525
+ newTitle: typeof newTitle === "string" ? newTitle : void 0
26526
+ },
26527
+ deps
26528
+ );
26529
+ if (moved > 0) enforceLinkHrefs();
26530
+ }
26531
+ };
26532
+ window.addEventListener("message", onPageMessage);
26533
+ return () => window.removeEventListener("message", onPageMessage);
26534
+ }, [isEditMode, postToParent2]);
26126
26535
  useEffect17(() => {
26127
26536
  if (!isEditMode || shouldUseDevFixtures()) return;
26128
26537
  void loadAllSectionsManifest().then((manifest) => {
@@ -27644,7 +28053,7 @@ function OhhwellsBridge() {
27644
28053
  isFooterFrameSelection,
27645
28054
  postToParent2
27646
28055
  ]);
27647
- useLayoutEffect6(() => {
28056
+ useLayoutEffect7(() => {
27648
28057
  if (!subdomain || isEditMode) {
27649
28058
  setFetchState("done");
27650
28059
  return;
@@ -27671,6 +28080,7 @@ function OhhwellsBridge() {
27671
28080
  applyAiFooterFromContent(content);
27672
28081
  if (typeof content[STYLE_STORE_KEY] === "string") {
27673
28082
  stylesRef.current = content[STYLE_STORE_KEY];
28083
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
27674
28084
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
27675
28085
  }
27676
28086
  applyBrandChrome(content);
@@ -27681,12 +28091,12 @@ function OhhwellsBridge() {
27681
28091
  if (key === "__ohw_sections") continue;
27682
28092
  if (key === AI_SECTIONS_KEY) continue;
27683
28093
  if (key === AI_FOOTER_KEY) continue;
28094
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
28095
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
27684
28096
  if (key === BRAND_KIT_KEY) continue;
27685
28097
  if (key === STYLE_STORE_KEY) continue;
27686
28098
  if (BRAND_CHROME_KEYS.has(key)) continue;
27687
- if (key === LOGO_PLACEHOLDER_KEY) continue;
27688
28099
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
27689
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
27690
28100
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
27691
28101
  continue;
27692
28102
  }
@@ -27917,17 +28327,18 @@ function OhhwellsBridge() {
27917
28327
  }
27918
28328
  applyAiFooterFromContent(content);
27919
28329
  if (typeof content[STYLE_STORE_KEY] === "string") {
28330
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
27920
28331
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
27921
28332
  }
27922
28333
  for (const [key, val] of Object.entries(content)) {
27923
28334
  if (key === "__ohw_sections") continue;
27924
28335
  if (key === AI_SECTIONS_KEY) continue;
27925
28336
  if (key === AI_FOOTER_KEY) continue;
28337
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
28338
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
27926
28339
  if (key === BRAND_KIT_KEY) continue;
27927
28340
  if (key === STYLE_STORE_KEY) continue;
27928
28341
  if (BRAND_CHROME_KEYS.has(key)) continue;
27929
- if (key === LOGO_PLACEHOLDER_KEY) continue;
27930
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
27931
28342
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
27932
28343
  continue;
27933
28344
  }
@@ -28010,7 +28421,7 @@ function OhhwellsBridge() {
28010
28421
  if (debounceTimer) clearTimeout(debounceTimer);
28011
28422
  };
28012
28423
  }, [subdomain, isEditMode, pathname]);
28013
- useLayoutEffect6(() => {
28424
+ useLayoutEffect7(() => {
28014
28425
  const el = document.getElementById("ohw-loader");
28015
28426
  if (!el) return;
28016
28427
  const visible = Boolean(subdomain) && fetchState !== "done";
@@ -28194,26 +28605,11 @@ function OhhwellsBridge() {
28194
28605
  const t2 = setTimeout(measure, 500);
28195
28606
  const ro = new ResizeObserver(schedule);
28196
28607
  ro.observe(document.body);
28197
- let lastWidth = window.innerWidth;
28198
- let resizeTimers = [];
28199
- const clearResizeTimers = () => {
28200
- resizeTimers.forEach(clearTimeout);
28201
- resizeTimers = [];
28202
- };
28203
- const handleResize = () => {
28204
- if (window.innerWidth === lastWidth) return;
28205
- lastWidth = window.innerWidth;
28206
- clearResizeTimers();
28207
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
28208
- };
28209
- window.addEventListener("resize", handleResize);
28210
28608
  return () => {
28211
28609
  clearTimeout(t1);
28212
28610
  clearTimeout(t2);
28213
28611
  if (raf != null) cancelAnimationFrame(raf);
28214
28612
  ro.disconnect();
28215
- clearResizeTimers();
28216
- window.removeEventListener("resize", handleResize);
28217
28613
  };
28218
28614
  }, [pathname, isEditMode, postToParent2]);
28219
28615
  useEffect17(() => {
@@ -28510,6 +28906,13 @@ function OhhwellsBridge() {
28510
28906
  if (target.closest("[data-ohw-state-toggle]")) return;
28511
28907
  if (target.closest("[data-ohw-max-badge]")) return;
28512
28908
  if (isInsideLinkEditor(target)) return;
28909
+ if (isInsideFloatingPanel(target)) return;
28910
+ if (target.closest("[data-ohw-form-toolbar]")) return;
28911
+ if (target.closest(
28912
+ '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
28913
+ )) {
28914
+ return;
28915
+ }
28513
28916
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
28514
28917
  clearMediaSelectionRef.current();
28515
28918
  }
@@ -28533,8 +28936,6 @@ function OhhwellsBridge() {
28533
28936
  return;
28534
28937
  }
28535
28938
  }
28536
- if (isInsideFloatingPanel(target)) return;
28537
- if (target.closest("[data-ohw-form-toolbar]")) return;
28538
28939
  const collectionItem = matchCollectionItemClick(target, collectionItemsRef.current);
28539
28940
  if (collectionItem) {
28540
28941
  e.preventDefault();
@@ -28546,11 +28947,6 @@ function OhhwellsBridge() {
28546
28947
  });
28547
28948
  return;
28548
28949
  }
28549
- if (target.closest(
28550
- '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
28551
- )) {
28552
- return;
28553
- }
28554
28950
  {
28555
28951
  const formEl = getFormElement(target);
28556
28952
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -28704,9 +29100,13 @@ function OhhwellsBridge() {
28704
29100
  if (isIconEditable(editable) && !getSocialItem(editable)) {
28705
29101
  e.preventDefault();
28706
29102
  e.stopPropagation();
28707
- selectIconElementRef.current(editable);
28708
29103
  const aiIcon = editable.hasAttribute("data-ai-icon");
28709
29104
  const featureIcon = aiIcon && /\.features\.\d+\.icon$/.test(editable.dataset.ohwKey ?? "");
29105
+ if (aiIcon) {
29106
+ aiSectionApiRef.current?.selectFromElement(editable);
29107
+ } else {
29108
+ selectIconElementRef.current(editable);
29109
+ }
28710
29110
  postToParentRef.current({
28711
29111
  type: "ow:icon-pick",
28712
29112
  key: editable.dataset.ohwKey ?? "",
@@ -28934,6 +29334,9 @@ function OhhwellsBridge() {
28934
29334
  setHoveredItemRect(null);
28935
29335
  hoveredNavContainerRef.current = null;
28936
29336
  setHoveredNavContainerRect(null);
29337
+ siblingHintElRef.current = null;
29338
+ setSiblingHintRect(null);
29339
+ setSiblingHintRects([]);
28937
29340
  return;
28938
29341
  }
28939
29342
  {
@@ -29054,7 +29457,6 @@ function OhhwellsBridge() {
29054
29457
  hoveredNavContainerRef.current = null;
29055
29458
  setHoveredNavContainerRect(null);
29056
29459
  hoveredItemElRef.current = editable;
29057
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
29058
29460
  }
29059
29461
  }
29060
29462
  }
@@ -29764,7 +30166,11 @@ function OhhwellsBridge() {
29764
30166
  };
29765
30167
  const handleMouseMove = (e) => {
29766
30168
  const { clientX, clientY } = e;
29767
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
30169
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
30170
+ dismissImageHover();
30171
+ clearImageHover();
30172
+ return;
30173
+ }
29768
30174
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) {
29769
30175
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
29770
30176
  formHoverElRef.current = null;
@@ -29793,7 +30199,12 @@ function OhhwellsBridge() {
29793
30199
  setHoveredItemRect(null);
29794
30200
  hoveredNavContainerRef.current = null;
29795
30201
  setHoveredNavContainerRect(null);
30202
+ siblingHintElRef.current = null;
30203
+ setSiblingHintRect(null);
30204
+ setSiblingHintRects([]);
29796
30205
  dismissImageHover();
30206
+ clearImageHover();
30207
+ setSectionGap(null);
29797
30208
  return;
29798
30209
  }
29799
30210
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -29805,7 +30216,11 @@ function OhhwellsBridge() {
29805
30216
  if (e.data?.type !== "ow:pointer-sync") return;
29806
30217
  const { clientX, clientY } = e.data;
29807
30218
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
29808
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
30219
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
30220
+ dismissImageHover();
30221
+ clearImageHover();
30222
+ return;
30223
+ }
29809
30224
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) return;
29810
30225
  if (probeSocialsRowAt(clientX, clientY)) return;
29811
30226
  probeSectionGapAt(clientX, clientY);
@@ -30185,6 +30600,7 @@ function OhhwellsBridge() {
30185
30600
  applyAiFooterFromContent(content);
30186
30601
  if (typeof content[STYLE_STORE_KEY] === "string") {
30187
30602
  stylesRef.current = content[STYLE_STORE_KEY];
30603
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
30188
30604
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
30189
30605
  }
30190
30606
  applyBrandChrome(content);
@@ -30199,12 +30615,12 @@ function OhhwellsBridge() {
30199
30615
  }
30200
30616
  if (key === AI_SECTIONS_KEY) continue;
30201
30617
  if (key === AI_FOOTER_KEY) continue;
30618
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
30619
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
30202
30620
  if (key === BRAND_KIT_KEY) continue;
30203
30621
  if (key === STYLE_STORE_KEY) continue;
30204
30622
  if (BRAND_CHROME_KEYS.has(key)) continue;
30205
- if (key === LOGO_PLACEHOLDER_KEY) continue;
30206
30623
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
30207
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
30208
30624
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
30209
30625
  continue;
30210
30626
  }
@@ -30840,6 +31256,12 @@ function OhhwellsBridge() {
30840
31256
  closeLinkPopoverRef.current();
30841
31257
  return;
30842
31258
  }
31259
+ if (floatingPanelOpenRef.current) {
31260
+ setFloatingPanelRef.current(null);
31261
+ deselectRef.current();
31262
+ deactivateRef.current();
31263
+ return;
31264
+ }
30843
31265
  deselectRef.current();
30844
31266
  deactivateRef.current();
30845
31267
  clearMediaSelectionRef.current();
@@ -31220,13 +31642,15 @@ function OhhwellsBridge() {
31220
31642
  if (e.data?.type !== "ow:collect-section") return;
31221
31643
  const sectionId = e.data.sectionId;
31222
31644
  if (typeof sectionId !== "string" || !sectionId) return;
31223
- const foundEl = document.querySelector(
31224
- `[data-ohw-section="${CSS.escape(sectionId)}"]`
31645
+ const escapedSectionId = CSS.escape(sectionId);
31646
+ const instanceEl = document.querySelector(
31647
+ `[data-ohw-instance="${escapedSectionId}"]`
31225
31648
  );
31649
+ const foundEl = instanceEl ? sectionElementOf(instanceEl) : document.querySelector(`[data-ohw-section="${escapedSectionId}"]`);
31226
31650
  const sectionEl = foundEl && !foundEl.hasAttribute("data-ohw-ai-template-hidden") && !foundEl.hasAttribute("data-ohw-ai-removed") && !foundEl.hasAttribute("data-ohw-ai-replaced-by") ? foundEl : null;
31227
31651
  const nodes = sectionEl ? collectEditableNodes(void 0, sectionEl).filter(
31228
31652
  (n) => n.key && !n.key.startsWith("__ohw_") && !["meta", "sections", "video-setting"].includes(n.type)
31229
- ).map((n) => ({ ...n, text: n.text ?? "", type: normalizeAiNodeType(n.type) })) : [];
31653
+ ).map((n) => ({ ...n, text: n.text ?? "", type: normalizeAiNodeType(n.type) })).map((n) => withNodeFontSize(n, sectionEl)) : [];
31230
31654
  const items = sectionEl ? collectItemGroups(sectionEl) : [];
31231
31655
  const box = sectionEl?.getBoundingClientRect();
31232
31656
  const rect = box ? {
@@ -31235,7 +31659,11 @@ function OhhwellsBridge() {
31235
31659
  width: box.width,
31236
31660
  height: box.height
31237
31661
  } : null;
31238
- const settings = resolveSectionSettingsContext(sectionId, aiSectionsRef.current, stylesRef.current);
31662
+ const settings = resolveSectionSettingsContext(
31663
+ sectionId,
31664
+ aiSectionsRef.current,
31665
+ stylesRef.current
31666
+ );
31239
31667
  const tree = resolveSectionTree(sectionId, aiSectionsRef.current);
31240
31668
  postToParentRef.current({
31241
31669
  type: "ow:section-nodes",
@@ -31669,10 +32097,10 @@ function OhhwellsBridge() {
31669
32097
  window.removeEventListener("message", handleDuplicateSection);
31670
32098
  window.removeEventListener("message", handleDeactivate);
31671
32099
  window.removeEventListener("message", handleSelectNav);
31672
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
31673
32100
  window.removeEventListener("message", handleToastAction);
31674
32101
  window.removeEventListener("message", handleFormCount);
31675
32102
  window.removeEventListener("message", handleUiEscape);
32103
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
31676
32104
  autoSaveTimers.current.forEach(clearTimeout);
31677
32105
  autoSaveTimers.current.clear();
31678
32106
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -31966,7 +32394,7 @@ function OhhwellsBridge() {
31966
32394
  postToParent2({
31967
32395
  type: "ow:ready",
31968
32396
  version: "1",
31969
- bridgeVersion: "0.1.158",
32397
+ bridgeVersion: "0.1.160-next.427",
31970
32398
  path: pathname,
31971
32399
  nodes: collectEditableNodes(editContentRef.current),
31972
32400
  sections
@@ -32701,7 +33129,7 @@ function OhhwellsBridge() {
32701
33129
  className: "flex h-7 w-7 items-center justify-center rounded-md text-foreground transition-colors hover:bg-muted/80",
32702
33130
  onClick: () => setFieldTypePickerOpen((open) => !open),
32703
33131
  "data-ohw-add-field": "",
32704
- children: /* @__PURE__ */ jsx40(Plus5, { size: 15, "aria-hidden": true })
33132
+ children: /* @__PURE__ */ jsx40(Plus6, { size: 15, "aria-hidden": true })
32705
33133
  }
32706
33134
  ),
32707
33135
  /* @__PURE__ */ jsx40("div", { className: "mx-0.5 h-5 w-px bg-border" }),