@ohhwells/bridge 0.1.160-next.429 → 0.1.161

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1208,21 +1208,21 @@ function chromeFrom(sample) {
1208
1208
  };
1209
1209
  }
1210
1210
  function filledPrimary(seed) {
1211
- const paint2 = parseColor(paintColor(seed)) ?? { r: 15, g: 15, b: 15, a: 1 };
1212
- const luminance = relativeLuminance(paint2);
1211
+ const paint = parseColor(paintColor(seed)) ?? { r: 15, g: 15, b: 15, a: 1 };
1212
+ const luminance = relativeLuminance(paint);
1213
1213
  return {
1214
- background: toCss({ ...paint2, a: 1 }),
1214
+ background: toCss({ ...paint, a: 1 }),
1215
1215
  color: luminance > 0.55 ? "rgb(15, 15, 15)" : "rgb(255, 255, 255)",
1216
1216
  borderColor: TRANSPARENT,
1217
1217
  borderWidth: "0px"
1218
1218
  };
1219
1219
  }
1220
1220
  function generatedSecondary(primary) {
1221
- const paint2 = accentColor(primary);
1221
+ const paint = accentColor(primary);
1222
1222
  return {
1223
1223
  background: TRANSPARENT,
1224
- color: paint2,
1225
- borderColor: paint2,
1224
+ color: paint,
1225
+ borderColor: paint,
1226
1226
  borderWidth: "1px"
1227
1227
  };
1228
1228
  }
@@ -1235,9 +1235,9 @@ function generatedGhost(primary) {
1235
1235
  };
1236
1236
  }
1237
1237
  function normalizeOutlineChrome(outline, primary) {
1238
- const paint2 = accentColor(primary);
1238
+ const paint = accentColor(primary);
1239
1239
  const text = parseColor(outline.color);
1240
- const paintRgb = parseColor(paint2);
1240
+ const paintRgb = parseColor(paint);
1241
1241
  const primaryLabel = parseColor(primary.color);
1242
1242
  const matchesOnPrimary = Boolean(text && primaryLabel) && colorKey(outline.color) === colorKey(primary.color);
1243
1243
  const textTooLight = text ? relativeLuminance(text) > 0.7 : false;
@@ -1246,8 +1246,8 @@ function normalizeOutlineChrome(outline, primary) {
1246
1246
  const hasBorder = parseFloat(outline.borderWidth) > 0;
1247
1247
  return {
1248
1248
  ...outline,
1249
- color: paint2,
1250
- borderColor: hasBorder ? paint2 : outline.borderColor
1249
+ color: paint,
1250
+ borderColor: hasBorder ? paint : outline.borderColor
1251
1251
  };
1252
1252
  }
1253
1253
  function generateButtonVariants(samples) {
@@ -6763,21 +6763,6 @@ function pricingCardFeatureCount(tree, index) {
6763
6763
  const target = findPricingCollection(tree)?.children?.[index];
6764
6764
  return target ? pricingFeatureList(target).length : 0;
6765
6765
  }
6766
- function prevailingFeatureIcon(features) {
6767
- const counts = /* @__PURE__ */ new Map();
6768
- for (const row of features) {
6769
- const key = `${row.icon ?? ""}|${row.iconColor ?? ""}`;
6770
- const entry = counts.get(key) ?? { count: 0, ...row.icon ? { icon: row.icon } : {}, ...row.iconColor ? { iconColor: row.iconColor } : {} };
6771
- entry.count += 1;
6772
- counts.set(key, entry);
6773
- }
6774
- let best = null;
6775
- for (const entry of counts.values()) {
6776
- if (!best || entry.count > best.count) best = entry;
6777
- }
6778
- if (!best) return {};
6779
- return { ...best.icon ? { icon: best.icon } : {}, ...best.iconColor ? { iconColor: best.iconColor } : {} };
6780
- }
6781
6766
  function addPricingCardFeature(tree, index, text) {
6782
6767
  return withPricingCollection(tree, (collection) => {
6783
6768
  const children = collection.children;
@@ -6786,7 +6771,7 @@ function addPricingCardFeature(tree, index, text) {
6786
6771
  const features = pricingFeatureList(target);
6787
6772
  if (features.length >= PRICING_FEATURES_MAX) return false;
6788
6773
  target.slots = target.slots ?? {};
6789
- target.slots.features = [...features, { text, ...prevailingFeatureIcon(features) }];
6774
+ target.slots.features = [...features, { text }];
6790
6775
  return true;
6791
6776
  });
6792
6777
  }
@@ -7430,120 +7415,6 @@ function findSelectableNavContainer(doc = document) {
7430
7415
  );
7431
7416
  }
7432
7417
 
7433
- // src/lib/youtube.ts
7434
- var YOUTUBE_ID_PATTERN = /^[A-Za-z0-9_-]{11}$/;
7435
- function extractYouTubeId(input) {
7436
- const trimmed = input.trim();
7437
- if (!trimmed) return null;
7438
- if (YOUTUBE_ID_PATTERN.test(trimmed)) return trimmed;
7439
- let url;
7440
- try {
7441
- url = new URL(trimmed);
7442
- } catch {
7443
- return null;
7444
- }
7445
- const host = url.hostname.replace(/^www\./, "");
7446
- if (host !== "youtube.com" && host !== "m.youtube.com" && host !== "youtu.be" && host !== "youtube-nocookie.com") {
7447
- return null;
7448
- }
7449
- let candidate = null;
7450
- if (host === "youtu.be") {
7451
- candidate = url.pathname.slice(1).split("/")[0] ?? null;
7452
- } else if (url.pathname === "/watch") {
7453
- candidate = url.searchParams.get("v");
7454
- } else {
7455
- const match = url.pathname.match(/^\/(?:embed|shorts)\/([^/]+)/);
7456
- candidate = match ? match[1] : null;
7457
- }
7458
- return candidate && YOUTUBE_ID_PATTERN.test(candidate) ? candidate : null;
7459
- }
7460
-
7461
- // src/lib/section-embed.ts
7462
- var SECTION_EMBED_SUFFIX = "__ohw_embed";
7463
- function readStoredEmbed(value) {
7464
- if (value.startsWith("start\n")) {
7465
- return { raw: value.slice("start\n".length), at: "start" };
7466
- }
7467
- return { raw: value, at: "end" };
7468
- }
7469
- function embedFrameSrc(value) {
7470
- const videoId = extractYouTubeId(value);
7471
- if (videoId) {
7472
- return { src: `https://www.youtube-nocookie.com/embed/${videoId}?rel=0`, title: "YouTube video" };
7473
- }
7474
- let url;
7475
- try {
7476
- url = new URL(value);
7477
- } catch {
7478
- return null;
7479
- }
7480
- const host = url.hostname.replace(/^www\./, "").toLowerCase();
7481
- if (host === "google.com" && url.pathname.startsWith("/maps") || host === "maps.google.com") {
7482
- const query = url.searchParams.get("q") || url.searchParams.get("query") || "";
7483
- const src = query ? `https://maps.google.com/maps?q=${encodeURIComponent(query)}&output=embed` : `https://maps.google.com/maps?output=embed`;
7484
- return { src, title: "Map" };
7485
- }
7486
- if (host === "maps.app.goo.gl") {
7487
- return { src: value, title: "Map" };
7488
- }
7489
- if (host === "calendly.com" || host.endsWith(".calendly.com") || host === "calendar.google.com") {
7490
- return { src: value, title: "Calendar" };
7491
- }
7492
- if (host === "docs.google.com" && url.pathname.includes("/forms/")) {
7493
- return { src: value.replace(/\/edit(?:\?.*)?$/i, "/viewform"), title: "Form" };
7494
- }
7495
- if (host === "forms.gle") {
7496
- return { src: value, title: "Form" };
7497
- }
7498
- return null;
7499
- }
7500
- function place(section, box, at) {
7501
- if (at === "start") {
7502
- section.insertBefore(box, section.firstChild);
7503
- return;
7504
- }
7505
- section.appendChild(box);
7506
- }
7507
- function paint(section, key, src, title, at) {
7508
- const existing = section.querySelector(`[data-ohw-section-embed="${CSS.escape(key)}"]`);
7509
- const frame = existing?.querySelector("iframe");
7510
- if (existing && frame) {
7511
- if (frame.getAttribute("src") !== src) frame.setAttribute("src", src);
7512
- frame.title = title;
7513
- place(section, existing, at);
7514
- return;
7515
- }
7516
- existing?.remove();
7517
- const box = document.createElement("div");
7518
- box.setAttribute("data-ohw-section-embed", key);
7519
- box.style.cssText = at === "start" ? "width:100%;max-width:960px;margin:0 auto 32px;padding:32px 24px 0;box-sizing:border-box;" : "width:100%;max-width:960px;margin:32px auto 0;padding:0 24px 32px;box-sizing:border-box;";
7520
- const frameWrap = document.createElement("div");
7521
- frameWrap.style.cssText = "position:relative;width:100%;aspect-ratio:16/9;min-height:250px;border-radius:12px;overflow:hidden;background:#0f172a;";
7522
- const iframe = document.createElement("iframe");
7523
- iframe.src = src;
7524
- iframe.title = title;
7525
- iframe.style.cssText = "position:absolute;inset:0;width:100%;height:100%;border:0;";
7526
- iframe.allow = "accelerometer; autoplay; clipboard-write; encrypted-media; fullscreen; gyroscope; picture-in-picture";
7527
- iframe.setAttribute("allowfullscreen", "");
7528
- frameWrap.appendChild(iframe);
7529
- box.appendChild(frameWrap);
7530
- place(section, box, at);
7531
- }
7532
- function applySectionEmbeds(content) {
7533
- for (const [key, val] of Object.entries(content)) {
7534
- if (!key.endsWith(SECTION_EMBED_SUFFIX)) continue;
7535
- const sectionId = key.slice(0, -SECTION_EMBED_SUFFIX.length);
7536
- const section = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
7537
- const stored = typeof val === "string" ? readStoredEmbed(val) : null;
7538
- const frame = stored ? embedFrameSrc(stored.raw) : null;
7539
- if (!section || !stored || !frame) {
7540
- section?.querySelector(`[data-ohw-section-embed="${CSS.escape(key)}"]`)?.remove();
7541
- continue;
7542
- }
7543
- paint(section, key, frame.src, frame.title, stored.at);
7544
- }
7545
- }
7546
-
7547
7418
  // src/ui/ai-tree/aiSectionsManager.tsx
7548
7419
  var import_react_dom = require("react-dom");
7549
7420
  var import_client = require("react-dom/client");
@@ -8117,7 +7988,7 @@ var AI_MOBILE_CSS = [
8117
7988
  // page's first section. Top only (bottom needs no nav clearance); ~96px approximates the
8118
7989
  // template nav's own height (measured ~94px) — there's no shared `--ohw-header-h` var to read
8119
7990
  // an exact figure from (no live measurement exists), so this is a safe approximation, not a
8120
- // pixel-exact fit. Horizontal spacing already comes from `data-ai-section-inner`.
7991
+ // pixel-exact fit. Horizontal spacing already comes from `data-ai-container`.
8121
7992
  '[data-ai-overlay-clearance] [data-ai-overlap-layer="overlay"]{padding-top:96px}',
8122
7993
  // A full-bleed carousel's height is pure arithmetic: it spans the whole window, so 16/9 turns
8123
7994
  // window width straight into height. Measured at 16/9 it came out ~90% of the viewport at EVERY
@@ -8133,10 +8004,8 @@ var AI_MOBILE_CSS = [
8133
8004
  "[data-ai-section]{overflow-x:hidden}",
8134
8005
  // `:not([data-ai-full-bleed])`: this `!important` gutter beat the inline `padding: 0` that a
8135
8006
  // full-bleed section's container sets, so a carousel/media hero that is edge-to-edge on desktop
8136
- // still got a 20px band of section background down both sides on a phone. The footer's own
8137
- // container keeps the unguarded rule below — `data-ai-full-bleed` can never appear on it.
8138
- "[data-ai-section-inner]:not([data-ai-full-bleed]){padding:0 20px !important}",
8139
- "[data-ai-container]{padding:0 20px !important}",
8007
+ // still got a 20px band of section background down both sides on a phone.
8008
+ "[data-ai-container]:not([data-ai-full-bleed]){padding:0 20px !important}",
8140
8009
  "[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
8141
8010
  "[data-ai-cell]{width:100%;min-width:0}",
8142
8011
  // The rule above turns every row into a COLUMN flex container on phones, which silently breaks
@@ -8341,31 +8210,6 @@ function pricingCardEmphasis(featured, ctx, hasBg) {
8341
8210
  function textAttrs(ctx, path) {
8342
8211
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
8343
8212
  }
8344
- var AI_RESPONSIVE_CSS = [
8345
- "@media (max-width: 960px) {",
8346
- " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
8347
- ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
8348
- "}",
8349
- "@media (max-width: 640px) {",
8350
- " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
8351
- " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
8352
- // Group containers flatten to a column on phones; span placements come along for free.
8353
- // `:not([data-ai-group="overlap"])` for the same reason as the twin pair in AI_MOBILE_CSS — see
8354
- // the comment there. This sheet's copy is the narrower selector, so without the exemption it
8355
- // flattens an `overlap` hero below 640px even though the other rule stands down: measured a 390px
8356
- // viewport with the heading/CTA in flow *under* the image, 219px of section background where the
8357
- // layered text used to be.
8358
- ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) { display: flex !important; flex-direction: column !important; }',
8359
- ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) > * { grid-column: auto !important; }',
8360
- // `:not([data-ai-full-bleed])` for the same reason as the twin rule in AI_MOBILE_CSS: this one
8361
- // is narrower (0,2,0 vs 0,1,0) and also `!important`, so without the guard it wins outright and
8362
- // a full-bleed hero gets its 20px side band back below 640px only — the one width where the
8363
- // other rule had already been taught to stand down.
8364
- " [data-ai-responsive] [data-ai-section-inner]:not([data-ai-full-bleed]) { padding-left: 20px !important; padding-right: 20px !important; }",
8365
- " [data-ai-responsive] { overflow-x: hidden; }",
8366
- " [data-ai-responsive] img { max-width: 100%; }",
8367
- "}"
8368
- ].join("\n");
8369
8213
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
8370
8214
  function MediaBox({
8371
8215
  refValue,
@@ -8420,7 +8264,7 @@ function MediaBox({
8420
8264
  position: fill ? "absolute" : void 0,
8421
8265
  inset: fill ? 0 : void 0,
8422
8266
  width: fill ? "100%" : fixedSize ? "auto" : "100%",
8423
- height: fill ? "100%" : fixedSize ? height : void 0,
8267
+ height: fill ? "100%" : void 0,
8424
8268
  aspectRatio: fill ? void 0 : aspectRatio,
8425
8269
  minHeight: fill || fixedSize ? void 0 : height ?? (aspectRatio ? void 0 : 180),
8426
8270
  borderRadius: radius,
@@ -9916,7 +9760,7 @@ function CollectionBlock({ node, ctx, path }) {
9916
9760
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
9917
9761
  "div",
9918
9762
  {
9919
- "data-ai-grid": String(itemsPerRow),
9763
+ "data-ai-grid": "",
9920
9764
  "data-ohw-pricing-collection": isPricing ? "" : void 0,
9921
9765
  style: {
9922
9766
  display: "grid",
@@ -10218,7 +10062,7 @@ function renderNode(node, ctx, path, ambientAlign) {
10218
10062
  },
10219
10063
  children: [
10220
10064
  i > 0 && ctx.heroOverlapFullBleed ? (
10221
- // The section's `data-ai-section-inner` gave up its max-width/gutter so the base
10065
+ // The section's `data-ai-container` gave up its max-width/gutter so the base
10222
10066
  // layer (this group's i===0 sibling) can span edge-to-edge — reintroduced here,
10223
10067
  // scoped to just the overlay text, so a heading/copy/CTA stack doesn't stretch
10224
10068
  // the full section width instead of reading at the usual content width.
@@ -10637,7 +10481,6 @@ function AiTreeRenderer({
10637
10481
  ...fillHeight ? { "data-ai-fill-height": "" } : {},
10638
10482
  ...needsOverlayClearance ? { "data-ai-overlay-clearance": "" } : {},
10639
10483
  ...bgAttrs,
10640
- "data-ai-responsive": "",
10641
10484
  style: {
10642
10485
  position: "relative",
10643
10486
  padding: `${padCss} 0`,
@@ -10651,13 +10494,12 @@ function AiTreeRenderer({
10651
10494
  ...fillHeight ? { display: "grid" } : {}
10652
10495
  },
10653
10496
  children: [
10654
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
10655
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
10656
10497
  isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10498
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
10657
10499
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
10658
10500
  "div",
10659
10501
  {
10660
- "data-ai-section-inner": "",
10502
+ "data-ai-container": "",
10661
10503
  ...fullBleedMedia ? { "data-ai-full-bleed": "" } : {},
10662
10504
  style: {
10663
10505
  position: "relative",
@@ -10674,7 +10516,7 @@ function AiTreeRenderer({
10674
10516
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
10675
10517
  "div",
10676
10518
  {
10677
- "data-ai-columns": "",
10519
+ "data-ai-row": "",
10678
10520
  style: {
10679
10521
  display: "grid",
10680
10522
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -11336,10 +11178,9 @@ function applyAiSectionsToDom(state, options) {
11336
11178
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
11337
11179
  continue;
11338
11180
  }
11181
+ const isNewMount = !existing || !existing.container.isConnected;
11339
11182
  let container = existing?.container;
11340
- let isNewMount = false;
11341
11183
  if (!container || !container.isConnected) {
11342
- isNewMount = true;
11343
11184
  existing?.root.unmount();
11344
11185
  container?.remove();
11345
11186
  container = document.createElement("div");
@@ -11677,34 +11518,6 @@ function minSpanForFooterBlock(type) {
11677
11518
  return 3;
11678
11519
  }
11679
11520
  }
11680
- function readLiveSocialItems() {
11681
- const root = getFooterRoot();
11682
- const row = root?.querySelector("[data-ohw-socials-row]");
11683
- if (!row) return [];
11684
- const items = [];
11685
- for (const anchor of Array.from(row.querySelectorAll("a[href]"))) {
11686
- const href = anchor.getAttribute("href")?.trim() ?? "";
11687
- const aria = anchor.getAttribute("aria-label")?.trim().toLowerCase() ?? "";
11688
- const platform = FOOTER_SOCIAL_PLATFORMS.find((name) => aria === name) ?? FOOTER_SOCIAL_PLATFORMS.find((name) => href.toLowerCase().includes(name));
11689
- if (!platform || !/^https?:\/\//i.test(href)) continue;
11690
- items.push({ platform, href });
11691
- }
11692
- return items;
11693
- }
11694
- function syncSocialRowsFromDom(tree) {
11695
- const root = getFooterRoot();
11696
- if (!root) return;
11697
- const row = root.querySelector("[data-ohw-socials-row]");
11698
- const live = readLiveSocialItems();
11699
- if (row && live.length === 0) return;
11700
- for (const col of tree.columns) {
11701
- col.blocks = col.blocks.flatMap((block) => {
11702
- if (block.type !== "social-row") return [block];
11703
- if (!row) return [];
11704
- return [{ ...block, items: live.map((item) => ({ platform: item.platform, href: item.href })) }];
11705
- });
11706
- }
11707
- }
11708
11521
  function collectBrandBlocks(tree) {
11709
11522
  const brand = [];
11710
11523
  for (const col of tree.columns) {
@@ -11950,7 +11763,6 @@ function textOf2(el) {
11950
11763
  }
11951
11764
  function syncFooterTreeLinksFromDom(tree) {
11952
11765
  const next = cloneTree(tree);
11953
- syncSocialRowsFromDom(next);
11954
11766
  const domCols = listFooterColumns().map((col) => {
11955
11767
  const heading = getFooterColumnHeadingEl(col);
11956
11768
  const title = heading ? textOf2(heading) : void 0;
@@ -17655,7 +17467,7 @@ function apiBase() {
17655
17467
  function bridgeVersion() {
17656
17468
  try {
17657
17469
  if (true) {
17658
- return "0.1.160-next.429";
17470
+ return "0.1.161";
17659
17471
  }
17660
17472
  } catch {
17661
17473
  }
@@ -17669,7 +17481,7 @@ function stampBridgeVersionOnDocument() {
17669
17481
  return;
17670
17482
  }
17671
17483
  try {
17672
- const v = true ? "0.1.160-next.429" : "";
17484
+ const v = true ? "0.1.161" : "";
17673
17485
  if (v) {
17674
17486
  document.documentElement.setAttribute("data-ohw-bridge-version", v);
17675
17487
  window.__OHW_BRIDGE_VERSION__ = v;
@@ -18347,7 +18159,6 @@ function MediaOverlay({
18347
18159
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
18348
18160
  );
18349
18161
  }, [isVideo]);
18350
- const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
18351
18162
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
18352
18163
  const box = {
18353
18164
  position: "fixed",
@@ -18446,10 +18257,11 @@ function MediaOverlay({
18446
18257
  className: "flex items-center justify-center cursor-pointer",
18447
18258
  style: {
18448
18259
  ...box,
18449
- // Visual only. `pointer-events: auto` on this full-bleed rect covered the navbar
18450
- // (hero bg includes the absolute TopNav) and swallowed CTA press-drag. Text overlay
18451
- // and cueOnly also need clicks to fall through — Replace / video controls opt back in.
18452
- pointerEvents: "none",
18260
+ // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
18261
+ // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
18262
+ // in-document, pointer-events does it natively. The button below opts back in, so
18263
+ // Replace still works.
18264
+ pointerEvents: hover.hasTextOverlap || cueOnly ? "none" : "auto",
18453
18265
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
18454
18266
  // than hovered. Hover keeps the existing tinted preview.
18455
18267
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -18476,17 +18288,17 @@ function MediaOverlay({
18476
18288
  },
18477
18289
  children: [
18478
18290
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
18479
- replaceLabel
18291
+ isVideo ? "Replace video" : "Replace image"
18480
18292
  ]
18481
18293
  }
18482
18294
  ),
18483
- showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
18295
+ replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
18484
18296
  Button,
18485
18297
  {
18486
18298
  "data-ohw-media-overlay": "",
18487
18299
  variant: "outline",
18488
18300
  size: "sm",
18489
- "aria-label": replaceLabel,
18301
+ "aria-label": isVideo ? "Replace video" : "Replace image",
18490
18302
  className: "gap-1.5 cursor-pointer hover:bg-background",
18491
18303
  style: {
18492
18304
  ...OVERLAY_BUTTON_STYLE,
@@ -18509,7 +18321,7 @@ function MediaOverlay({
18509
18321
  },
18510
18322
  children: [
18511
18323
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
18512
- replaceMode === "full" ? replaceLabel : null
18324
+ replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
18513
18325
  ]
18514
18326
  }
18515
18327
  )
@@ -19103,7 +18915,7 @@ function AiSectionOverlay({
19103
18915
 
19104
18916
  // src/ui/ai-section/AiMediaStripOverlay.tsx
19105
18917
  var import_react12 = require("react");
19106
- var import_lucide_react9 = require("lucide-react");
18918
+ var import_lucide_react8 = require("lucide-react");
19107
18919
 
19108
18920
  // src/lib/ai-media-strip-tree.ts
19109
18921
  var MEDIA_STRIP_ITEM_MIN = 1;
@@ -19217,6 +19029,14 @@ function setMediaStripItemAsset(tree, index, assetId) {
19217
19029
  return true;
19218
19030
  });
19219
19031
  }
19032
+ function setMediaStripItemAlt(tree, index, alt) {
19033
+ return withMediaStrip(tree, (block) => {
19034
+ const items = block.items;
19035
+ if (index < 0 || index >= items.length) return false;
19036
+ items[index] = { ...items[index], alt };
19037
+ return true;
19038
+ });
19039
+ }
19220
19040
  function setMediaStripItemLink(tree, index, link) {
19221
19041
  return withMediaStrip(tree, (block) => {
19222
19042
  const items = block.items;
@@ -19234,117 +19054,11 @@ function setMediaStripItemLink(tree, index, link) {
19234
19054
  });
19235
19055
  }
19236
19056
 
19237
- // src/ui/dialog.tsx
19238
- var React9 = __toESM(require("react"), 1);
19239
- var import_radix_ui6 = require("radix-ui");
19240
- var import_lucide_react8 = require("lucide-react");
19241
- var import_jsx_runtime20 = require("react/jsx-runtime");
19242
- function Dialog2({
19243
- ...props
19244
- }) {
19245
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_radix_ui6.Dialog.Root, { "data-slot": "dialog", ...props });
19246
- }
19247
- function DialogPortal({
19248
- ...props
19249
- }) {
19250
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_radix_ui6.Dialog.Portal, { ...props });
19251
- }
19252
- function DialogOverlay({
19253
- className,
19254
- ...props
19255
- }) {
19256
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19257
- import_radix_ui6.Dialog.Overlay,
19258
- {
19259
- "data-slot": "dialog-overlay",
19260
- "data-ohw-link-modal-root": "",
19261
- className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
19262
- ...props
19263
- }
19264
- );
19265
- }
19266
- var DialogContent = React9.forwardRef(
19267
- ({ className, children, showCloseButton = true, container, ...props }, ref) => {
19268
- const positionMode = container ? "absolute" : "fixed";
19269
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(DialogPortal, { container: container ?? void 0, children: [
19270
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
19271
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
19272
- import_radix_ui6.Dialog.Content,
19273
- {
19274
- ref,
19275
- "data-slot": "dialog-content",
19276
- className: cn(
19277
- positionMode,
19278
- "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",
19279
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
19280
- container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
19281
- className
19282
- ),
19283
- onMouseDown: (e) => e.stopPropagation(),
19284
- ...props,
19285
- children: [
19286
- children,
19287
- showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19288
- import_radix_ui6.Dialog.Close,
19289
- {
19290
- type: "button",
19291
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
19292
- "aria-label": "Close",
19293
- children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react8.X, { size: 16, "aria-hidden": true })
19294
- }
19295
- ) : null
19296
- ]
19297
- }
19298
- )
19299
- ] });
19300
- }
19301
- );
19302
- DialogContent.displayName = import_radix_ui6.Dialog.Content.displayName;
19303
- function DialogHeader({
19304
- className,
19305
- ...props
19306
- }) {
19307
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
19308
- }
19309
- function DialogFooter({
19310
- className,
19311
- ...props
19312
- }) {
19313
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19314
- "div",
19315
- {
19316
- className: cn("flex items-center justify-end gap-2", className),
19317
- ...props
19318
- }
19319
- );
19320
- }
19321
- var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19322
- import_radix_ui6.Dialog.Title,
19323
- {
19324
- ref,
19325
- className: cn(
19326
- "text-lg font-semibold leading-none tracking-tight text-card-foreground",
19327
- className
19328
- ),
19329
- ...props
19330
- }
19331
- ));
19332
- DialogTitle.displayName = import_radix_ui6.Dialog.Title.displayName;
19333
- var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19334
- import_radix_ui6.Dialog.Description,
19335
- {
19336
- ref,
19337
- className: cn("text-sm text-muted-foreground", className),
19338
- ...props
19339
- }
19340
- ));
19341
- DialogDescription.displayName = import_radix_ui6.Dialog.Description.displayName;
19342
- var DialogClose = import_radix_ui6.Dialog.Close;
19343
-
19344
19057
  // src/ui/ai-section/AiMediaStripOverlay.tsx
19345
- var import_jsx_runtime21 = require("react/jsx-runtime");
19346
- var PRIMARY3 = "var(--ohw-primary, #0885FE)";
19347
- 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]";
19058
+ var import_jsx_runtime20 = require("react/jsx-runtime");
19059
+ var PRIMARY3 = "#0885FE";
19060
+ var OHW_BLUE = `var(--ohw-primary, ${PRIMARY3})`;
19061
+ var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY3}) 60%, #000000)`;
19348
19062
  function findMediaStripItemElement(sectionId, index) {
19349
19063
  const sectionEl = findByInstanceId(sectionId);
19350
19064
  if (!sectionEl) return null;
@@ -19354,35 +19068,14 @@ function findMediaStripItemElement(sectionId, index) {
19354
19068
  }
19355
19069
  return null;
19356
19070
  }
19357
- function itemRefFromTarget(target) {
19358
- if (!(target instanceof Element)) return null;
19359
- if (target.closest(CHROME_SELECTOR)) return null;
19360
- const itemEl = target.closest("[data-ohw-media-strip-item]");
19361
- if (!itemEl) return null;
19362
- const sectionEl = itemEl.closest("[data-ohw-section]");
19363
- const unit = sectionEl ? movableUnit(sectionEl) : null;
19364
- const sectionId = unit ? instanceIdOf(unit) || null : null;
19365
- const index = Number(itemEl.dataset.ohwMediaStripIndex);
19366
- if (!sectionId || Number.isNaN(index)) return null;
19367
- return { sectionId, index };
19368
- }
19369
- function sameRef(a, b) {
19370
- return a?.sectionId === b?.sectionId && a?.index === b?.index;
19371
- }
19372
- function rectOf(el) {
19071
+ function readRect2(ref) {
19072
+ const el = ref && findMediaStripItemElement(ref.sectionId, ref.index);
19373
19073
  if (!el) return null;
19374
19074
  const r2 = el.getBoundingClientRect();
19375
19075
  if (r2.width <= 0 || r2.height <= 0) return null;
19376
19076
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
19377
19077
  }
19378
- function readRect2(ref) {
19379
- return rectOf(ref && findMediaStripItemElement(ref.sectionId, ref.index));
19380
- }
19381
- function readStripRect(ref) {
19382
- const item = ref && findMediaStripItemElement(ref.sectionId, ref.index);
19383
- return rectOf(item?.closest("[data-ohw-media-strip]") ?? item);
19384
- }
19385
- function useLiveItemRect(ref, strip = false) {
19078
+ function useLiveItemRect(ref) {
19386
19079
  const [rect, setRect] = (0, import_react12.useState)(null);
19387
19080
  (0, import_react12.useEffect)(() => {
19388
19081
  if (!ref) {
@@ -19390,7 +19083,7 @@ function useLiveItemRect(ref, strip = false) {
19390
19083
  return;
19391
19084
  }
19392
19085
  const update = () => {
19393
- const next = strip ? readStripRect(ref) : readRect2(ref);
19086
+ const next = readRect2(ref);
19394
19087
  setRect(
19395
19088
  (prev) => prev && next && prev.top === next.top && prev.left === next.left && prev.width === next.width && prev.height === next.height ? prev : next
19396
19089
  );
@@ -19405,49 +19098,9 @@ function useLiveItemRect(ref, strip = false) {
19405
19098
  window.removeEventListener("resize", update);
19406
19099
  clearInterval(interval);
19407
19100
  };
19408
- }, [ref?.sectionId, ref?.index, strip]);
19101
+ }, [ref?.sectionId, ref?.index]);
19409
19102
  return rect;
19410
19103
  }
19411
- var TOOLBAR_STRIP_GAP = 52;
19412
- var TOOLBAR_STRIP_GAP_MOBILE = 42;
19413
- var TOOLBAR_EDGE_MARGIN2 = 4;
19414
- function StripToolbarSlot({ stripRect, children }) {
19415
- const slotRef = (0, import_react12.useRef)(null);
19416
- const [shift, setShift] = (0, import_react12.useState)(0);
19417
- const centerX = stripRect.left + stripRect.width / 2;
19418
- (0, import_react12.useLayoutEffect)(() => {
19419
- const slot = slotRef.current;
19420
- if (!slot) return;
19421
- const clamp = () => {
19422
- const w = slot.offsetWidth;
19423
- if (w <= 0) return;
19424
- const half = w / 2;
19425
- const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
19426
- setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
19427
- };
19428
- clamp();
19429
- const ro = new ResizeObserver(clamp);
19430
- ro.observe(slot);
19431
- return () => ro.disconnect();
19432
- }, [centerX, children]);
19433
- const mobile = window.innerWidth <= 480;
19434
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19435
- "div",
19436
- {
19437
- ref: slotRef,
19438
- "data-ohw-bridge": "",
19439
- "data-ohw-item-interaction": "",
19440
- className: "pointer-events-auto fixed",
19441
- style: {
19442
- top: stripRect.top - (mobile ? TOOLBAR_STRIP_GAP_MOBILE : TOOLBAR_STRIP_GAP),
19443
- left: centerX + shift,
19444
- transform: "translate(-50%, -100%)",
19445
- zIndex: 2147483645
19446
- },
19447
- children
19448
- }
19449
- );
19450
- }
19451
19104
  function requestValidateTree(postToParent2, tree) {
19452
19105
  return new Promise((resolve) => {
19453
19106
  const id = `ai-validate-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -19484,72 +19137,116 @@ function requestAssetPick(postToParent2) {
19484
19137
  postToParent2({ type: "ow:asset-pick", id });
19485
19138
  });
19486
19139
  }
19487
- function ToolbarAction({
19140
+ function Pill({
19488
19141
  label,
19489
19142
  onClick,
19490
- disabled = false,
19491
- active = false,
19492
- children
19493
- }) {
19494
- const button = /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, active, onClick, className: "size-8", children });
19495
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19496
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "inline-flex", children: button }) : button }),
19497
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19498
- ] });
19499
- }
19500
- function ToolbarSeparator() {
19501
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19502
- }
19503
- function OptionMenu({
19504
- label,
19505
- value,
19506
- options,
19507
- onChange
19143
+ disabled,
19144
+ active
19508
19145
  }) {
19509
- const current = options.find((o) => o.value === value) ?? options[0];
19510
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19511
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xs text-muted-foreground", children: label }),
19512
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DropdownMenu, { modal: false, children: [
19513
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19514
- "button",
19515
- {
19516
- type: "button",
19517
- "aria-label": label,
19518
- 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",
19519
- children: [
19520
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "flex-1 text-left", children: current.label }),
19521
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ChevronDown, { size: 16, style: { pointerEvents: "none" } })
19522
- ]
19523
- }
19524
- ) }),
19525
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[132px] min-w-[132px] p-1", children: options.map((option) => {
19526
- const selected = option.value === value;
19527
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19528
- DropdownMenuItem,
19146
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19147
+ "button",
19148
+ {
19149
+ type: "button",
19150
+ onClick,
19151
+ disabled,
19152
+ style: {
19153
+ height: 28,
19154
+ padding: "0 10px",
19155
+ display: "inline-flex",
19156
+ alignItems: "center",
19157
+ gap: 4,
19158
+ borderRadius: 999,
19159
+ border: "none",
19160
+ cursor: disabled ? "default" : "pointer",
19161
+ background: active ? OHW_BLUE_DARK : "#ffffff",
19162
+ color: active ? "#ffffff" : OHW_BLUE_DARK,
19163
+ opacity: disabled ? 0.4 : 1,
19164
+ fontSize: 12,
19165
+ lineHeight: "16px",
19166
+ fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
19167
+ whiteSpace: "nowrap",
19168
+ flex: "0 0 auto"
19169
+ },
19170
+ children: label
19171
+ }
19172
+ );
19173
+ }
19174
+ function AltInput({ value, onCommit }) {
19175
+ const [draft, setDraft] = (0, import_react12.useState)(value);
19176
+ (0, import_react12.useEffect)(() => setDraft(value), [value]);
19177
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
19178
+ "div",
19179
+ {
19180
+ style: {
19181
+ display: "flex",
19182
+ alignItems: "center",
19183
+ gap: 6,
19184
+ height: 28,
19185
+ padding: "0 12px",
19186
+ borderRadius: 999,
19187
+ background: "#ffffff",
19188
+ width: "100%",
19189
+ maxWidth: 260,
19190
+ boxSizing: "border-box"
19191
+ },
19192
+ children: [
19193
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19194
+ "span",
19529
19195
  {
19530
- role: "menuitemradio",
19531
- "aria-checked": selected,
19532
- onSelect: () => onChange(option.value),
19533
- className: cn("grid grid-cols-[1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
19534
- children: [
19535
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: option.label }),
19536
- selected && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Check, { size: 16, className: "text-bridge-primary" })
19537
- ]
19538
- },
19539
- option.value
19540
- );
19541
- }) })
19542
- ] })
19543
- ] });
19196
+ style: {
19197
+ flex: "0 0 auto",
19198
+ color: OHW_BLUE_DARK,
19199
+ fontSize: 10,
19200
+ fontWeight: 700,
19201
+ textTransform: "uppercase",
19202
+ letterSpacing: 0.4
19203
+ },
19204
+ children: "Alt"
19205
+ }
19206
+ ),
19207
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19208
+ "input",
19209
+ {
19210
+ type: "text",
19211
+ value: draft,
19212
+ onChange: (e) => setDraft(e.target.value),
19213
+ onBlur: () => {
19214
+ if (draft !== value) onCommit(draft);
19215
+ },
19216
+ onKeyDown: (e) => {
19217
+ if (e.key === "Enter") e.currentTarget.blur();
19218
+ if (e.key === "Escape") {
19219
+ setDraft(value);
19220
+ e.currentTarget.blur();
19221
+ }
19222
+ },
19223
+ placeholder: "Describe this image",
19224
+ "aria-label": "Image alt text",
19225
+ style: {
19226
+ flex: 1,
19227
+ minWidth: 0,
19228
+ border: "none",
19229
+ outline: "none",
19230
+ background: "transparent",
19231
+ fontSize: 12,
19232
+ lineHeight: "16px",
19233
+ fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
19234
+ color: "#1f2937"
19235
+ }
19236
+ }
19237
+ )
19238
+ ]
19239
+ }
19240
+ );
19544
19241
  }
19545
19242
  var VARIANTS = [
19546
19243
  { value: "logos", label: "Logos" },
19547
19244
  { value: "reel", label: "Reel" }
19548
19245
  ];
19549
19246
  var ALIGNS = [
19550
- { value: "left", label: "Align left", Icon: import_lucide_react9.AlignLeft },
19551
- { value: "center", label: "Align center", Icon: import_lucide_react9.AlignCenter },
19552
- { value: "right", label: "Align right", Icon: import_lucide_react9.AlignRight }
19247
+ { value: "left", label: "Left" },
19248
+ { value: "center", label: "Center" },
19249
+ { value: "right", label: "Right" }
19553
19250
  ];
19554
19251
  var HOVERS = [
19555
19252
  { value: "none", label: "None" },
@@ -19557,104 +19254,104 @@ var HOVERS = [
19557
19254
  { value: "zoom", label: "Zoom" },
19558
19255
  { value: "fade", label: "Fade" }
19559
19256
  ];
19257
+ var TREATMENTS = [
19258
+ { value: "color", label: "Color" },
19259
+ { value: "grayscale", label: "Grayscale" }
19260
+ ];
19560
19261
  var HTTP_URL_RE = /^https?:\/\/\S+$/i;
19561
- function LinkModal({
19562
- href,
19563
- linkTarget,
19564
- onClose,
19565
- onSave
19566
- }) {
19567
- const [draft, setDraft] = (0, import_react12.useState)(href);
19568
- const [newTab, setNewTab] = (0, import_react12.useState)(linkTarget !== "same-tab");
19262
+ function LinkInput({ value, onCommit }) {
19263
+ const [draft, setDraft] = (0, import_react12.useState)(value);
19569
19264
  const [invalid, setInvalid] = (0, import_react12.useState)(false);
19570
- const save = () => {
19265
+ (0, import_react12.useEffect)(() => {
19266
+ setDraft(value);
19267
+ setInvalid(false);
19268
+ }, [value]);
19269
+ const commit = () => {
19571
19270
  const next = draft.trim();
19572
- if (next && !HTTP_URL_RE.test(next)) {
19573
- setInvalid(true);
19574
- return;
19575
- }
19576
- onSave(next, newTab ? "new-tab" : "same-tab");
19577
- onClose();
19271
+ if (next === value) return setInvalid(false);
19272
+ if (next && !HTTP_URL_RE.test(next)) return setInvalid(true);
19273
+ setInvalid(false);
19274
+ onCommit(next);
19578
19275
  };
19579
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Dialog2, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DialogContent, { "data-ohw-link-modal-root": "", className: "max-w-[400px] gap-4 p-5", children: [
19580
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DialogHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DialogTitle, { className: "text-base", children: "Image link" }) }),
19581
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex flex-col gap-3", children: [
19582
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19583
- "input",
19584
- {
19585
- autoFocus: true,
19586
- type: "text",
19587
- inputMode: "url",
19588
- value: draft,
19589
- onChange: (e) => {
19590
- setDraft(e.target.value);
19591
- setInvalid(false);
19592
- },
19593
- onKeyDown: (e) => {
19594
- if (e.key === "Enter") save();
19595
- },
19596
- placeholder: "https://example.com",
19597
- "aria-label": "Image link URL",
19598
- "aria-invalid": invalid,
19599
- className: cn(
19600
- "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",
19601
- invalid && "border-destructive focus:border-destructive"
19602
- )
19603
- }
19604
- ),
19605
- invalid && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-xs text-destructive", children: "Enter a full URL starting with http:// or https://" }),
19606
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("label", { className: "flex items-center gap-2 text-[13px] text-foreground", children: [
19607
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("input", { type: "checkbox", checked: newTab, onChange: (e) => setNewTab(e.target.checked) }),
19608
- "Open in new tab"
19609
- ] })
19610
- ] }),
19611
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DialogFooter, { children: [
19612
- href && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19613
- "button",
19614
- {
19615
- type: "button",
19616
- onClick: () => {
19617
- onSave("", "new-tab");
19618
- onClose();
19619
- },
19620
- className: "mr-auto rounded-[6px] px-3 py-2 text-[13px] font-medium text-destructive transition-colors hover:bg-muted",
19621
- children: "Remove link"
19622
- }
19623
- ),
19624
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19625
- "button",
19626
- {
19627
- type: "button",
19628
- onClick: onClose,
19629
- className: "rounded-[6px] px-3 py-2 text-[13px] font-medium text-foreground transition-colors hover:bg-muted",
19630
- children: "Cancel"
19631
- }
19632
- ),
19633
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19634
- "button",
19635
- {
19636
- type: "button",
19637
- onClick: save,
19638
- className: "rounded-[6px] bg-bridge-primary px-3 py-2 text-[13px] font-medium text-primary-foreground transition-opacity hover:opacity-90",
19639
- children: "Save"
19640
- }
19641
- )
19642
- ] })
19643
- ] }) });
19276
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
19277
+ "div",
19278
+ {
19279
+ style: {
19280
+ display: "flex",
19281
+ alignItems: "center",
19282
+ gap: 6,
19283
+ height: 28,
19284
+ padding: "0 12px",
19285
+ borderRadius: 999,
19286
+ background: "#ffffff",
19287
+ width: "100%",
19288
+ maxWidth: 260,
19289
+ boxSizing: "border-box",
19290
+ boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
19291
+ },
19292
+ children: [
19293
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19294
+ "span",
19295
+ {
19296
+ style: {
19297
+ flex: "0 0 auto",
19298
+ color: OHW_BLUE_DARK,
19299
+ fontSize: 10,
19300
+ fontWeight: 700,
19301
+ textTransform: "uppercase",
19302
+ letterSpacing: 0.4
19303
+ },
19304
+ children: "Link"
19305
+ }
19306
+ ),
19307
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19308
+ "input",
19309
+ {
19310
+ type: "text",
19311
+ inputMode: "url",
19312
+ value: draft,
19313
+ onChange: (e) => setDraft(e.target.value),
19314
+ onBlur: commit,
19315
+ onKeyDown: (e) => {
19316
+ if (e.key === "Enter") e.currentTarget.blur();
19317
+ if (e.key === "Escape") {
19318
+ setDraft(value);
19319
+ setInvalid(false);
19320
+ e.currentTarget.blur();
19321
+ }
19322
+ },
19323
+ placeholder: "https://example.com",
19324
+ "aria-label": "Image link URL",
19325
+ "aria-invalid": invalid,
19326
+ style: {
19327
+ flex: 1,
19328
+ minWidth: 0,
19329
+ border: "none",
19330
+ outline: "none",
19331
+ background: "transparent",
19332
+ fontSize: 12,
19333
+ lineHeight: "16px",
19334
+ fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
19335
+ color: "#1f2937"
19336
+ }
19337
+ }
19338
+ )
19339
+ ]
19340
+ }
19341
+ );
19644
19342
  }
19645
19343
  function MediaStripToolbar({
19646
19344
  entry,
19647
19345
  index,
19648
19346
  pending,
19649
19347
  error,
19650
- itemEditOn,
19651
- onToggleItemEdit,
19652
19348
  onSetVariant,
19653
19349
  onSetAlign,
19654
19350
  onSetHover,
19655
19351
  onSetTreatment,
19656
19352
  onResize,
19657
- onOpenLink,
19353
+ onSetAlt,
19354
+ onSetLink,
19658
19355
  onAddItem,
19659
19356
  onRemoveItem,
19660
19357
  onChangeImage,
@@ -19663,102 +19360,81 @@ function MediaStripToolbar({
19663
19360
  }) {
19664
19361
  const items = mediaStripItems(entry.tree);
19665
19362
  const itemCount = items.length;
19363
+ const currentAlt = items[index]?.alt ?? "";
19364
+ const currentHref = items[index]?.href ?? "";
19365
+ const currentTarget = items[index]?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
19666
19366
  const slots = findMediaStripSlots(entry.tree);
19667
19367
  const variant = slots?.variant === "reel" ? "reel" : "logos";
19668
19368
  const align = slots?.align === "left" || slots?.align === "right" ? slots.align : "center";
19669
19369
  const hover = slots?.hover === "none" || slots?.hover === "lift" || slots?.hover === "zoom" || slots?.hover === "fade" ? slots.hover : "lift";
19670
19370
  const treatment = slots?.treatment === "color" || slots?.treatment === "grayscale" ? slots.treatment : variant === "logos" ? "grayscale" : "color";
19671
19371
  const sizeValue = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19672
- const mobile = window.innerWidth <= 480;
19673
- const gatedLabel = (label) => itemEditOn ? label : "Turn on the star to edit this image";
19674
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19372
+ const isMobile = window.innerWidth <= 480;
19373
+ const rowStyle = isMobile ? {
19374
+ display: "flex",
19375
+ gap: 6,
19376
+ flexWrap: "nowrap",
19377
+ justifyContent: "center",
19378
+ overflowX: "auto",
19379
+ WebkitOverflowScrolling: "touch",
19380
+ width: "100%",
19381
+ scrollbarWidth: "none"
19382
+ } : { display: "flex", gap: 6, flexWrap: "nowrap", justifyContent: "center" };
19383
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
19675
19384
  "div",
19676
19385
  {
19677
19386
  "data-ohw-bridge": "",
19678
19387
  "data-ohw-toolbar": "",
19679
19388
  onMouseDown: (e) => e.stopPropagation(),
19680
- className: cn("flex flex-col items-center gap-1.5 font-sans", pending && "pointer-events-none opacity-60"),
19681
- style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
19389
+ style: {
19390
+ display: "flex",
19391
+ flexDirection: "column",
19392
+ gap: 6,
19393
+ alignItems: "center",
19394
+ background: OHW_BLUE,
19395
+ padding: 8,
19396
+ borderRadius: 12,
19397
+ boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
19398
+ pointerEvents: pending ? "none" : "auto",
19399
+ opacity: pending ? 0.6 : 1,
19400
+ maxWidth: isMobile ? "calc(100vw - 24px)" : "none"
19401
+ },
19682
19402
  children: [
19683
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19684
- "div",
19685
- {
19686
- className: cn(
19687
- "flex items-center gap-1 rounded-[10px] border border-border bg-background p-1 whitespace-nowrap",
19688
- "shadow-[0px_10px_15px_-3px_rgba(0,0,0,0.1),0px_4px_6px_-4px_rgba(0,0,0,0.1)]",
19689
- mobile ? "flex-wrap justify-center" : "flex-nowrap"
19690
- ),
19691
- children: [
19692
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19693
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19694
- CustomToolbarButton,
19695
- {
19696
- type: "button",
19697
- "aria-label": itemEditOn ? "Done editing this image" : "Edit this image",
19698
- "aria-pressed": itemEditOn,
19699
- onClick: onToggleItemEdit,
19700
- className: "group size-8",
19701
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19702
- import_lucide_react9.Star,
19703
- {
19704
- size: 16,
19705
- fill: "currentColor",
19706
- strokeWidth: 0,
19707
- className: cn(
19708
- "transition-colors",
19709
- itemEditOn ? "text-[#f59e0b] group-hover:text-[#d97706]" : "text-[#d6d3d1] group-hover:text-[#fbbf24]"
19710
- ),
19711
- style: { pointerEvents: "none" }
19712
- }
19713
- )
19714
- }
19715
- ) }),
19716
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: itemEditOn ? "Done editing this image" : "Edit this image" })
19717
- ] }),
19718
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19719
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: gatedLabel("Change image"), onClick: onChangeImage, disabled: !itemEditOn, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Image, { size: 16, style: { pointerEvents: "none" } }) }),
19720
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: gatedLabel("Link"), onClick: onOpenLink, disabled: !itemEditOn, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Link, { size: 16, style: { pointerEvents: "none" } }) }),
19721
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: gatedLabel("Move left"), onClick: onMoveLeft, disabled: !itemEditOn || index <= 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ArrowLeft, { size: 16, style: { pointerEvents: "none" } }) }),
19722
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19723
- ToolbarAction,
19724
- {
19725
- label: gatedLabel("Move right"),
19726
- onClick: onMoveRight,
19727
- disabled: !itemEditOn || index >= itemCount - 1,
19728
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ArrowRight, { size: 16, style: { pointerEvents: "none" } })
19729
- }
19730
- ),
19731
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: gatedLabel("Remove image"), onClick: onRemoveItem, disabled: !itemEditOn || itemCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ImageMinus, { size: 16, style: { pointerEvents: "none" } }) }),
19732
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19733
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(OptionMenu, { label: "Layout", value: variant, options: VARIANTS, onChange: onSetVariant }),
19734
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(OptionMenu, { label: "Hover", value: hover, options: HOVERS, onChange: onSetHover }),
19735
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19736
- ToolbarAction,
19737
- {
19738
- label: treatment === "grayscale" ? "Show in color" : "Show grayscale",
19739
- active: treatment === "grayscale",
19740
- onClick: () => onSetTreatment(treatment === "grayscale" ? "color" : "grayscale"),
19741
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Palette, { size: 16, style: { pointerEvents: "none" } })
19742
- }
19743
- ),
19744
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19745
- ALIGNS.map(({ value, label, Icon }) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label, active: align === value, onClick: () => onSetAlign(value), children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Icon, { size: 16, style: { pointerEvents: "none" } }) }, value)),
19746
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19747
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Smaller", onClick: () => onResize(-8), children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Minus, { size: 16, style: { pointerEvents: "none" } }) }),
19748
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "min-w-[40px] text-center text-xs tabular-nums text-muted-foreground", children: [
19749
- sizeValue,
19750
- "px"
19751
- ] }),
19752
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Larger", onClick: () => onResize(8), children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Plus, { size: 16, style: { pointerEvents: "none" } }) }),
19753
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19754
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Add image", onClick: onAddItem, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ImagePlus, { size: 16, style: { pointerEvents: "none" } }) })
19755
- ]
19756
- }
19757
- ),
19758
- error && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19403
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
19404
+ VARIANTS.map((v) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: v.label, active: variant === v.value, onClick: () => onSetVariant(v.value) }, v.value)),
19405
+ ALIGNS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: a.label, active: align === a.value, onClick: () => onSetAlign(a.value) }, a.value))
19406
+ ] }),
19407
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
19408
+ HOVERS.map((h) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: h.label, active: hover === h.value, onClick: () => onSetHover(h.value) }, h.value)),
19409
+ TREATMENTS.map((t) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: t.label, active: treatment === t.value, onClick: () => onSetTreatment(t.value) }, t.value))
19410
+ ] }),
19411
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
19412
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2212", onClick: () => onResize(-8) }),
19413
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { style: { color: "#ffffff", fontSize: 12, minWidth: 34, textAlign: "center" }, children: [
19414
+ sizeValue,
19415
+ "px"
19416
+ ] }),
19417
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "+", onClick: () => onResize(8) })
19418
+ ] }),
19419
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(AltInput, { value: currentAlt, onCommit: onSetAlt }) }),
19420
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(LinkInput, { value: currentHref, onCommit: (href) => onSetLink(href, currentTarget) }) }),
19421
+ currentHref && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
19422
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "New tab", active: currentTarget === "new-tab", onClick: () => onSetLink(currentHref, "new-tab") }),
19423
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "Same tab", active: currentTarget === "same-tab", onClick: () => onSetLink(currentHref, "same-tab") })
19424
+ ] }),
19425
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
19426
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "Change Image", onClick: onChangeImage }),
19427
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "+ Item", onClick: onAddItem }),
19428
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2212 Item", onClick: onRemoveItem, disabled: itemCount <= 1 })
19429
+ ] }),
19430
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
19431
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2190 Move", onClick: onMoveLeft, disabled: index <= 0 }),
19432
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2192 Move", onClick: onMoveRight, disabled: index >= itemCount - 1 })
19433
+ ] }),
19434
+ error && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
19759
19435
  ]
19760
19436
  }
19761
- ) });
19437
+ );
19762
19438
  }
19763
19439
  function findMediaStripSlots(tree) {
19764
19440
  const stack = tree.rows.flatMap((row) => row.blocks);
@@ -19777,23 +19453,31 @@ function AiMediaStripOverlay({
19777
19453
  }) {
19778
19454
  const [hover, setHover] = (0, import_react12.useState)(null);
19779
19455
  const [active, setActive] = (0, import_react12.useState)(null);
19780
- const [itemEditOn, setItemEditOn] = (0, import_react12.useState)(false);
19781
- const [linkOpen, setLinkOpen] = (0, import_react12.useState)(false);
19782
19456
  const [pending, setPending] = (0, import_react12.useState)(false);
19783
19457
  const [error, setError] = (0, import_react12.useState)(null);
19784
19458
  const activeRef = (0, import_react12.useRef)(null);
19785
19459
  activeRef.current = active;
19786
- const linkOpenRef = (0, import_react12.useRef)(false);
19787
- linkOpenRef.current = linkOpen;
19788
19460
  (0, import_react12.useEffect)(() => {
19789
19461
  const onMove = (e) => {
19790
- const ref = itemRefFromTarget(e.target);
19791
19462
  const t = e.target;
19792
- const overEditable = Boolean(t?.closest?.("[data-ohw-editable]"));
19793
- setHover((prev) => {
19794
- const next = ref && !overEditable ? ref : null;
19795
- return sameRef(prev, next) ? prev : next;
19796
- });
19463
+ if (!(t instanceof HTMLElement)) return;
19464
+ 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]")) {
19465
+ return;
19466
+ }
19467
+ const itemEl = t.closest("[data-ohw-media-strip-item]");
19468
+ if (!itemEl) {
19469
+ setHover(null);
19470
+ return;
19471
+ }
19472
+ const sectionEl = itemEl.closest("[data-ohw-section]");
19473
+ const unit = sectionEl ? movableUnit(sectionEl) : null;
19474
+ const sectionId = unit ? instanceIdOf(unit) || null : null;
19475
+ const index = Number(itemEl.dataset.ohwMediaStripIndex);
19476
+ if (!sectionId || Number.isNaN(index)) {
19477
+ setHover(null);
19478
+ return;
19479
+ }
19480
+ setHover({ sectionId, index });
19797
19481
  };
19798
19482
  const onLeave = () => setHover(null);
19799
19483
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -19805,25 +19489,23 @@ function AiMediaStripOverlay({
19805
19489
  }, []);
19806
19490
  (0, import_react12.useEffect)(() => {
19807
19491
  const onClick = (e) => {
19492
+ const current = activeRef.current;
19493
+ if (!current) return;
19808
19494
  const t = e.target;
19809
- if (!(t instanceof Element)) return;
19810
- if (t.closest(CHROME_SELECTOR)) return;
19811
- const ref = itemRefFromTarget(t);
19812
- if (sameRef(activeRef.current, ref)) return;
19813
- if (ref) clearInteractionChromeRef.current();
19814
- setItemEditOn(false);
19495
+ if (!(t instanceof HTMLElement)) return;
19496
+ if (t.closest("[data-ohw-item-interaction]")) return;
19497
+ const itemEl = findMediaStripItemElement(current.sectionId, current.index);
19498
+ if (itemEl && itemEl.contains(t)) return;
19499
+ setActive(null);
19815
19500
  setError(null);
19816
- setActive(ref);
19817
19501
  };
19818
19502
  document.addEventListener("click", onClick, true);
19819
19503
  return () => document.removeEventListener("click", onClick, true);
19820
- }, [clearInteractionChromeRef]);
19504
+ }, []);
19821
19505
  (0, import_react12.useEffect)(() => {
19822
19506
  const onBlur = () => {
19823
19507
  if (!activeRef.current) return;
19824
19508
  setActive(null);
19825
- setItemEditOn(false);
19826
- setLinkOpen(false);
19827
19509
  setError(null);
19828
19510
  };
19829
19511
  window.addEventListener("blur", onBlur);
@@ -19833,10 +19515,7 @@ function AiMediaStripOverlay({
19833
19515
  const onKeyDown = (e) => {
19834
19516
  if (e.key !== "Escape") return;
19835
19517
  if (!activeRef.current) return;
19836
- if (linkOpenRef.current) return;
19837
- if (document.querySelector('[data-ohw-more-menu][data-state="open"]')) return;
19838
19518
  setActive(null);
19839
- setItemEditOn(false);
19840
19519
  setError(null);
19841
19520
  };
19842
19521
  document.addEventListener("keydown", onKeyDown);
@@ -19870,133 +19549,137 @@ function AiMediaStripOverlay({
19870
19549
  tree: nextTree,
19871
19550
  label: entry.label,
19872
19551
  media: extraMedia ? { ...entry.media, ...extraMedia } : entry.media,
19873
- ...entry.brand ? { brand: entry.brand } : {},
19874
- // The user is already looking at the strip they just edited — recentering the viewport
19875
- // on every edit reads as the section jumping around.
19876
- skipScrollIntoView: true
19552
+ ...entry.brand ? { brand: entry.brand } : {}
19877
19553
  });
19878
19554
  return true;
19879
19555
  },
19880
19556
  [findEntry, postToParent2, applyTreeLocalRef]
19881
19557
  );
19882
19558
  const activeEntry = active ? findEntry(active.sectionId) : null;
19559
+ const hoverHandleRect = useLiveItemRect(!active && hover ? hover : null);
19883
19560
  const activeItemRect = useLiveItemRect(active);
19884
- const activeStripRect = useLiveItemRect(active, true);
19885
- const hoverRect = useLiveItemRect(hover && !sameRef(hover, active) ? hover : null);
19886
- const activeItems = activeEntry ? mediaStripItems(activeEntry.tree) : [];
19887
- const activeItem = active ? activeItems[active.index] : void 0;
19888
- const activeHref = activeItem?.href ?? "";
19889
- const activeTarget = activeItem?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
19890
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19891
- hoverRect && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19892
- activeItemRect && activeStripRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19893
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: activeItemRect, state: "active-top" }),
19894
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StripToolbarSlot, { stripRect: activeStripRect, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19895
- MediaStripToolbar,
19896
- {
19897
- entry: activeEntry,
19898
- index: active.index,
19899
- pending,
19900
- error,
19901
- itemEditOn,
19902
- onToggleItemEdit: () => setItemEditOn((on) => !on),
19903
- onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
19904
- onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
19905
- onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
19906
- onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
19907
- onResize: (delta) => {
19908
- const slots = findMediaStripSlots(activeEntry.tree);
19909
- const variant = slots?.variant === "reel" ? "reel" : "logos";
19910
- const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19911
- void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
19912
- },
19913
- onOpenLink: () => setLinkOpen(true),
19914
- onAddItem: () => {
19915
- void requestAssetPick(postToParent2).then((picked) => {
19916
- if (!picked) return;
19917
- const item = { assetId: picked.assetId, alt: "Logo" };
19561
+ const TOOLBAR_APPROX_H = 170;
19562
+ const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
19563
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
19564
+ hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19565
+ "button",
19566
+ {
19567
+ type: "button",
19568
+ "data-ohw-toolbar": "",
19569
+ onClick: () => {
19570
+ clearInteractionChromeRef.current();
19571
+ setActive(hover);
19572
+ },
19573
+ "aria-label": "Configure media item",
19574
+ style: {
19575
+ position: "fixed",
19576
+ top: hoverHandleRect.top + 8,
19577
+ left: hoverHandleRect.left + hoverHandleRect.width - 36,
19578
+ width: 28,
19579
+ height: 28,
19580
+ borderRadius: 8,
19581
+ border: `1.5px dashed ${PRIMARY3}`,
19582
+ background: "#ffffff",
19583
+ color: PRIMARY3,
19584
+ display: "flex",
19585
+ alignItems: "center",
19586
+ justifyContent: "center",
19587
+ cursor: "pointer",
19588
+ zIndex: 2147483643,
19589
+ fontSize: 14,
19590
+ fontWeight: 700
19591
+ },
19592
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react8.Star, { size: 14, style: { pointerEvents: "none" } })
19593
+ }
19594
+ ),
19595
+ activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19596
+ ItemInteractionLayer,
19597
+ {
19598
+ rect: activeItemRect,
19599
+ state: toolbarState,
19600
+ toolbar: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19601
+ MediaStripToolbar,
19602
+ {
19603
+ entry: activeEntry,
19604
+ index: active.index,
19605
+ pending,
19606
+ error,
19607
+ onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
19608
+ onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
19609
+ onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
19610
+ onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
19611
+ onResize: (delta) => {
19612
+ const slots = findMediaStripSlots(activeEntry.tree);
19613
+ const variant = slots?.variant === "reel" ? "reel" : "logos";
19614
+ const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19615
+ void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
19616
+ },
19617
+ onSetAlt: (alt) => {
19618
+ void runMutation(active.sectionId, (tree) => setMediaStripItemAlt(tree, active.index, alt));
19619
+ },
19620
+ onSetLink: (href, linkTarget) => {
19918
19621
  void runMutation(
19919
19622
  active.sectionId,
19920
- (tree) => addMediaStripItem(tree, item, active.index),
19921
- { [picked.assetId]: picked.url }
19922
- ).then((applied) => {
19923
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19623
+ (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
19624
+ );
19625
+ },
19626
+ onAddItem: () => {
19627
+ void requestAssetPick(postToParent2).then((picked) => {
19628
+ if (!picked) return;
19629
+ const item = { assetId: picked.assetId, alt: "Logo" };
19630
+ void runMutation(
19631
+ active.sectionId,
19632
+ (tree) => addMediaStripItem(tree, item, active.index),
19633
+ { [picked.assetId]: picked.url }
19634
+ ).then((applied) => {
19635
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19636
+ });
19924
19637
  });
19925
- });
19926
- },
19927
- onChangeImage: () => {
19928
- void requestAssetPick(postToParent2).then((picked) => {
19929
- if (!picked) return;
19930
- void runMutation(
19931
- active.sectionId,
19932
- (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
19933
- { [picked.assetId]: picked.url }
19638
+ },
19639
+ onChangeImage: () => {
19640
+ void requestAssetPick(postToParent2).then((picked) => {
19641
+ if (!picked) return;
19642
+ void runMutation(
19643
+ active.sectionId,
19644
+ (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
19645
+ { [picked.assetId]: picked.url }
19646
+ );
19647
+ });
19648
+ },
19649
+ onRemoveItem: () => {
19650
+ void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
19651
+ (applied) => {
19652
+ if (applied) setActive(null);
19653
+ }
19934
19654
  );
19935
- });
19936
- },
19937
- onRemoveItem: () => {
19938
- void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
19939
- (applied) => {
19940
- if (applied) setActive(null);
19941
- }
19942
- );
19943
- },
19944
- onMoveLeft: () => {
19945
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
19946
- (applied) => {
19947
- if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
19948
- }
19949
- );
19950
- },
19951
- onMoveRight: () => {
19952
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
19953
- (applied) => {
19954
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19955
- }
19956
- );
19957
- }
19958
- }
19959
- ) }),
19960
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19961
- "div",
19962
- {
19963
- "data-ohw-bridge": "",
19964
- "data-ohw-item-interaction": "",
19965
- "aria-hidden": true,
19966
- className: "pointer-events-none fixed rounded-[4px] px-[7px] py-[3px] font-sans text-[11px] font-medium leading-none text-primary-foreground whitespace-nowrap",
19967
- style: {
19968
- top: activeItemRect.top + activeItemRect.height + 10,
19969
- left: activeItemRect.left - 5,
19970
- background: PRIMARY3,
19971
- zIndex: 2147483644
19972
- },
19973
- children: "Media item"
19974
- }
19975
- ),
19976
- linkOpen && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19977
- LinkModal,
19978
- {
19979
- href: activeHref,
19980
- linkTarget: activeTarget,
19981
- onClose: () => setLinkOpen(false),
19982
- onSave: (href, linkTarget) => {
19983
- void runMutation(
19984
- active.sectionId,
19985
- (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
19986
- );
19655
+ },
19656
+ onMoveLeft: () => {
19657
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
19658
+ (applied) => {
19659
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
19660
+ }
19661
+ );
19662
+ },
19663
+ onMoveRight: () => {
19664
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
19665
+ (applied) => {
19666
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19667
+ }
19668
+ );
19669
+ }
19987
19670
  }
19988
- }
19989
- )
19990
- ] })
19671
+ )
19672
+ }
19673
+ )
19991
19674
  ] });
19992
19675
  }
19993
19676
 
19994
19677
  // src/ui/ai-section/AiPricingCardOverlay.tsx
19995
19678
  var import_react13 = require("react");
19996
- var import_lucide_react10 = require("lucide-react");
19997
- var import_jsx_runtime22 = require("react/jsx-runtime");
19679
+ var import_lucide_react9 = require("lucide-react");
19680
+ var import_jsx_runtime21 = require("react/jsx-runtime");
19998
19681
  var PRIMARY4 = "var(--ohw-primary, #0885FE)";
19999
- 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]";
19682
+ 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]";
20000
19683
  function findPricingCardElement(sectionId, index) {
20001
19684
  const sectionEl = findByInstanceId(sectionId);
20002
19685
  if (!sectionEl) return null;
@@ -20008,7 +19691,7 @@ function findPricingCardElement(sectionId, index) {
20008
19691
  }
20009
19692
  function cardRefFromTarget(target) {
20010
19693
  if (!(target instanceof Element)) return null;
20011
- if (target.closest(CHROME_SELECTOR2)) return null;
19694
+ if (target.closest(CHROME_SELECTOR)) return null;
20012
19695
  const cardEl = target.closest("[data-ohw-pricing-card]");
20013
19696
  if (!cardEl) return null;
20014
19697
  const sectionEl = cardEl.closest("[data-ohw-section]");
@@ -20018,21 +19701,21 @@ function cardRefFromTarget(target) {
20018
19701
  if (!sectionId || Number.isNaN(index)) return null;
20019
19702
  return { sectionId, index };
20020
19703
  }
20021
- function sameRef2(a, b) {
19704
+ function sameRef(a, b) {
20022
19705
  return a?.sectionId === b?.sectionId && a?.index === b?.index;
20023
19706
  }
20024
- function rectOf2(el) {
19707
+ function rectOf(el) {
20025
19708
  if (!el) return null;
20026
19709
  const r2 = el.getBoundingClientRect();
20027
19710
  if (r2.width <= 0 || r2.height <= 0) return null;
20028
19711
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
20029
19712
  }
20030
19713
  function readRect3(ref) {
20031
- return rectOf2(ref && findPricingCardElement(ref.sectionId, ref.index));
19714
+ return rectOf(ref && findPricingCardElement(ref.sectionId, ref.index));
20032
19715
  }
20033
19716
  function readGridRect(ref) {
20034
19717
  const card = ref && findPricingCardElement(ref.sectionId, ref.index);
20035
- return rectOf2(card?.closest("[data-ohw-pricing-collection]") ?? card);
19718
+ return rectOf(card?.closest("[data-ohw-pricing-collection]") ?? card);
20036
19719
  }
20037
19720
  function useLiveCardRect(ref, grid = false) {
20038
19721
  const [rect, setRect] = (0, import_react13.useState)(null);
@@ -20062,7 +19745,7 @@ function useLiveCardRect(ref, grid = false) {
20062
19745
  }
20063
19746
  var TOOLBAR_GRID_GAP = 52;
20064
19747
  var TOOLBAR_GRID_GAP_MOBILE = 42;
20065
- var TOOLBAR_EDGE_MARGIN3 = 4;
19748
+ var TOOLBAR_EDGE_MARGIN2 = 4;
20066
19749
  function GridToolbarSlot({ gridRect, children }) {
20067
19750
  const slotRef = (0, import_react13.useRef)(null);
20068
19751
  const [shift, setShift] = (0, import_react13.useState)(0);
@@ -20074,7 +19757,7 @@ function GridToolbarSlot({ gridRect, children }) {
20074
19757
  const w = slot.offsetWidth;
20075
19758
  if (w <= 0) return;
20076
19759
  const half = w / 2;
20077
- const clamped = Math.max(TOOLBAR_EDGE_MARGIN3 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half));
19760
+ const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
20078
19761
  setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
20079
19762
  };
20080
19763
  clamp();
@@ -20083,7 +19766,7 @@ function GridToolbarSlot({ gridRect, children }) {
20083
19766
  return () => ro.disconnect();
20084
19767
  }, [centerX, children]);
20085
19768
  const mobile = window.innerWidth <= 480;
20086
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19769
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
20087
19770
  "div",
20088
19771
  {
20089
19772
  ref: slotRef,
@@ -20118,7 +19801,7 @@ function requestValidateTree2(postToParent2, tree) {
20118
19801
  });
20119
19802
  }
20120
19803
  function StyleSwatch({ style }) {
20121
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19804
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
20122
19805
  "span",
20123
19806
  {
20124
19807
  "aria-hidden": true,
@@ -20132,35 +19815,35 @@ function StyleSwatch({ style }) {
20132
19815
  );
20133
19816
  }
20134
19817
  function ButtonGlyph() {
20135
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("svg", { width: "18", height: "12", viewBox: "0 0 18 12", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
20136
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("rect", { x: "0.75", y: "0.75", width: "16.5", height: "10.5", rx: "3", stroke: "currentColor", strokeWidth: "1.5" }),
20137
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
19818
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("svg", { width: "18", height: "12", viewBox: "0 0 18 12", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19819
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("rect", { x: "0.75", y: "0.75", width: "16.5", height: "10.5", rx: "3", stroke: "currentColor", strokeWidth: "1.5" }),
19820
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
20138
19821
  ] });
20139
19822
  }
20140
19823
  function FeatureGlyph({ mode }) {
20141
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
20142
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
20143
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
20144
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("circle", { cx: "13.5", cy: "13.5", r: "4.25", fill: "var(--ohw-background, #ffffff)", stroke: "var(--ohw-background, #ffffff)", strokeWidth: "1" }),
20145
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("circle", { cx: "13.5", cy: "13.5", r: "3.75", fill: "var(--ohw-background, #ffffff)", stroke: PRIMARY4, strokeWidth: "1" }),
20146
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
20147
- mode === "add" && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
19824
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, style: { pointerEvents: "none" }, children: [
19825
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
19826
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
19827
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("circle", { cx: "13.5", cy: "13.5", r: "4.25", fill: "var(--ohw-background, #ffffff)", stroke: "var(--ohw-background, #ffffff)", strokeWidth: "1" }),
19828
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("circle", { cx: "13.5", cy: "13.5", r: "3.75", fill: "var(--ohw-background, #ffffff)", stroke: PRIMARY4, strokeWidth: "1" }),
19829
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
19830
+ mode === "add" && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
20148
19831
  ] });
20149
19832
  }
20150
- function ToolbarAction2({
19833
+ function ToolbarAction({
20151
19834
  label,
20152
19835
  onClick,
20153
19836
  disabled = false,
20154
19837
  children
20155
19838
  }) {
20156
- const button = /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
20157
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Tooltip, { children: [
20158
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "inline-flex", children: button }) : button }),
20159
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19839
+ const button = /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
19840
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19841
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "inline-flex", children: button }) : button }),
19842
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
20160
19843
  ] });
20161
19844
  }
20162
- function ToolbarSeparator2() {
20163
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19845
+ function ToolbarSeparator() {
19846
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
20164
19847
  }
20165
19848
  function CardStyleMenu({
20166
19849
  value,
@@ -20168,7 +19851,7 @@ function CardStyleMenu({
20168
19851
  disabled = false
20169
19852
  }) {
20170
19853
  const current = PRICING_CARD_STYLES.find((s) => s.value === value) ?? PRICING_CARD_STYLES[0];
20171
- const trigger = /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19854
+ const trigger = /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
20172
19855
  "button",
20173
19856
  {
20174
19857
  type: "button",
@@ -20179,28 +19862,28 @@ function CardStyleMenu({
20179
19862
  disabled && "cursor-not-allowed opacity-40 hover:bg-transparent"
20180
19863
  ),
20181
19864
  children: [
20182
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(StyleSwatch, { style: current.value }),
20183
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "flex-1 text-left", children: current.label }),
20184
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.ChevronDown, { size: 16, style: { pointerEvents: "none" } })
19865
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StyleSwatch, { style: current.value }),
19866
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "flex-1 text-left", children: current.label }),
19867
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ChevronDown, { size: 16, style: { pointerEvents: "none" } })
20185
19868
  ]
20186
19869
  }
20187
19870
  );
20188
19871
  if (disabled) {
20189
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
20190
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
20191
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Tooltip, { children: [
20192
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "inline-flex", children: trigger }) }),
20193
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
19872
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19873
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
19874
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19875
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "inline-flex", children: trigger }) }),
19876
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
20194
19877
  ] })
20195
19878
  ] });
20196
19879
  }
20197
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
20198
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
20199
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(DropdownMenu, { modal: false, children: [
20200
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(DropdownMenuTrigger, { asChild: true, children: trigger }),
20201
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[152px] min-w-[152px] p-1", children: PRICING_CARD_STYLES.map((style) => {
19880
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19881
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
19882
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DropdownMenu, { modal: false, children: [
19883
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuTrigger, { asChild: true, children: trigger }),
19884
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[152px] min-w-[152px] p-1", children: PRICING_CARD_STYLES.map((style) => {
20202
19885
  const selected = style.value === value;
20203
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19886
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
20204
19887
  DropdownMenuItem,
20205
19888
  {
20206
19889
  role: "menuitemradio",
@@ -20208,9 +19891,9 @@ function CardStyleMenu({
20208
19891
  onSelect: () => onChange(style.value),
20209
19892
  className: cn("grid grid-cols-[18px_1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
20210
19893
  children: [
20211
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(StyleSwatch, { style: style.value }),
20212
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { children: style.label }),
20213
- selected && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Check, { size: 16, className: "text-bridge-primary" })
19894
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StyleSwatch, { style: style.value }),
19895
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: style.label }),
19896
+ selected && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Check, { size: 16, className: "text-bridge-primary" })
20214
19897
  ]
20215
19898
  },
20216
19899
  style.value
@@ -20240,7 +19923,7 @@ function PricingCardToolbar({
20240
19923
  const style = pricingCardStyle(entry.tree, index);
20241
19924
  const featuredTag = pricingCardFeaturedTag(entry.tree, index);
20242
19925
  const mobile = window.innerWidth <= 480;
20243
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19926
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
20244
19927
  "div",
20245
19928
  {
20246
19929
  "data-ohw-bridge": "",
@@ -20252,7 +19935,7 @@ function PricingCardToolbar({
20252
19935
  ),
20253
19936
  style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
20254
19937
  children: [
20255
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19938
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
20256
19939
  "div",
20257
19940
  {
20258
19941
  className: cn(
@@ -20261,8 +19944,8 @@ function PricingCardToolbar({
20261
19944
  mobile ? "flex-wrap justify-center" : "flex-nowrap"
20262
19945
  ),
20263
19946
  children: [
20264
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Tooltip, { children: [
20265
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19947
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19948
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
20266
19949
  CustomToolbarButton,
20267
19950
  {
20268
19951
  type: "button",
@@ -20270,8 +19953,8 @@ function PricingCardToolbar({
20270
19953
  "aria-pressed": featuredTag,
20271
19954
  onClick: () => onSetFeaturedTag(!featuredTag),
20272
19955
  className: "group size-8",
20273
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20274
- import_lucide_react10.Star,
19956
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19957
+ import_lucide_react9.Star,
20275
19958
  {
20276
19959
  size: 16,
20277
19960
  fill: "currentColor",
@@ -20285,25 +19968,25 @@ function PricingCardToolbar({
20285
19968
  )
20286
19969
  }
20287
19970
  ) }),
20288
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
19971
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
20289
19972
  ] }),
20290
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarSeparator2, {}),
20291
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
20292
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarSeparator2, {}),
20293
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ButtonGlyph, {}) }),
20294
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Remove button", onClick: onRemoveButton, disabled: buttonCount === 0, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("span", { className: "relative inline-flex", children: [
20295
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ButtonGlyph, {}),
20296
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Minus, { size: 10, strokeWidth: 2.5, className: "absolute -right-1.5 -top-1.5 rounded-full bg-background text-bridge-primary" })
19973
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19974
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
19975
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19976
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ButtonGlyph, {}) }),
19977
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Remove button", onClick: onRemoveButton, disabled: buttonCount === 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "relative inline-flex", children: [
19978
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ButtonGlyph, {}),
19979
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Minus, { size: 10, strokeWidth: 2.5, className: "absolute -right-1.5 -top-1.5 rounded-full bg-background text-bridge-primary" })
20297
19980
  ] }) }),
20298
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Add feature", onClick: onAddFeature, disabled: featureCount >= 10, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(FeatureGlyph, { mode: "add" }) }),
20299
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Remove feature", onClick: onRemoveFeature, disabled: featureCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(FeatureGlyph, { mode: "remove" }) }),
20300
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarSeparator2, {}),
20301
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Add tier", onClick: onAddTier, disabled: collectionCount >= 4, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Plus, { size: 16, style: { pointerEvents: "none" } }) }),
20302
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Remove tier", onClick: onRemoveTier, disabled: collectionCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Minus, { size: 16, style: { pointerEvents: "none" } }) })
19981
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Add feature", onClick: onAddFeature, disabled: featureCount >= 10, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(FeatureGlyph, { mode: "add" }) }),
19982
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Remove feature", onClick: onRemoveFeature, disabled: featureCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(FeatureGlyph, { mode: "remove" }) }),
19983
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19984
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Add tier", onClick: onAddTier, disabled: collectionCount >= 4, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Plus, { size: 16, style: { pointerEvents: "none" } }) }),
19985
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Remove tier", onClick: onRemoveTier, disabled: collectionCount <= 1, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Minus, { size: 16, style: { pointerEvents: "none" } }) })
20303
19986
  ]
20304
19987
  }
20305
19988
  ),
20306
- error && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
19989
+ error && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "max-w-[260px] rounded-md bg-background px-2 py-1 text-center text-[11px] text-destructive shadow-sm", children: error })
20307
19990
  ]
20308
19991
  }
20309
19992
  ) });
@@ -20337,7 +20020,7 @@ function AiPricingCardOverlay({
20337
20020
  const overEditable = Boolean(t?.closest?.("[data-ohw-editable], [data-ohw-role], a, button"));
20338
20021
  setHover((prev) => {
20339
20022
  const next = ref && !overEditable ? ref : null;
20340
- return sameRef2(prev, next) ? prev : next;
20023
+ return sameRef(prev, next) ? prev : next;
20341
20024
  });
20342
20025
  };
20343
20026
  const onLeave = () => setHover(null);
@@ -20352,10 +20035,10 @@ function AiPricingCardOverlay({
20352
20035
  const onClick = (e) => {
20353
20036
  const t = e.target;
20354
20037
  if (!(t instanceof Element)) return;
20355
- if (t.closest(CHROME_SELECTOR2)) return;
20038
+ if (t.closest(CHROME_SELECTOR)) return;
20356
20039
  const ref = cardRefFromTarget(t);
20357
- setActive((prev) => sameRef2(prev, ref) ? prev : ref);
20358
- if (!ref || !sameRef2(activeRef.current, ref)) setError(null);
20040
+ setActive((prev) => sameRef(prev, ref) ? prev : ref);
20041
+ if (!ref || !sameRef(activeRef.current, ref)) setError(null);
20359
20042
  };
20360
20043
  document.addEventListener("click", onClick, true);
20361
20044
  return () => document.removeEventListener("click", onClick, true);
@@ -20420,12 +20103,12 @@ function AiPricingCardOverlay({
20420
20103
  const activeEntry = active ? findEntry(active.sectionId) : null;
20421
20104
  const activeCardRect = useLiveCardRect(active);
20422
20105
  const activeGridRect = useLiveCardRect(active, true);
20423
- const hoverRect = useLiveCardRect(hover && !sameRef2(hover, active) ? hover : null);
20424
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
20425
- hoverRect && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
20426
- activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
20427
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
20428
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20106
+ const hoverRect = useLiveCardRect(hover && !sameRef(hover, active) ? hover : null);
20107
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
20108
+ hoverRect && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
20109
+ activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
20110
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
20111
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
20429
20112
  PricingCardToolbar,
20430
20113
  {
20431
20114
  entry: activeEntry,
@@ -20453,7 +20136,7 @@ function AiPricingCardOverlay({
20453
20136
  onRemoveFeature: () => runMutation(active.sectionId, (tree) => removePricingCardFeature(tree, active.index))
20454
20137
  }
20455
20138
  ) }),
20456
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20139
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
20457
20140
  "div",
20458
20141
  {
20459
20142
  "data-ohw-bridge": "",
@@ -20475,7 +20158,7 @@ function AiPricingCardOverlay({
20475
20158
 
20476
20159
  // src/ui/ai-section/AiVideoOverlay.tsx
20477
20160
  var import_react14 = require("react");
20478
- var import_lucide_react11 = require("lucide-react");
20161
+ var import_lucide_react10 = require("lucide-react");
20479
20162
 
20480
20163
  // src/lib/ai-video-tree.ts
20481
20164
  function resolveVideoBlock(tree, path) {
@@ -20517,11 +20200,39 @@ function clearVideo(tree, path) {
20517
20200
  });
20518
20201
  }
20519
20202
 
20203
+ // src/lib/youtube.ts
20204
+ var YOUTUBE_ID_PATTERN = /^[A-Za-z0-9_-]{11}$/;
20205
+ function extractYouTubeId(input) {
20206
+ const trimmed = input.trim();
20207
+ if (!trimmed) return null;
20208
+ if (YOUTUBE_ID_PATTERN.test(trimmed)) return trimmed;
20209
+ let url;
20210
+ try {
20211
+ url = new URL(trimmed);
20212
+ } catch {
20213
+ return null;
20214
+ }
20215
+ const host = url.hostname.replace(/^www\./, "");
20216
+ if (host !== "youtube.com" && host !== "m.youtube.com" && host !== "youtu.be" && host !== "youtube-nocookie.com") {
20217
+ return null;
20218
+ }
20219
+ let candidate = null;
20220
+ if (host === "youtu.be") {
20221
+ candidate = url.pathname.slice(1).split("/")[0] ?? null;
20222
+ } else if (url.pathname === "/watch") {
20223
+ candidate = url.searchParams.get("v");
20224
+ } else {
20225
+ const match = url.pathname.match(/^\/(?:embed|shorts)\/([^/]+)/);
20226
+ candidate = match ? match[1] : null;
20227
+ }
20228
+ return candidate && YOUTUBE_ID_PATTERN.test(candidate) ? candidate : null;
20229
+ }
20230
+
20520
20231
  // src/ui/ai-section/AiVideoOverlay.tsx
20521
- var import_jsx_runtime23 = require("react/jsx-runtime");
20232
+ var import_jsx_runtime22 = require("react/jsx-runtime");
20522
20233
  var PRIMARY5 = "#0885FE";
20523
- var OHW_BLUE = `var(--ohw-primary, ${PRIMARY5})`;
20524
- var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
20234
+ var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY5})`;
20235
+ var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
20525
20236
  function findVideoBlockElement(sectionId, path) {
20526
20237
  const sectionEl = findByInstanceId(sectionId);
20527
20238
  if (!sectionEl) return null;
@@ -20581,12 +20292,12 @@ function requestValidateTree3(postToParent2, tree) {
20581
20292
  postToParent2({ type: "ow:ai-validate-tree-request", id, tree });
20582
20293
  });
20583
20294
  }
20584
- function Pill({
20295
+ function Pill2({
20585
20296
  label,
20586
20297
  onClick,
20587
20298
  disabled
20588
20299
  }) {
20589
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20300
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20590
20301
  "button",
20591
20302
  {
20592
20303
  type: "button",
@@ -20602,7 +20313,7 @@ function Pill({
20602
20313
  border: "none",
20603
20314
  cursor: disabled ? "default" : "pointer",
20604
20315
  background: "#ffffff",
20605
- color: OHW_BLUE_DARK,
20316
+ color: OHW_BLUE_DARK2,
20606
20317
  opacity: disabled ? 0.4 : 1,
20607
20318
  fontSize: 12,
20608
20319
  lineHeight: "16px",
@@ -20624,7 +20335,7 @@ function VideoUrlInput({ value, onCommit }) {
20624
20335
  setDraft(value ? watchUrl(value) : "");
20625
20336
  setInvalid(false);
20626
20337
  }, [value]);
20627
- const commit2 = () => {
20338
+ const commit = () => {
20628
20339
  const next = draft.trim();
20629
20340
  if (!next) {
20630
20341
  setInvalid(false);
@@ -20645,8 +20356,8 @@ function VideoUrlInput({ value, onCommit }) {
20645
20356
  onCommit(id);
20646
20357
  }
20647
20358
  };
20648
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
20649
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20359
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
20360
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
20650
20361
  "div",
20651
20362
  {
20652
20363
  style: {
@@ -20662,12 +20373,12 @@ function VideoUrlInput({ value, onCommit }) {
20662
20373
  boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
20663
20374
  },
20664
20375
  children: [
20665
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20376
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20666
20377
  "span",
20667
20378
  {
20668
20379
  style: {
20669
20380
  flex: "0 0 auto",
20670
- color: OHW_BLUE_DARK,
20381
+ color: OHW_BLUE_DARK2,
20671
20382
  fontSize: 10,
20672
20383
  lineHeight: "16px",
20673
20384
  fontWeight: 700,
@@ -20677,14 +20388,14 @@ function VideoUrlInput({ value, onCommit }) {
20677
20388
  children: "Video"
20678
20389
  }
20679
20390
  ),
20680
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20391
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20681
20392
  "input",
20682
20393
  {
20683
20394
  type: "text",
20684
20395
  inputMode: "url",
20685
20396
  value: draft,
20686
20397
  onChange: (e) => handleChange(e.target.value),
20687
- onBlur: commit2,
20398
+ onBlur: commit,
20688
20399
  onKeyDown: (e) => {
20689
20400
  if (e.key === "Enter") e.currentTarget.blur();
20690
20401
  if (e.key === "Escape") {
@@ -20714,7 +20425,7 @@ function VideoUrlInput({ value, onCommit }) {
20714
20425
  ]
20715
20426
  }
20716
20427
  ),
20717
- invalid && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, textAlign: "center" }, children: "That doesn't look like a YouTube link \u2014 try pasting the video URL again." })
20428
+ invalid && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, textAlign: "center" }, children: "That doesn't look like a YouTube link \u2014 try pasting the video URL again." })
20718
20429
  ] });
20719
20430
  }
20720
20431
  function VideoToolbar({
@@ -20724,7 +20435,7 @@ function VideoToolbar({
20724
20435
  onSetVideoId,
20725
20436
  onRemove
20726
20437
  }) {
20727
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20438
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
20728
20439
  "div",
20729
20440
  {
20730
20441
  "data-ohw-bridge": "",
@@ -20736,7 +20447,7 @@ function VideoToolbar({
20736
20447
  gap: 6,
20737
20448
  alignItems: "center",
20738
20449
  width: 280,
20739
- background: OHW_BLUE,
20450
+ background: OHW_BLUE2,
20740
20451
  padding: 8,
20741
20452
  borderRadius: 12,
20742
20453
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -20744,9 +20455,9 @@ function VideoToolbar({
20744
20455
  opacity: pending ? 0.6 : 1
20745
20456
  },
20746
20457
  children: [
20747
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
20748
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Pill, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
20749
- error && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20458
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
20459
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Pill2, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
20460
+ error && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20750
20461
  ]
20751
20462
  }
20752
20463
  );
@@ -20866,8 +20577,8 @@ function AiVideoOverlay({
20866
20577
  const activeBlockRect = useLiveBlockRect(active);
20867
20578
  const TOOLBAR_APPROX_H = 90;
20868
20579
  const toolbarState = activeBlockRect && activeBlockRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
20869
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
20870
- hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20580
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
20581
+ hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20871
20582
  "button",
20872
20583
  {
20873
20584
  type: "button",
@@ -20895,15 +20606,15 @@ function AiVideoOverlay({
20895
20606
  fontSize: 14,
20896
20607
  fontWeight: 700
20897
20608
  },
20898
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.Star, { size: 14, style: { pointerEvents: "none" } })
20609
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Star, { size: 14, style: { pointerEvents: "none" } })
20899
20610
  }
20900
20611
  ),
20901
- activeBlockRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20612
+ activeBlockRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20902
20613
  ItemInteractionLayer,
20903
20614
  {
20904
20615
  rect: activeBlockRect,
20905
20616
  state: toolbarState,
20906
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20617
+ toolbar: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20907
20618
  VideoToolbar,
20908
20619
  {
20909
20620
  videoId: videoIdAt(activeEntry.tree, active.path),
@@ -20927,7 +20638,7 @@ function AiVideoOverlay({
20927
20638
 
20928
20639
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
20929
20640
  var import_react15 = require("react");
20930
- var import_lucide_react12 = require("lucide-react");
20641
+ var import_lucide_react11 = require("lucide-react");
20931
20642
 
20932
20643
  // src/lib/ai-carousel-tree.ts
20933
20644
  function isCarouselBlock(node) {
@@ -20978,10 +20689,10 @@ function setCarouselAutoplay(tree, on) {
20978
20689
  }
20979
20690
 
20980
20691
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
20981
- var import_jsx_runtime24 = require("react/jsx-runtime");
20692
+ var import_jsx_runtime23 = require("react/jsx-runtime");
20982
20693
  var PRIMARY6 = "#0885FE";
20983
- var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY6})`;
20984
- var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
20694
+ var OHW_BLUE3 = `var(--ohw-primary, ${PRIMARY6})`;
20695
+ var OHW_BLUE_DARK3 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
20985
20696
  function findCarouselItemElement(sectionId, index) {
20986
20697
  const sectionEl = findByInstanceId(sectionId);
20987
20698
  if (!sectionEl) return null;
@@ -21060,8 +20771,8 @@ function requestAssetPick2(postToParent2) {
21060
20771
  postToParent2({ type: "ow:asset-pick", id });
21061
20772
  });
21062
20773
  }
21063
- function Pill2({ label, onClick, disabled }) {
21064
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20774
+ function Pill3({ label, onClick, disabled }) {
20775
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
21065
20776
  "button",
21066
20777
  {
21067
20778
  type: "button",
@@ -21077,7 +20788,7 @@ function Pill2({ label, onClick, disabled }) {
21077
20788
  border: "none",
21078
20789
  cursor: disabled ? "default" : "pointer",
21079
20790
  background: "#ffffff",
21080
- color: OHW_BLUE_DARK2,
20791
+ color: OHW_BLUE_DARK3,
21081
20792
  opacity: disabled ? 0.4 : 1,
21082
20793
  fontSize: 12,
21083
20794
  lineHeight: "16px",
@@ -21095,7 +20806,7 @@ function TogglePill({
21095
20806
  onToggle,
21096
20807
  disabled
21097
20808
  }) {
21098
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
20809
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
21099
20810
  "button",
21100
20811
  {
21101
20812
  type: "button",
@@ -21114,7 +20825,7 @@ function TogglePill({
21114
20825
  border: "none",
21115
20826
  cursor: disabled ? "default" : "pointer",
21116
20827
  background: "#ffffff",
21117
- color: OHW_BLUE_DARK2,
20828
+ color: OHW_BLUE_DARK3,
21118
20829
  opacity: disabled ? 0.4 : 1,
21119
20830
  fontSize: 12,
21120
20831
  lineHeight: "16px",
@@ -21124,7 +20835,7 @@ function TogglePill({
21124
20835
  },
21125
20836
  children: [
21126
20837
  label,
21127
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20838
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
21128
20839
  "span",
21129
20840
  {
21130
20841
  "aria-hidden": true,
@@ -21137,10 +20848,10 @@ function TogglePill({
21137
20848
  // Off is a grey track, not a washed-out blue: the pill's own white background already
21138
20849
  // carries the brand colour in the text, and a pale blue track next to it read as
21139
20850
  // "disabled control" rather than "switch that is off".
21140
- background: on ? OHW_BLUE_DARK2 : "#CBD5E1",
20851
+ background: on ? OHW_BLUE_DARK3 : "#CBD5E1",
21141
20852
  transition: "background 120ms ease"
21142
20853
  },
21143
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20854
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
21144
20855
  "span",
21145
20856
  {
21146
20857
  style: {
@@ -21169,7 +20880,7 @@ function CarouselSlideToolbar({
21169
20880
  onChangeImage,
21170
20881
  onToggleAutoplay
21171
20882
  }) {
21172
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
20883
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
21173
20884
  "div",
21174
20885
  {
21175
20886
  "data-ohw-bridge": "",
@@ -21180,7 +20891,7 @@ function CarouselSlideToolbar({
21180
20891
  flexDirection: "column",
21181
20892
  gap: 6,
21182
20893
  alignItems: "center",
21183
- background: OHW_BLUE2,
20894
+ background: OHW_BLUE3,
21184
20895
  padding: 8,
21185
20896
  borderRadius: 12,
21186
20897
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -21192,11 +20903,11 @@ function CarouselSlideToolbar({
21192
20903
  pointerEvents: pending ? "none" : "auto"
21193
20904
  },
21194
20905
  children: [
21195
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
21196
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(Pill2, { label: "Change Image", onClick: onChangeImage }),
21197
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
20906
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20907
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Pill3, { label: "Change Image", onClick: onChangeImage }),
20908
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
21198
20909
  ] }),
21199
- error && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20910
+ error && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
21200
20911
  ]
21201
20912
  }
21202
20913
  );
@@ -21336,8 +21047,8 @@ function AiFadeCarouselOverlay({
21336
21047
  const activeItemRect = useLiveItemRect2(active);
21337
21048
  const TOOLBAR_APPROX_H = 90;
21338
21049
  const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
21339
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
21340
- hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21050
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
21051
+ hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
21341
21052
  "button",
21342
21053
  {
21343
21054
  type: "button",
@@ -21366,10 +21077,10 @@ function AiFadeCarouselOverlay({
21366
21077
  fontSize: 14,
21367
21078
  fontWeight: 700
21368
21079
  },
21369
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react12.Star, { size: 14, style: { pointerEvents: "none" } })
21080
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.Star, { size: 14, style: { pointerEvents: "none" } })
21370
21081
  }
21371
21082
  ),
21372
- swapping && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21083
+ swapping && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
21373
21084
  "div",
21374
21085
  {
21375
21086
  ref: skeletonRef,
@@ -21387,15 +21098,15 @@ function AiFadeCarouselOverlay({
21387
21098
  zIndex: 2147483646,
21388
21099
  pointerEvents: "none"
21389
21100
  },
21390
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("style", { children: MEDIA_SKELETON_CSS })
21101
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("style", { children: MEDIA_SKELETON_CSS })
21391
21102
  }
21392
21103
  ),
21393
- activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21104
+ activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
21394
21105
  ItemInteractionLayer,
21395
21106
  {
21396
21107
  rect: activeItemRect,
21397
21108
  state: toolbarState,
21398
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21109
+ toolbar: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
21399
21110
  CarouselSlideToolbar,
21400
21111
  {
21401
21112
  pending,
@@ -21586,6 +21297,113 @@ function mergePrefetchedSections(prev, fetched, livePath, liveSections) {
21586
21297
  // src/ui/link-modal/LinkPopover.tsx
21587
21298
  var import_react19 = require("react");
21588
21299
 
21300
+ // src/ui/dialog.tsx
21301
+ var React9 = __toESM(require("react"), 1);
21302
+ var import_radix_ui6 = require("radix-ui");
21303
+ var import_lucide_react12 = require("lucide-react");
21304
+ var import_jsx_runtime24 = require("react/jsx-runtime");
21305
+ function Dialog2({
21306
+ ...props
21307
+ }) {
21308
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_radix_ui6.Dialog.Root, { "data-slot": "dialog", ...props });
21309
+ }
21310
+ function DialogPortal({
21311
+ ...props
21312
+ }) {
21313
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_radix_ui6.Dialog.Portal, { ...props });
21314
+ }
21315
+ function DialogOverlay({
21316
+ className,
21317
+ ...props
21318
+ }) {
21319
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21320
+ import_radix_ui6.Dialog.Overlay,
21321
+ {
21322
+ "data-slot": "dialog-overlay",
21323
+ "data-ohw-link-modal-root": "",
21324
+ className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
21325
+ ...props
21326
+ }
21327
+ );
21328
+ }
21329
+ var DialogContent = React9.forwardRef(
21330
+ ({ className, children, showCloseButton = true, container, ...props }, ref) => {
21331
+ const positionMode = container ? "absolute" : "fixed";
21332
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(DialogPortal, { container: container ?? void 0, children: [
21333
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
21334
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
21335
+ import_radix_ui6.Dialog.Content,
21336
+ {
21337
+ ref,
21338
+ "data-slot": "dialog-content",
21339
+ className: cn(
21340
+ positionMode,
21341
+ "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",
21342
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
21343
+ container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
21344
+ className
21345
+ ),
21346
+ onMouseDown: (e) => e.stopPropagation(),
21347
+ ...props,
21348
+ children: [
21349
+ children,
21350
+ showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21351
+ import_radix_ui6.Dialog.Close,
21352
+ {
21353
+ type: "button",
21354
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
21355
+ "aria-label": "Close",
21356
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react12.X, { size: 16, "aria-hidden": true })
21357
+ }
21358
+ ) : null
21359
+ ]
21360
+ }
21361
+ )
21362
+ ] });
21363
+ }
21364
+ );
21365
+ DialogContent.displayName = import_radix_ui6.Dialog.Content.displayName;
21366
+ function DialogHeader({
21367
+ className,
21368
+ ...props
21369
+ }) {
21370
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
21371
+ }
21372
+ function DialogFooter({
21373
+ className,
21374
+ ...props
21375
+ }) {
21376
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21377
+ "div",
21378
+ {
21379
+ className: cn("flex items-center justify-end gap-2", className),
21380
+ ...props
21381
+ }
21382
+ );
21383
+ }
21384
+ var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21385
+ import_radix_ui6.Dialog.Title,
21386
+ {
21387
+ ref,
21388
+ className: cn(
21389
+ "text-lg font-semibold leading-none tracking-tight text-card-foreground",
21390
+ className
21391
+ ),
21392
+ ...props
21393
+ }
21394
+ ));
21395
+ DialogTitle.displayName = import_radix_ui6.Dialog.Title.displayName;
21396
+ var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21397
+ import_radix_ui6.Dialog.Description,
21398
+ {
21399
+ ref,
21400
+ className: cn("text-sm text-muted-foreground", className),
21401
+ ...props
21402
+ }
21403
+ ));
21404
+ DialogDescription.displayName = import_radix_ui6.Dialog.Description.displayName;
21405
+ var DialogClose = import_radix_ui6.Dialog.Close;
21406
+
21589
21407
  // src/ui/link-modal/LinkEditorPanel.tsx
21590
21408
  var import_lucide_react16 = require("lucide-react");
21591
21409
 
@@ -22689,7 +22507,6 @@ function shouldUseDevFixtures() {
22689
22507
  function getLogoElement(el) {
22690
22508
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
22691
22509
  if (marked) return marked;
22692
- if (el.closest('[data-ohw-editable="icon"]')) return null;
22693
22510
  const root = el.closest("nav, [data-ohw-nav-root], footer");
22694
22511
  if (!root) return null;
22695
22512
  const anchor = el.closest("a");
@@ -24384,102 +24201,6 @@ function deleteSelectedNavFooterItem(deps) {
24384
24201
  return false;
24385
24202
  }
24386
24203
 
24387
- // src/lib/nav-page-sync.ts
24388
- function labelKeyFor(hrefKey) {
24389
- return hrefKey.replace(/-href$/, "-label");
24390
- }
24391
- function isPageLinkKey(key) {
24392
- return isNavbarHrefKey(key) || isFooterHrefKey(key);
24393
- }
24394
- function currentHref(key, anchor, content) {
24395
- const stored = content[key];
24396
- if (typeof stored === "string" && stored.length > 0) return stored;
24397
- return anchor.getAttribute("href") ?? "";
24398
- }
24399
- function normalizePath2(value) {
24400
- if (value.length > 1 && value.endsWith("/")) return value.slice(0, -1);
24401
- return value;
24402
- }
24403
- function pointsAt(href, path) {
24404
- const normalized = normalizePath2(href);
24405
- const target = normalizePath2(path);
24406
- if (normalized === target) return true;
24407
- return (normalized.startsWith(`${target}#`) || normalized.startsWith(`${target}?`)) && target !== "/";
24408
- }
24409
- function retargetHref(href, oldPath, newPath) {
24410
- const normalized = normalizePath2(href);
24411
- const target = normalizePath2(oldPath);
24412
- if (normalized === target) return newPath;
24413
- return `${newPath}${normalized.slice(target.length)}`;
24414
- }
24415
- function setLabelText(key, text) {
24416
- document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`).forEach((el) => {
24417
- el.textContent = text;
24418
- });
24419
- }
24420
- function commit(delta, deps) {
24421
- if (Object.keys(delta).length === 0) return;
24422
- deps.setEditContent({ ...deps.getEditContent(), ...delta });
24423
- deps.postToParent({
24424
- type: "ow:change",
24425
- nodes: Object.entries(delta).map(([key, text]) => ({ key, text }))
24426
- });
24427
- }
24428
- function navLinksTo(path, content) {
24429
- return Array.from(document.querySelectorAll("[data-ohw-href-key]")).filter((anchor) => {
24430
- const key = anchor.getAttribute("data-ohw-href-key");
24431
- return isNavbarHrefKey(key) && pointsAt(currentHref(key, anchor, content), path);
24432
- }).map((anchor) => anchor.getAttribute("data-ohw-href-key"));
24433
- }
24434
- function addNavLinkForPage(page, deps) {
24435
- const content = deps.getEditContent();
24436
- if (navLinksTo(page.path, content).length > 0) return false;
24437
- const roots = listNavbarRootItems();
24438
- const afterAnchor = roots[roots.length - 1] ?? null;
24439
- let inserted;
24440
- try {
24441
- inserted = insertNavbarItem(page.path, page.title, afterAnchor);
24442
- } catch {
24443
- return false;
24444
- }
24445
- const { hrefKey, labelKey, index } = inserted;
24446
- deps.applyLinkByKey(hrefKey, page.path);
24447
- setLabelText(labelKey, page.title);
24448
- commit(
24449
- {
24450
- [hrefKey]: page.path,
24451
- [labelKey]: page.title,
24452
- [NAV_COUNT_KEY]: String(index + 1),
24453
- [NAV_ORDER_KEY]: getNavbarOrderJson()
24454
- },
24455
- deps
24456
- );
24457
- return true;
24458
- }
24459
- function retargetPageLinks(change, deps) {
24460
- const { oldPath, newPath, oldTitle, newTitle } = change;
24461
- if (!oldPath || !newPath || normalizePath2(oldPath) === normalizePath2(newPath)) return 0;
24462
- const content = deps.getEditContent();
24463
- const delta = {};
24464
- for (const anchor of Array.from(document.querySelectorAll("[data-ohw-href-key]"))) {
24465
- const hrefKey = anchor.getAttribute("data-ohw-href-key");
24466
- if (!isPageLinkKey(hrefKey)) continue;
24467
- const href = currentHref(hrefKey, anchor, content);
24468
- if (!pointsAt(href, oldPath)) continue;
24469
- const nextHref = retargetHref(href, oldPath, newPath);
24470
- deps.applyLinkByKey(hrefKey, nextHref);
24471
- delta[hrefKey] = nextHref;
24472
- if (!oldTitle || !newTitle) continue;
24473
- const labelKey = labelKeyFor(hrefKey);
24474
- const label = content[labelKey] ?? anchor.querySelector('[data-ohw-editable="text"]')?.textContent ?? "";
24475
- if (label.trim() !== oldTitle) continue;
24476
- setLabelText(labelKey, newTitle);
24477
- delta[labelKey] = newTitle;
24478
- }
24479
- commit(delta, deps);
24480
- return Object.keys(delta).filter((key) => key.endsWith("-href")).length;
24481
- }
24482
-
24483
24204
  // src/ui/navbar-container-chrome.tsx
24484
24205
  var import_lucide_react21 = require("lucide-react");
24485
24206
  var import_jsx_runtime37 = require("react/jsx-runtime");
@@ -25367,7 +25088,7 @@ function getNavigationSelectionParent(el) {
25367
25088
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
25368
25089
  return resolveFooterLinksContainerFrom(el);
25369
25090
  }
25370
- 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)) {
25091
+ if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
25371
25092
  return getNavigationRoot(el);
25372
25093
  }
25373
25094
  return null;
@@ -25589,6 +25310,7 @@ var ICONS = {
25589
25310
  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"/>',
25590
25311
  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"/>'
25591
25312
  };
25313
+ var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
25592
25314
  var SELECTION_CHROME_GAP2 = 4;
25593
25315
  var TOOLBAR_STROKE_GAP2 = 4;
25594
25316
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -25686,7 +25408,7 @@ function applyVisibleViewport(el, parentScroll) {
25686
25408
  el.style.top = `${top}px`;
25687
25409
  el.style.height = `${height}px`;
25688
25410
  }
25689
- var TOOLBAR_EDGE_MARGIN4 = 4;
25411
+ var TOOLBAR_EDGE_MARGIN3 = 4;
25690
25412
  function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
25691
25413
  const isAbove = transform.includes("translateY(-100%)");
25692
25414
  let visualTop = isAbove ? top - approxH : top;
@@ -25713,8 +25435,8 @@ function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
25713
25435
  function clampToolbarLeft(centerX, toolbarWidth) {
25714
25436
  const half = toolbarWidth / 2;
25715
25437
  return Math.max(
25716
- TOOLBAR_EDGE_MARGIN4 + half,
25717
- Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN4 - half)
25438
+ TOOLBAR_EDGE_MARGIN3 + half,
25439
+ Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half)
25718
25440
  );
25719
25441
  }
25720
25442
  function calcToolbarPos(rect, parentScroll, toolbarW = 306) {
@@ -26596,6 +26318,21 @@ function OhhwellsBridge() {
26596
26318
  const [isItemDragging, setIsItemDragging] = (0, import_react23.useState)(false);
26597
26319
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react23.useState)(false);
26598
26320
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
26321
+ const [floatingPanel, setFloatingPanel] = (0, import_react23.useState)(null);
26322
+ const floatingPanelOpenRef = (0, import_react23.useRef)(false);
26323
+ floatingPanelOpenRef.current = floatingPanel !== null;
26324
+ const floatingPanelRef = (0, import_react23.useRef)(null);
26325
+ floatingPanelRef.current = floatingPanel;
26326
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react23.useState)(null);
26327
+ const buttonCollectionDragRef = (0, import_react23.useRef)(null);
26328
+ const buttonDropInsertIndexRef = (0, import_react23.useRef)(null);
26329
+ const [buttonDropSlots, setButtonDropSlots] = (0, import_react23.useState)([]);
26330
+ const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react23.useState)(null);
26331
+ const buttonPointerDragRef = (0, import_react23.useRef)(null);
26332
+ const [buttonStyleTick, setButtonStyleTick] = (0, import_react23.useState)(0);
26333
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react23.useState)(null);
26334
+ const [editorViewport, setEditorViewport] = (0, import_react23.useState)("desktop");
26335
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react23.useState)(null);
26599
26336
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react23.useState)(null);
26600
26337
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react23.useState)(null);
26601
26338
  const footerDragRef = (0, import_react23.useRef)(null);
@@ -26621,21 +26358,6 @@ function OhhwellsBridge() {
26621
26358
  const stylesRef = (0, import_react23.useRef)("");
26622
26359
  const appliedDrawerRef = (0, import_react23.useRef)(null);
26623
26360
  const pendingDeleteUndoRef = (0, import_react23.useRef)(null);
26624
- const [floatingPanel, setFloatingPanel] = (0, import_react23.useState)(null);
26625
- const floatingPanelOpenRef = (0, import_react23.useRef)(false);
26626
- const floatingPanelRef = (0, import_react23.useRef)(null);
26627
- floatingPanelRef.current = floatingPanel;
26628
- const setFloatingPanelRef = (0, import_react23.useRef)(setFloatingPanel);
26629
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react23.useState)(null);
26630
- const buttonCollectionDragRef = (0, import_react23.useRef)(null);
26631
- const buttonDropInsertIndexRef = (0, import_react23.useRef)(null);
26632
- const [buttonDropSlots, setButtonDropSlots] = (0, import_react23.useState)([]);
26633
- const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react23.useState)(null);
26634
- const buttonPointerDragRef = (0, import_react23.useRef)(null);
26635
- const [buttonStyleTick, setButtonStyleTick] = (0, import_react23.useState)(0);
26636
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react23.useState)(null);
26637
- const [editorViewport, setEditorViewport] = (0, import_react23.useState)("desktop");
26638
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react23.useState)(null);
26639
26361
  const [sitePages, setSitePages] = (0, import_react23.useState)([]);
26640
26362
  const collectionItemsRef = (0, import_react23.useRef)(/* @__PURE__ */ new Map());
26641
26363
  const [sectionsByPath, setSectionsByPath] = (0, import_react23.useState)({});
@@ -26645,18 +26367,7 @@ function OhhwellsBridge() {
26645
26367
  const linkPopoverOpenRef = (0, import_react23.useRef)(false);
26646
26368
  const linkPopoverGraceUntilRef = (0, import_react23.useRef)(0);
26647
26369
  setLinkPopoverRef.current = setLinkPopover;
26648
- setFloatingPanelRef.current = setFloatingPanel;
26649
26370
  linkPopoverSessionRef.current = linkPopover;
26650
- floatingPanelOpenRef.current = Boolean(floatingPanel);
26651
- (0, import_react23.useEffect)(() => {
26652
- const syncViewport = () => {
26653
- const next = window.innerWidth <= 480 ? "mobile" : "desktop";
26654
- setEditorViewport((prev) => prev === next ? prev : next);
26655
- };
26656
- syncViewport();
26657
- window.addEventListener("resize", syncViewport);
26658
- return () => window.removeEventListener("resize", syncViewport);
26659
- }, []);
26660
26371
  (0, import_react23.useEffect)(() => {
26661
26372
  setSectionOrderPersistHandler((entries) => {
26662
26373
  const currentPath = window.location.pathname;
@@ -26805,43 +26516,6 @@ function OhhwellsBridge() {
26805
26516
  if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
26806
26517
  return () => window.removeEventListener("message", onSitePages);
26807
26518
  }, [isEditMode, postToParent2]);
26808
- (0, import_react23.useEffect)(() => {
26809
- if (!isEditMode) return;
26810
- const deps = {
26811
- getEditContent: () => editContentRef.current,
26812
- setEditContent: (next) => {
26813
- editContentRef.current = next;
26814
- },
26815
- applyLinkByKey,
26816
- postToParent: postToParent2
26817
- };
26818
- const onPageMessage = (e) => {
26819
- if (e.data?.type === "ow:page-created") {
26820
- const { path, title } = e.data;
26821
- if (typeof path !== "string" || !path) return;
26822
- if (addNavLinkForPage({ path, title: typeof title === "string" && title ? title : path }, deps)) {
26823
- enforceLinkHrefs();
26824
- }
26825
- return;
26826
- }
26827
- if (e.data?.type === "ow:page-renamed") {
26828
- const { oldPath, newPath, oldTitle, newTitle } = e.data;
26829
- if (typeof oldPath !== "string" || typeof newPath !== "string") return;
26830
- const moved = retargetPageLinks(
26831
- {
26832
- oldPath,
26833
- newPath,
26834
- oldTitle: typeof oldTitle === "string" ? oldTitle : void 0,
26835
- newTitle: typeof newTitle === "string" ? newTitle : void 0
26836
- },
26837
- deps
26838
- );
26839
- if (moved > 0) enforceLinkHrefs();
26840
- }
26841
- };
26842
- window.addEventListener("message", onPageMessage);
26843
- return () => window.removeEventListener("message", onPageMessage);
26844
- }, [isEditMode, postToParent2]);
26845
26519
  (0, import_react23.useEffect)(() => {
26846
26520
  if (!isEditMode || shouldUseDevFixtures()) return;
26847
26521
  void loadAllSectionsManifest().then((manifest) => {
@@ -28390,7 +28064,6 @@ function OhhwellsBridge() {
28390
28064
  applyAiFooterFromContent(content);
28391
28065
  if (typeof content[STYLE_STORE_KEY] === "string") {
28392
28066
  stylesRef.current = content[STYLE_STORE_KEY];
28393
- document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
28394
28067
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
28395
28068
  }
28396
28069
  applyBrandChrome(content);
@@ -28401,16 +28074,15 @@ function OhhwellsBridge() {
28401
28074
  if (key === "__ohw_sections") continue;
28402
28075
  if (key === AI_SECTIONS_KEY) continue;
28403
28076
  if (key === AI_FOOTER_KEY) continue;
28404
- if (key === LOGO_PLACEHOLDER_KEY) continue;
28405
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
28406
28077
  if (key === BRAND_KIT_KEY) continue;
28407
28078
  if (key === STYLE_STORE_KEY) continue;
28408
28079
  if (BRAND_CHROME_KEYS.has(key)) continue;
28080
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
28409
28081
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
28082
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
28410
28083
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
28411
28084
  continue;
28412
28085
  }
28413
- if (key.endsWith(SECTION_EMBED_SUFFIX)) continue;
28414
28086
  if (applyVideoSettingNode(key, val)) continue;
28415
28087
  if (applyCarouselNode(key, val)) continue;
28416
28088
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -28450,7 +28122,6 @@ function OhhwellsBridge() {
28450
28122
  });
28451
28123
  applyLinkByKey(key, val);
28452
28124
  }
28453
- applySectionEmbeds(content);
28454
28125
  applyLogoFromContent(content);
28455
28126
  applyLogoSizes(content);
28456
28127
  reconcileNavbarItemsFromContent(content);
@@ -28639,22 +28310,20 @@ function OhhwellsBridge() {
28639
28310
  }
28640
28311
  applyAiFooterFromContent(content);
28641
28312
  if (typeof content[STYLE_STORE_KEY] === "string") {
28642
- document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
28643
28313
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
28644
28314
  }
28645
28315
  for (const [key, val] of Object.entries(content)) {
28646
28316
  if (key === "__ohw_sections") continue;
28647
28317
  if (key === AI_SECTIONS_KEY) continue;
28648
28318
  if (key === AI_FOOTER_KEY) continue;
28649
- if (key === LOGO_PLACEHOLDER_KEY) continue;
28650
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
28651
28319
  if (key === BRAND_KIT_KEY) continue;
28652
28320
  if (key === STYLE_STORE_KEY) continue;
28653
28321
  if (BRAND_CHROME_KEYS.has(key)) continue;
28322
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
28323
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
28654
28324
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
28655
28325
  continue;
28656
28326
  }
28657
- if (key.endsWith(SECTION_EMBED_SUFFIX)) continue;
28658
28327
  if (applyVideoSettingNode(key, val)) continue;
28659
28328
  if (applyCarouselNode(key, val)) continue;
28660
28329
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -28679,7 +28348,6 @@ function OhhwellsBridge() {
28679
28348
  });
28680
28349
  applyLinkByKey(key, val);
28681
28350
  }
28682
- applySectionEmbeds(content);
28683
28351
  applyLogoFromContent(content);
28684
28352
  reconcileNavbarItemsFromContent(content);
28685
28353
  reconcileFooterOrderFromContent(content);
@@ -28919,11 +28587,26 @@ function OhhwellsBridge() {
28919
28587
  const t2 = setTimeout(measure, 500);
28920
28588
  const ro = new ResizeObserver(schedule);
28921
28589
  ro.observe(document.body);
28590
+ let lastWidth = window.innerWidth;
28591
+ let resizeTimers = [];
28592
+ const clearResizeTimers = () => {
28593
+ resizeTimers.forEach(clearTimeout);
28594
+ resizeTimers = [];
28595
+ };
28596
+ const handleResize = () => {
28597
+ if (window.innerWidth === lastWidth) return;
28598
+ lastWidth = window.innerWidth;
28599
+ clearResizeTimers();
28600
+ resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
28601
+ };
28602
+ window.addEventListener("resize", handleResize);
28922
28603
  return () => {
28923
28604
  clearTimeout(t1);
28924
28605
  clearTimeout(t2);
28925
28606
  if (raf != null) cancelAnimationFrame(raf);
28926
28607
  ro.disconnect();
28608
+ clearResizeTimers();
28609
+ window.removeEventListener("resize", handleResize);
28927
28610
  };
28928
28611
  }, [pathname, isEditMode, postToParent2]);
28929
28612
  (0, import_react23.useEffect)(() => {
@@ -29220,13 +28903,6 @@ function OhhwellsBridge() {
29220
28903
  if (target.closest("[data-ohw-state-toggle]")) return;
29221
28904
  if (target.closest("[data-ohw-max-badge]")) return;
29222
28905
  if (isInsideLinkEditor(target)) return;
29223
- if (isInsideFloatingPanel(target)) return;
29224
- if (target.closest("[data-ohw-form-toolbar]")) return;
29225
- if (target.closest(
29226
- '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
29227
- )) {
29228
- return;
29229
- }
29230
28906
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
29231
28907
  clearMediaSelectionRef.current();
29232
28908
  }
@@ -29250,6 +28926,8 @@ function OhhwellsBridge() {
29250
28926
  return;
29251
28927
  }
29252
28928
  }
28929
+ if (isInsideFloatingPanel(target)) return;
28930
+ if (target.closest("[data-ohw-form-toolbar]")) return;
29253
28931
  const collectionItem = matchCollectionItemClick(target, collectionItemsRef.current);
29254
28932
  if (collectionItem) {
29255
28933
  e.preventDefault();
@@ -29261,6 +28939,11 @@ function OhhwellsBridge() {
29261
28939
  });
29262
28940
  return;
29263
28941
  }
28942
+ if (target.closest(
28943
+ '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
28944
+ )) {
28945
+ return;
28946
+ }
29264
28947
  {
29265
28948
  const formEl = getFormElement(target);
29266
28949
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -29414,13 +29097,9 @@ function OhhwellsBridge() {
29414
29097
  if (isIconEditable(editable) && !getSocialItem(editable)) {
29415
29098
  e.preventDefault();
29416
29099
  e.stopPropagation();
29100
+ selectIconElementRef.current(editable);
29417
29101
  const aiIcon = editable.hasAttribute("data-ai-icon");
29418
29102
  const featureIcon = aiIcon && /\.features\.\d+\.icon$/.test(editable.dataset.ohwKey ?? "");
29419
- if (aiIcon) {
29420
- aiSectionApiRef.current?.selectFromElement(editable);
29421
- } else {
29422
- selectIconElementRef.current(editable);
29423
- }
29424
29103
  postToParentRef.current({
29425
29104
  type: "ow:icon-pick",
29426
29105
  key: editable.dataset.ohwKey ?? "",
@@ -29648,9 +29327,6 @@ function OhhwellsBridge() {
29648
29327
  setHoveredItemRect(null);
29649
29328
  hoveredNavContainerRef.current = null;
29650
29329
  setHoveredNavContainerRect(null);
29651
- siblingHintElRef.current = null;
29652
- setSiblingHintRect(null);
29653
- setSiblingHintRects([]);
29654
29330
  return;
29655
29331
  }
29656
29332
  {
@@ -29771,6 +29447,7 @@ function OhhwellsBridge() {
29771
29447
  hoveredNavContainerRef.current = null;
29772
29448
  setHoveredNavContainerRect(null);
29773
29449
  hoveredItemElRef.current = editable;
29450
+ setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
29774
29451
  }
29775
29452
  }
29776
29453
  }
@@ -30480,11 +30157,7 @@ function OhhwellsBridge() {
30480
30157
  };
30481
30158
  const handleMouseMove = (e) => {
30482
30159
  const { clientX, clientY } = e;
30483
- if (pointOwnedByFloatingPanel(clientX, clientY)) {
30484
- dismissImageHover();
30485
- clearImageHover();
30486
- return;
30487
- }
30160
+ if (pointOwnedByFloatingPanel(clientX, clientY)) return;
30488
30161
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) {
30489
30162
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
30490
30163
  formHoverElRef.current = null;
@@ -30513,12 +30186,7 @@ function OhhwellsBridge() {
30513
30186
  setHoveredItemRect(null);
30514
30187
  hoveredNavContainerRef.current = null;
30515
30188
  setHoveredNavContainerRect(null);
30516
- siblingHintElRef.current = null;
30517
- setSiblingHintRect(null);
30518
- setSiblingHintRects([]);
30519
30189
  dismissImageHover();
30520
- clearImageHover();
30521
- setSectionGap(null);
30522
30190
  return;
30523
30191
  }
30524
30192
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -30530,11 +30198,7 @@ function OhhwellsBridge() {
30530
30198
  if (e.data?.type !== "ow:pointer-sync") return;
30531
30199
  const { clientX, clientY } = e.data;
30532
30200
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
30533
- if (pointOwnedByFloatingPanel(clientX, clientY)) {
30534
- dismissImageHover();
30535
- clearImageHover();
30536
- return;
30537
- }
30201
+ if (pointOwnedByFloatingPanel(clientX, clientY)) return;
30538
30202
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) return;
30539
30203
  if (probeSocialsRowAt(clientX, clientY)) return;
30540
30204
  probeSectionGapAt(clientX, clientY);
@@ -30914,7 +30578,6 @@ function OhhwellsBridge() {
30914
30578
  applyAiFooterFromContent(content);
30915
30579
  if (typeof content[STYLE_STORE_KEY] === "string") {
30916
30580
  stylesRef.current = content[STYLE_STORE_KEY];
30917
- document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
30918
30581
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
30919
30582
  }
30920
30583
  applyBrandChrome(content);
@@ -30929,16 +30592,15 @@ function OhhwellsBridge() {
30929
30592
  }
30930
30593
  if (key === AI_SECTIONS_KEY) continue;
30931
30594
  if (key === AI_FOOTER_KEY) continue;
30932
- if (key === LOGO_PLACEHOLDER_KEY) continue;
30933
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
30934
30595
  if (key === BRAND_KIT_KEY) continue;
30935
30596
  if (key === STYLE_STORE_KEY) continue;
30936
30597
  if (BRAND_CHROME_KEYS.has(key)) continue;
30598
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
30937
30599
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
30600
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
30938
30601
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
30939
30602
  continue;
30940
30603
  }
30941
- if (key.endsWith(SECTION_EMBED_SUFFIX)) continue;
30942
30604
  if (applyVideoSettingNode(key, val)) continue;
30943
30605
  if (applyCarouselNode(key, val)) continue;
30944
30606
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -30964,7 +30626,6 @@ function OhhwellsBridge() {
30964
30626
  });
30965
30627
  applyLinkByKey(key, val);
30966
30628
  }
30967
- applySectionEmbeds(content);
30968
30629
  applyLogoFromContent(content);
30969
30630
  applyLogoSizes(content);
30970
30631
  document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
@@ -31572,12 +31233,6 @@ function OhhwellsBridge() {
31572
31233
  closeLinkPopoverRef.current();
31573
31234
  return;
31574
31235
  }
31575
- if (floatingPanelOpenRef.current) {
31576
- setFloatingPanelRef.current(null);
31577
- deselectRef.current();
31578
- deactivateRef.current();
31579
- return;
31580
- }
31581
31236
  deselectRef.current();
31582
31237
  deactivateRef.current();
31583
31238
  clearMediaSelectionRef.current();
@@ -32413,10 +32068,10 @@ function OhhwellsBridge() {
32413
32068
  window.removeEventListener("message", handleDuplicateSection);
32414
32069
  window.removeEventListener("message", handleDeactivate);
32415
32070
  window.removeEventListener("message", handleSelectNav);
32071
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
32416
32072
  window.removeEventListener("message", handleToastAction);
32417
32073
  window.removeEventListener("message", handleFormCount);
32418
32074
  window.removeEventListener("message", handleUiEscape);
32419
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
32420
32075
  autoSaveTimers.current.forEach(clearTimeout);
32421
32076
  autoSaveTimers.current.clear();
32422
32077
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -32710,7 +32365,7 @@ function OhhwellsBridge() {
32710
32365
  postToParent2({
32711
32366
  type: "ow:ready",
32712
32367
  version: "1",
32713
- bridgeVersion: "0.1.160-next.429",
32368
+ bridgeVersion: "0.1.161",
32714
32369
  path: pathname,
32715
32370
  nodes: collectEditableNodes(editContentRef.current),
32716
32371
  sections