@ohhwells/bridge 0.1.159 → 0.1.160-next.428

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 paint = parseColor(paintColor(seed)) ?? { r: 15, g: 15, b: 15, a: 1 };
1212
- const luminance = relativeLuminance(paint);
1211
+ const paint2 = parseColor(paintColor(seed)) ?? { r: 15, g: 15, b: 15, a: 1 };
1212
+ const luminance = relativeLuminance(paint2);
1213
1213
  return {
1214
- background: toCss({ ...paint, a: 1 }),
1214
+ background: toCss({ ...paint2, 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 paint = accentColor(primary);
1221
+ const paint2 = accentColor(primary);
1222
1222
  return {
1223
1223
  background: TRANSPARENT,
1224
- color: paint,
1225
- borderColor: paint,
1224
+ color: paint2,
1225
+ borderColor: paint2,
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 paint = accentColor(primary);
1238
+ const paint2 = accentColor(primary);
1239
1239
  const text = parseColor(outline.color);
1240
- const paintRgb = parseColor(paint);
1240
+ const paintRgb = parseColor(paint2);
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: paint,
1250
- borderColor: hasBorder ? paint : outline.borderColor
1249
+ color: paint2,
1250
+ borderColor: hasBorder ? paint2 : outline.borderColor
1251
1251
  };
1252
1252
  }
1253
1253
  function generateButtonVariants(samples) {
@@ -6763,6 +6763,21 @@ 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
+ }
6766
6781
  function addPricingCardFeature(tree, index, text) {
6767
6782
  return withPricingCollection(tree, (collection) => {
6768
6783
  const children = collection.children;
@@ -6771,7 +6786,7 @@ function addPricingCardFeature(tree, index, text) {
6771
6786
  const features = pricingFeatureList(target);
6772
6787
  if (features.length >= PRICING_FEATURES_MAX) return false;
6773
6788
  target.slots = target.slots ?? {};
6774
- target.slots.features = [...features, { text }];
6789
+ target.slots.features = [...features, { text, ...prevailingFeatureIcon(features) }];
6775
6790
  return true;
6776
6791
  });
6777
6792
  }
@@ -7383,6 +7398,23 @@ function applyCustomCodeToHead(content) {
7383
7398
  if (beforeNodes.length > 0) document.head.append(...beforeNodes);
7384
7399
  }
7385
7400
 
7401
+ // src/lib/node-font-size.ts
7402
+ var TEXT_NODE_TYPES = /* @__PURE__ */ new Set(["text", "text/rich", "plain", "link"]);
7403
+ function escapeKey(key) {
7404
+ return typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(key) : key.replace(/["\\]/g, "\\$&");
7405
+ }
7406
+ function measureNodeFontSize(key, type, root) {
7407
+ if (!TEXT_NODE_TYPES.has(type)) return void 0;
7408
+ const el = root.querySelector(`[data-ohw-editable][data-ohw-key="${escapeKey(key)}"]`);
7409
+ if (!el) return void 0;
7410
+ const size = Number.parseFloat(window.getComputedStyle(el).fontSize);
7411
+ return Number.isFinite(size) && size > 0 ? Math.round(size) : void 0;
7412
+ }
7413
+ function withNodeFontSize(node, root) {
7414
+ const fontSize = measureNodeFontSize(node.key, node.type, root);
7415
+ return fontSize === void 0 ? node : { ...node, fontSize };
7416
+ }
7417
+
7386
7418
  // src/lib/selectNavContainer.ts
7387
7419
  function isOnScreen(el) {
7388
7420
  const rect = el.getBoundingClientRect();
@@ -7398,6 +7430,120 @@ function findSelectableNavContainer(doc = document) {
7398
7430
  );
7399
7431
  }
7400
7432
 
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
+
7401
7547
  // src/ui/ai-tree/aiSectionsManager.tsx
7402
7548
  var import_react_dom = require("react-dom");
7403
7549
  var import_client = require("react-dom/client");
@@ -7971,7 +8117,7 @@ var AI_MOBILE_CSS = [
7971
8117
  // page's first section. Top only (bottom needs no nav clearance); ~96px approximates the
7972
8118
  // template nav's own height (measured ~94px) — there's no shared `--ohw-header-h` var to read
7973
8119
  // an exact figure from (no live measurement exists), so this is a safe approximation, not a
7974
- // pixel-exact fit. Horizontal spacing already comes from `data-ai-container`.
8120
+ // pixel-exact fit. Horizontal spacing already comes from `data-ai-section-inner`.
7975
8121
  '[data-ai-overlay-clearance] [data-ai-overlap-layer="overlay"]{padding-top:96px}',
7976
8122
  // A full-bleed carousel's height is pure arithmetic: it spans the whole window, so 16/9 turns
7977
8123
  // window width straight into height. Measured at 16/9 it came out ~90% of the viewport at EVERY
@@ -7987,8 +8133,10 @@ var AI_MOBILE_CSS = [
7987
8133
  "[data-ai-section]{overflow-x:hidden}",
7988
8134
  // `:not([data-ai-full-bleed])`: this `!important` gutter beat the inline `padding: 0` that a
7989
8135
  // full-bleed section's container sets, so a carousel/media hero that is edge-to-edge on desktop
7990
- // still got a 20px band of section background down both sides on a phone.
7991
- "[data-ai-container]:not([data-ai-full-bleed]){padding:0 20px !important}",
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}",
7992
8140
  "[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
7993
8141
  "[data-ai-cell]{width:100%;min-width:0}",
7994
8142
  // The rule above turns every row into a COLUMN flex container on phones, which silently breaks
@@ -8193,6 +8341,31 @@ function pricingCardEmphasis(featured, ctx, hasBg) {
8193
8341
  function textAttrs(ctx, path) {
8194
8342
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
8195
8343
  }
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");
8196
8369
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
8197
8370
  function MediaBox({
8198
8371
  refValue,
@@ -8247,7 +8420,7 @@ function MediaBox({
8247
8420
  position: fill ? "absolute" : void 0,
8248
8421
  inset: fill ? 0 : void 0,
8249
8422
  width: fill ? "100%" : fixedSize ? "auto" : "100%",
8250
- height: fill ? "100%" : void 0,
8423
+ height: fill ? "100%" : fixedSize ? height : void 0,
8251
8424
  aspectRatio: fill ? void 0 : aspectRatio,
8252
8425
  minHeight: fill || fixedSize ? void 0 : height ?? (aspectRatio ? void 0 : 180),
8253
8426
  borderRadius: radius,
@@ -9743,7 +9916,7 @@ function CollectionBlock({ node, ctx, path }) {
9743
9916
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
9744
9917
  "div",
9745
9918
  {
9746
- "data-ai-grid": "",
9919
+ "data-ai-grid": String(itemsPerRow),
9747
9920
  "data-ohw-pricing-collection": isPricing ? "" : void 0,
9748
9921
  style: {
9749
9922
  display: "grid",
@@ -10045,7 +10218,7 @@ function renderNode(node, ctx, path, ambientAlign) {
10045
10218
  },
10046
10219
  children: [
10047
10220
  i > 0 && ctx.heroOverlapFullBleed ? (
10048
- // The section's `data-ai-container` gave up its max-width/gutter so the base
10221
+ // The section's `data-ai-section-inner` gave up its max-width/gutter so the base
10049
10222
  // layer (this group's i===0 sibling) can span edge-to-edge — reintroduced here,
10050
10223
  // scoped to just the overlay text, so a heading/copy/CTA stack doesn't stretch
10051
10224
  // the full section width instead of reading at the usual content width.
@@ -10464,6 +10637,7 @@ function AiTreeRenderer({
10464
10637
  ...fillHeight ? { "data-ai-fill-height": "" } : {},
10465
10638
  ...needsOverlayClearance ? { "data-ai-overlay-clearance": "" } : {},
10466
10639
  ...bgAttrs,
10640
+ "data-ai-responsive": "",
10467
10641
  style: {
10468
10642
  position: "relative",
10469
10643
  padding: `${padCss} 0`,
@@ -10477,12 +10651,13 @@ function AiTreeRenderer({
10477
10651
  ...fillHeight ? { display: "grid" } : {}
10478
10652
  },
10479
10653
  children: [
10480
- isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10654
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
10481
10655
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
10656
+ isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
10482
10657
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
10483
10658
  "div",
10484
10659
  {
10485
- "data-ai-container": "",
10660
+ "data-ai-section-inner": "",
10486
10661
  ...fullBleedMedia ? { "data-ai-full-bleed": "" } : {},
10487
10662
  style: {
10488
10663
  position: "relative",
@@ -10499,7 +10674,7 @@ function AiTreeRenderer({
10499
10674
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
10500
10675
  "div",
10501
10676
  {
10502
- "data-ai-row": "",
10677
+ "data-ai-columns": "",
10503
10678
  style: {
10504
10679
  display: "grid",
10505
10680
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -11161,9 +11336,10 @@ function applyAiSectionsToDom(state, options) {
11161
11336
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
11162
11337
  continue;
11163
11338
  }
11164
- const isNewMount = !existing || !existing.container.isConnected;
11165
11339
  let container = existing?.container;
11340
+ let isNewMount = false;
11166
11341
  if (!container || !container.isConnected) {
11342
+ isNewMount = true;
11167
11343
  existing?.root.unmount();
11168
11344
  container?.remove();
11169
11345
  container = document.createElement("div");
@@ -11501,6 +11677,34 @@ function minSpanForFooterBlock(type) {
11501
11677
  return 3;
11502
11678
  }
11503
11679
  }
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
+ }
11504
11708
  function collectBrandBlocks(tree) {
11505
11709
  const brand = [];
11506
11710
  for (const col of tree.columns) {
@@ -11746,6 +11950,7 @@ function textOf2(el) {
11746
11950
  }
11747
11951
  function syncFooterTreeLinksFromDom(tree) {
11748
11952
  const next = cloneTree(tree);
11953
+ syncSocialRowsFromDom(next);
11749
11954
  const domCols = listFooterColumns().map((col) => {
11750
11955
  const heading = getFooterColumnHeadingEl(col);
11751
11956
  const title = heading ? textOf2(heading) : void 0;
@@ -17322,7 +17527,7 @@ function apiBase() {
17322
17527
  function bridgeVersion() {
17323
17528
  try {
17324
17529
  if (true) {
17325
- return "0.1.159";
17530
+ return "0.1.160-next.428";
17326
17531
  }
17327
17532
  } catch {
17328
17533
  }
@@ -17336,7 +17541,7 @@ function stampBridgeVersionOnDocument() {
17336
17541
  return;
17337
17542
  }
17338
17543
  try {
17339
- const v = true ? "0.1.159" : "";
17544
+ const v = true ? "0.1.160-next.428" : "";
17340
17545
  if (v) {
17341
17546
  document.documentElement.setAttribute("data-ohw-bridge-version", v);
17342
17547
  window.__OHW_BRIDGE_VERSION__ = v;
@@ -18014,6 +18219,7 @@ function MediaOverlay({
18014
18219
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
18015
18220
  );
18016
18221
  }, [isVideo]);
18222
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
18017
18223
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
18018
18224
  const box = {
18019
18225
  position: "fixed",
@@ -18112,11 +18318,10 @@ function MediaOverlay({
18112
18318
  className: "flex items-center justify-center cursor-pointer",
18113
18319
  style: {
18114
18320
  ...box,
18115
- // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
18116
- // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
18117
- // in-document, pointer-events does it natively. The button below opts back in, so
18118
- // Replace still works.
18119
- pointerEvents: hover.hasTextOverlap || cueOnly ? "none" : "auto",
18321
+ // Visual only. `pointer-events: auto` on this full-bleed rect covered the navbar
18322
+ // (hero bg includes the absolute TopNav) and swallowed CTA press-drag. Text overlay
18323
+ // and cueOnly also need clicks to fall through — Replace / video controls opt back in.
18324
+ pointerEvents: "none",
18120
18325
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
18121
18326
  // than hovered. Hover keeps the existing tinted preview.
18122
18327
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -18143,17 +18348,17 @@ function MediaOverlay({
18143
18348
  },
18144
18349
  children: [
18145
18350
  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 }),
18146
- isVideo ? "Replace video" : "Replace image"
18351
+ replaceLabel
18147
18352
  ]
18148
18353
  }
18149
18354
  ),
18150
- replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
18355
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
18151
18356
  Button,
18152
18357
  {
18153
18358
  "data-ohw-media-overlay": "",
18154
18359
  variant: "outline",
18155
18360
  size: "sm",
18156
- "aria-label": isVideo ? "Replace video" : "Replace image",
18361
+ "aria-label": replaceLabel,
18157
18362
  className: "gap-1.5 cursor-pointer hover:bg-background",
18158
18363
  style: {
18159
18364
  ...OVERLAY_BUTTON_STYLE,
@@ -18176,7 +18381,7 @@ function MediaOverlay({
18176
18381
  },
18177
18382
  children: [
18178
18383
  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 }),
18179
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
18384
+ replaceMode === "full" ? replaceLabel : null
18180
18385
  ]
18181
18386
  }
18182
18387
  )
@@ -18770,7 +18975,7 @@ function AiSectionOverlay({
18770
18975
 
18771
18976
  // src/ui/ai-section/AiMediaStripOverlay.tsx
18772
18977
  var import_react10 = require("react");
18773
- var import_lucide_react8 = require("lucide-react");
18978
+ var import_lucide_react9 = require("lucide-react");
18774
18979
 
18775
18980
  // src/lib/ai-media-strip-tree.ts
18776
18981
  var MEDIA_STRIP_ITEM_MIN = 1;
@@ -18884,14 +19089,6 @@ function setMediaStripItemAsset(tree, index, assetId) {
18884
19089
  return true;
18885
19090
  });
18886
19091
  }
18887
- function setMediaStripItemAlt(tree, index, alt) {
18888
- return withMediaStrip(tree, (block) => {
18889
- const items = block.items;
18890
- if (index < 0 || index >= items.length) return false;
18891
- items[index] = { ...items[index], alt };
18892
- return true;
18893
- });
18894
- }
18895
19092
  function setMediaStripItemLink(tree, index, link) {
18896
19093
  return withMediaStrip(tree, (block) => {
18897
19094
  const items = block.items;
@@ -18909,11 +19106,117 @@ function setMediaStripItemLink(tree, index, link) {
18909
19106
  });
18910
19107
  }
18911
19108
 
18912
- // src/ui/ai-section/AiMediaStripOverlay.tsx
19109
+ // src/ui/dialog.tsx
19110
+ var React9 = __toESM(require("react"), 1);
19111
+ var import_radix_ui6 = require("radix-ui");
19112
+ var import_lucide_react8 = require("lucide-react");
18913
19113
  var import_jsx_runtime20 = require("react/jsx-runtime");
18914
- var PRIMARY3 = "#0885FE";
18915
- var OHW_BLUE = `var(--ohw-primary, ${PRIMARY3})`;
18916
- var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY3}) 60%, #000000)`;
19114
+ function Dialog2({
19115
+ ...props
19116
+ }) {
19117
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_radix_ui6.Dialog.Root, { "data-slot": "dialog", ...props });
19118
+ }
19119
+ function DialogPortal({
19120
+ ...props
19121
+ }) {
19122
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_radix_ui6.Dialog.Portal, { ...props });
19123
+ }
19124
+ function DialogOverlay({
19125
+ className,
19126
+ ...props
19127
+ }) {
19128
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19129
+ import_radix_ui6.Dialog.Overlay,
19130
+ {
19131
+ "data-slot": "dialog-overlay",
19132
+ "data-ohw-link-modal-root": "",
19133
+ className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
19134
+ ...props
19135
+ }
19136
+ );
19137
+ }
19138
+ var DialogContent = React9.forwardRef(
19139
+ ({ className, children, showCloseButton = true, container, ...props }, ref) => {
19140
+ const positionMode = container ? "absolute" : "fixed";
19141
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(DialogPortal, { container: container ?? void 0, children: [
19142
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
19143
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
19144
+ import_radix_ui6.Dialog.Content,
19145
+ {
19146
+ ref,
19147
+ "data-slot": "dialog-content",
19148
+ className: cn(
19149
+ positionMode,
19150
+ "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",
19151
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
19152
+ container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
19153
+ className
19154
+ ),
19155
+ onMouseDown: (e) => e.stopPropagation(),
19156
+ ...props,
19157
+ children: [
19158
+ children,
19159
+ showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19160
+ import_radix_ui6.Dialog.Close,
19161
+ {
19162
+ type: "button",
19163
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
19164
+ "aria-label": "Close",
19165
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react8.X, { size: 16, "aria-hidden": true })
19166
+ }
19167
+ ) : null
19168
+ ]
19169
+ }
19170
+ )
19171
+ ] });
19172
+ }
19173
+ );
19174
+ DialogContent.displayName = import_radix_ui6.Dialog.Content.displayName;
19175
+ function DialogHeader({
19176
+ className,
19177
+ ...props
19178
+ }) {
19179
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
19180
+ }
19181
+ function DialogFooter({
19182
+ className,
19183
+ ...props
19184
+ }) {
19185
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19186
+ "div",
19187
+ {
19188
+ className: cn("flex items-center justify-end gap-2", className),
19189
+ ...props
19190
+ }
19191
+ );
19192
+ }
19193
+ var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19194
+ import_radix_ui6.Dialog.Title,
19195
+ {
19196
+ ref,
19197
+ className: cn(
19198
+ "text-lg font-semibold leading-none tracking-tight text-card-foreground",
19199
+ className
19200
+ ),
19201
+ ...props
19202
+ }
19203
+ ));
19204
+ DialogTitle.displayName = import_radix_ui6.Dialog.Title.displayName;
19205
+ var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19206
+ import_radix_ui6.Dialog.Description,
19207
+ {
19208
+ ref,
19209
+ className: cn("text-sm text-muted-foreground", className),
19210
+ ...props
19211
+ }
19212
+ ));
19213
+ DialogDescription.displayName = import_radix_ui6.Dialog.Description.displayName;
19214
+ var DialogClose = import_radix_ui6.Dialog.Close;
19215
+
19216
+ // src/ui/ai-section/AiMediaStripOverlay.tsx
19217
+ var import_jsx_runtime21 = require("react/jsx-runtime");
19218
+ var PRIMARY3 = "var(--ohw-primary, #0885FE)";
19219
+ 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]";
18917
19220
  function findMediaStripItemElement(sectionId, index) {
18918
19221
  const sectionEl = findByInstanceId(sectionId);
18919
19222
  if (!sectionEl) return null;
@@ -18923,14 +19226,35 @@ function findMediaStripItemElement(sectionId, index) {
18923
19226
  }
18924
19227
  return null;
18925
19228
  }
18926
- function readRect2(ref) {
18927
- const el = ref && findMediaStripItemElement(ref.sectionId, ref.index);
19229
+ function itemRefFromTarget(target) {
19230
+ if (!(target instanceof Element)) return null;
19231
+ if (target.closest(CHROME_SELECTOR)) return null;
19232
+ const itemEl = target.closest("[data-ohw-media-strip-item]");
19233
+ if (!itemEl) return null;
19234
+ const sectionEl = itemEl.closest("[data-ohw-section]");
19235
+ const unit = sectionEl ? movableUnit(sectionEl) : null;
19236
+ const sectionId = unit ? instanceIdOf(unit) || null : null;
19237
+ const index = Number(itemEl.dataset.ohwMediaStripIndex);
19238
+ if (!sectionId || Number.isNaN(index)) return null;
19239
+ return { sectionId, index };
19240
+ }
19241
+ function sameRef(a, b) {
19242
+ return a?.sectionId === b?.sectionId && a?.index === b?.index;
19243
+ }
19244
+ function rectOf(el) {
18928
19245
  if (!el) return null;
18929
19246
  const r2 = el.getBoundingClientRect();
18930
19247
  if (r2.width <= 0 || r2.height <= 0) return null;
18931
19248
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
18932
19249
  }
18933
- function useLiveItemRect(ref) {
19250
+ function readRect2(ref) {
19251
+ return rectOf(ref && findMediaStripItemElement(ref.sectionId, ref.index));
19252
+ }
19253
+ function readStripRect(ref) {
19254
+ const item = ref && findMediaStripItemElement(ref.sectionId, ref.index);
19255
+ return rectOf(item?.closest("[data-ohw-media-strip]") ?? item);
19256
+ }
19257
+ function useLiveItemRect(ref, strip = false) {
18934
19258
  const [rect, setRect] = (0, import_react10.useState)(null);
18935
19259
  (0, import_react10.useEffect)(() => {
18936
19260
  if (!ref) {
@@ -18938,7 +19262,7 @@ function useLiveItemRect(ref) {
18938
19262
  return;
18939
19263
  }
18940
19264
  const update = () => {
18941
- const next = readRect2(ref);
19265
+ const next = strip ? readStripRect(ref) : readRect2(ref);
18942
19266
  setRect(
18943
19267
  (prev) => prev && next && prev.top === next.top && prev.left === next.left && prev.width === next.width && prev.height === next.height ? prev : next
18944
19268
  );
@@ -18953,9 +19277,49 @@ function useLiveItemRect(ref) {
18953
19277
  window.removeEventListener("resize", update);
18954
19278
  clearInterval(interval);
18955
19279
  };
18956
- }, [ref?.sectionId, ref?.index]);
19280
+ }, [ref?.sectionId, ref?.index, strip]);
18957
19281
  return rect;
18958
19282
  }
19283
+ var TOOLBAR_STRIP_GAP = 52;
19284
+ var TOOLBAR_STRIP_GAP_MOBILE = 42;
19285
+ var TOOLBAR_EDGE_MARGIN2 = 4;
19286
+ function StripToolbarSlot({ stripRect, children }) {
19287
+ const slotRef = (0, import_react10.useRef)(null);
19288
+ const [shift, setShift] = (0, import_react10.useState)(0);
19289
+ const centerX = stripRect.left + stripRect.width / 2;
19290
+ (0, import_react10.useLayoutEffect)(() => {
19291
+ const slot = slotRef.current;
19292
+ if (!slot) return;
19293
+ const clamp = () => {
19294
+ const w = slot.offsetWidth;
19295
+ if (w <= 0) return;
19296
+ const half = w / 2;
19297
+ const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
19298
+ setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
19299
+ };
19300
+ clamp();
19301
+ const ro = new ResizeObserver(clamp);
19302
+ ro.observe(slot);
19303
+ return () => ro.disconnect();
19304
+ }, [centerX, children]);
19305
+ const mobile = window.innerWidth <= 480;
19306
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19307
+ "div",
19308
+ {
19309
+ ref: slotRef,
19310
+ "data-ohw-bridge": "",
19311
+ "data-ohw-item-interaction": "",
19312
+ className: "pointer-events-auto fixed",
19313
+ style: {
19314
+ top: stripRect.top - (mobile ? TOOLBAR_STRIP_GAP_MOBILE : TOOLBAR_STRIP_GAP),
19315
+ left: centerX + shift,
19316
+ transform: "translate(-50%, -100%)",
19317
+ zIndex: 2147483645
19318
+ },
19319
+ children
19320
+ }
19321
+ );
19322
+ }
18959
19323
  function requestValidateTree(postToParent2, tree) {
18960
19324
  return new Promise((resolve) => {
18961
19325
  const id = `ai-validate-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -18992,116 +19356,72 @@ function requestAssetPick(postToParent2) {
18992
19356
  postToParent2({ type: "ow:asset-pick", id });
18993
19357
  });
18994
19358
  }
18995
- function Pill({
19359
+ function ToolbarAction({
18996
19360
  label,
18997
19361
  onClick,
18998
- disabled,
18999
- active
19362
+ disabled = false,
19363
+ active = false,
19364
+ children
19000
19365
  }) {
19001
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19002
- "button",
19003
- {
19004
- type: "button",
19005
- onClick,
19006
- disabled,
19007
- style: {
19008
- height: 28,
19009
- padding: "0 10px",
19010
- display: "inline-flex",
19011
- alignItems: "center",
19012
- gap: 4,
19013
- borderRadius: 999,
19014
- border: "none",
19015
- cursor: disabled ? "default" : "pointer",
19016
- background: active ? OHW_BLUE_DARK : "#ffffff",
19017
- color: active ? "#ffffff" : OHW_BLUE_DARK,
19018
- opacity: disabled ? 0.4 : 1,
19019
- fontSize: 12,
19020
- lineHeight: "16px",
19021
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
19022
- whiteSpace: "nowrap",
19023
- flex: "0 0 auto"
19024
- },
19025
- children: label
19026
- }
19027
- );
19366
+ const button = /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, active, onClick, className: "size-8", children });
19367
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19368
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "inline-flex", children: button }) : button }),
19369
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19370
+ ] });
19028
19371
  }
19029
- function AltInput({ value, onCommit }) {
19030
- const [draft, setDraft] = (0, import_react10.useState)(value);
19031
- (0, import_react10.useEffect)(() => setDraft(value), [value]);
19032
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
19033
- "div",
19034
- {
19035
- style: {
19036
- display: "flex",
19037
- alignItems: "center",
19038
- gap: 6,
19039
- height: 28,
19040
- padding: "0 12px",
19041
- borderRadius: 999,
19042
- background: "#ffffff",
19043
- width: "100%",
19044
- maxWidth: 260,
19045
- boxSizing: "border-box"
19046
- },
19047
- children: [
19048
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19049
- "span",
19050
- {
19051
- style: {
19052
- flex: "0 0 auto",
19053
- color: OHW_BLUE_DARK,
19054
- fontSize: 10,
19055
- fontWeight: 700,
19056
- textTransform: "uppercase",
19057
- letterSpacing: 0.4
19058
- },
19059
- children: "Alt"
19060
- }
19061
- ),
19062
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19063
- "input",
19372
+ function ToolbarSeparator() {
19373
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19374
+ }
19375
+ function OptionMenu({
19376
+ label,
19377
+ value,
19378
+ options,
19379
+ onChange
19380
+ }) {
19381
+ const current = options.find((o) => o.value === value) ?? options[0];
19382
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19383
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xs text-muted-foreground", children: label }),
19384
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DropdownMenu, { modal: false, children: [
19385
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19386
+ "button",
19387
+ {
19388
+ type: "button",
19389
+ "aria-label": label,
19390
+ 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",
19391
+ children: [
19392
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "flex-1 text-left", children: current.label }),
19393
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ChevronDown, { size: 16, style: { pointerEvents: "none" } })
19394
+ ]
19395
+ }
19396
+ ) }),
19397
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-[132px] min-w-[132px] p-1", children: options.map((option) => {
19398
+ const selected = option.value === value;
19399
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19400
+ DropdownMenuItem,
19064
19401
  {
19065
- type: "text",
19066
- value: draft,
19067
- onChange: (e) => setDraft(e.target.value),
19068
- onBlur: () => {
19069
- if (draft !== value) onCommit(draft);
19070
- },
19071
- onKeyDown: (e) => {
19072
- if (e.key === "Enter") e.currentTarget.blur();
19073
- if (e.key === "Escape") {
19074
- setDraft(value);
19075
- e.currentTarget.blur();
19076
- }
19077
- },
19078
- placeholder: "Describe this image",
19079
- "aria-label": "Image alt text",
19080
- style: {
19081
- flex: 1,
19082
- minWidth: 0,
19083
- border: "none",
19084
- outline: "none",
19085
- background: "transparent",
19086
- fontSize: 12,
19087
- lineHeight: "16px",
19088
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
19089
- color: "#1f2937"
19090
- }
19091
- }
19092
- )
19093
- ]
19094
- }
19095
- );
19402
+ role: "menuitemradio",
19403
+ "aria-checked": selected,
19404
+ onSelect: () => onChange(option.value),
19405
+ className: cn("grid grid-cols-[1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
19406
+ children: [
19407
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: option.label }),
19408
+ selected && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Check, { size: 16, className: "text-bridge-primary" })
19409
+ ]
19410
+ },
19411
+ option.value
19412
+ );
19413
+ }) })
19414
+ ] })
19415
+ ] });
19096
19416
  }
19097
19417
  var VARIANTS = [
19098
19418
  { value: "logos", label: "Logos" },
19099
19419
  { value: "reel", label: "Reel" }
19100
19420
  ];
19101
19421
  var ALIGNS = [
19102
- { value: "left", label: "Left" },
19103
- { value: "center", label: "Center" },
19104
- { value: "right", label: "Right" }
19422
+ { value: "left", label: "Align left", Icon: import_lucide_react9.AlignLeft },
19423
+ { value: "center", label: "Align center", Icon: import_lucide_react9.AlignCenter },
19424
+ { value: "right", label: "Align right", Icon: import_lucide_react9.AlignRight }
19105
19425
  ];
19106
19426
  var HOVERS = [
19107
19427
  { value: "none", label: "None" },
@@ -19109,104 +19429,104 @@ var HOVERS = [
19109
19429
  { value: "zoom", label: "Zoom" },
19110
19430
  { value: "fade", label: "Fade" }
19111
19431
  ];
19112
- var TREATMENTS = [
19113
- { value: "color", label: "Color" },
19114
- { value: "grayscale", label: "Grayscale" }
19115
- ];
19116
19432
  var HTTP_URL_RE = /^https?:\/\/\S+$/i;
19117
- function LinkInput({ value, onCommit }) {
19118
- const [draft, setDraft] = (0, import_react10.useState)(value);
19433
+ function LinkModal({
19434
+ href,
19435
+ linkTarget,
19436
+ onClose,
19437
+ onSave
19438
+ }) {
19439
+ const [draft, setDraft] = (0, import_react10.useState)(href);
19440
+ const [newTab, setNewTab] = (0, import_react10.useState)(linkTarget !== "same-tab");
19119
19441
  const [invalid, setInvalid] = (0, import_react10.useState)(false);
19120
- (0, import_react10.useEffect)(() => {
19121
- setDraft(value);
19122
- setInvalid(false);
19123
- }, [value]);
19124
- const commit = () => {
19442
+ const save = () => {
19125
19443
  const next = draft.trim();
19126
- if (next === value) return setInvalid(false);
19127
- if (next && !HTTP_URL_RE.test(next)) return setInvalid(true);
19128
- setInvalid(false);
19129
- onCommit(next);
19130
- };
19131
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
19132
- "div",
19133
- {
19134
- style: {
19135
- display: "flex",
19136
- alignItems: "center",
19137
- gap: 6,
19138
- height: 28,
19139
- padding: "0 12px",
19140
- borderRadius: 999,
19141
- background: "#ffffff",
19142
- width: "100%",
19143
- maxWidth: 260,
19144
- boxSizing: "border-box",
19145
- boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
19146
- },
19147
- children: [
19148
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19149
- "span",
19150
- {
19151
- style: {
19152
- flex: "0 0 auto",
19153
- color: OHW_BLUE_DARK,
19154
- fontSize: 10,
19155
- fontWeight: 700,
19156
- textTransform: "uppercase",
19157
- letterSpacing: 0.4
19158
- },
19159
- children: "Link"
19160
- }
19161
- ),
19162
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19163
- "input",
19164
- {
19165
- type: "text",
19166
- inputMode: "url",
19167
- value: draft,
19168
- onChange: (e) => setDraft(e.target.value),
19169
- onBlur: commit,
19170
- onKeyDown: (e) => {
19171
- if (e.key === "Enter") e.currentTarget.blur();
19172
- if (e.key === "Escape") {
19173
- setDraft(value);
19174
- setInvalid(false);
19175
- e.currentTarget.blur();
19176
- }
19177
- },
19178
- placeholder: "https://example.com",
19179
- "aria-label": "Image link URL",
19180
- "aria-invalid": invalid,
19181
- style: {
19182
- flex: 1,
19183
- minWidth: 0,
19184
- border: "none",
19185
- outline: "none",
19186
- background: "transparent",
19187
- fontSize: 12,
19188
- lineHeight: "16px",
19189
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
19190
- color: "#1f2937"
19191
- }
19192
- }
19193
- )
19194
- ]
19444
+ if (next && !HTTP_URL_RE.test(next)) {
19445
+ setInvalid(true);
19446
+ return;
19195
19447
  }
19196
- );
19448
+ onSave(next, newTab ? "new-tab" : "same-tab");
19449
+ onClose();
19450
+ };
19451
+ 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: [
19452
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DialogHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DialogTitle, { className: "text-base", children: "Image link" }) }),
19453
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex flex-col gap-3", children: [
19454
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19455
+ "input",
19456
+ {
19457
+ autoFocus: true,
19458
+ type: "text",
19459
+ inputMode: "url",
19460
+ value: draft,
19461
+ onChange: (e) => {
19462
+ setDraft(e.target.value);
19463
+ setInvalid(false);
19464
+ },
19465
+ onKeyDown: (e) => {
19466
+ if (e.key === "Enter") save();
19467
+ },
19468
+ placeholder: "https://example.com",
19469
+ "aria-label": "Image link URL",
19470
+ "aria-invalid": invalid,
19471
+ className: cn(
19472
+ "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",
19473
+ invalid && "border-destructive focus:border-destructive"
19474
+ )
19475
+ }
19476
+ ),
19477
+ invalid && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-xs text-destructive", children: "Enter a full URL starting with http:// or https://" }),
19478
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("label", { className: "flex items-center gap-2 text-[13px] text-foreground", children: [
19479
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("input", { type: "checkbox", checked: newTab, onChange: (e) => setNewTab(e.target.checked) }),
19480
+ "Open in new tab"
19481
+ ] })
19482
+ ] }),
19483
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DialogFooter, { children: [
19484
+ href && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19485
+ "button",
19486
+ {
19487
+ type: "button",
19488
+ onClick: () => {
19489
+ onSave("", "new-tab");
19490
+ onClose();
19491
+ },
19492
+ className: "mr-auto rounded-[6px] px-3 py-2 text-[13px] font-medium text-destructive transition-colors hover:bg-muted",
19493
+ children: "Remove link"
19494
+ }
19495
+ ),
19496
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19497
+ "button",
19498
+ {
19499
+ type: "button",
19500
+ onClick: onClose,
19501
+ className: "rounded-[6px] px-3 py-2 text-[13px] font-medium text-foreground transition-colors hover:bg-muted",
19502
+ children: "Cancel"
19503
+ }
19504
+ ),
19505
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19506
+ "button",
19507
+ {
19508
+ type: "button",
19509
+ onClick: save,
19510
+ className: "rounded-[6px] bg-bridge-primary px-3 py-2 text-[13px] font-medium text-primary-foreground transition-opacity hover:opacity-90",
19511
+ children: "Save"
19512
+ }
19513
+ )
19514
+ ] })
19515
+ ] }) });
19197
19516
  }
19198
19517
  function MediaStripToolbar({
19199
19518
  entry,
19200
19519
  index,
19201
19520
  pending,
19202
19521
  error,
19522
+ itemEditOn,
19523
+ onToggleItemEdit,
19203
19524
  onSetVariant,
19204
19525
  onSetAlign,
19205
19526
  onSetHover,
19206
19527
  onSetTreatment,
19207
19528
  onResize,
19208
- onSetAlt,
19209
- onSetLink,
19529
+ onOpenLink,
19210
19530
  onAddItem,
19211
19531
  onRemoveItem,
19212
19532
  onChangeImage,
@@ -19215,81 +19535,102 @@ function MediaStripToolbar({
19215
19535
  }) {
19216
19536
  const items = mediaStripItems(entry.tree);
19217
19537
  const itemCount = items.length;
19218
- const currentAlt = items[index]?.alt ?? "";
19219
- const currentHref = items[index]?.href ?? "";
19220
- const currentTarget = items[index]?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
19221
19538
  const slots = findMediaStripSlots(entry.tree);
19222
19539
  const variant = slots?.variant === "reel" ? "reel" : "logos";
19223
19540
  const align = slots?.align === "left" || slots?.align === "right" ? slots.align : "center";
19224
19541
  const hover = slots?.hover === "none" || slots?.hover === "lift" || slots?.hover === "zoom" || slots?.hover === "fade" ? slots.hover : "lift";
19225
19542
  const treatment = slots?.treatment === "color" || slots?.treatment === "grayscale" ? slots.treatment : variant === "logos" ? "grayscale" : "color";
19226
19543
  const sizeValue = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19227
- const isMobile = window.innerWidth <= 480;
19228
- const rowStyle = isMobile ? {
19229
- display: "flex",
19230
- gap: 6,
19231
- flexWrap: "nowrap",
19232
- justifyContent: "center",
19233
- overflowX: "auto",
19234
- WebkitOverflowScrolling: "touch",
19235
- width: "100%",
19236
- scrollbarWidth: "none"
19237
- } : { display: "flex", gap: 6, flexWrap: "nowrap", justifyContent: "center" };
19238
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
19544
+ const mobile = window.innerWidth <= 480;
19545
+ const gatedLabel = (label) => itemEditOn ? label : "Turn on the star to edit this image";
19546
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19239
19547
  "div",
19240
19548
  {
19241
19549
  "data-ohw-bridge": "",
19242
19550
  "data-ohw-toolbar": "",
19243
19551
  onMouseDown: (e) => e.stopPropagation(),
19244
- style: {
19245
- display: "flex",
19246
- flexDirection: "column",
19247
- gap: 6,
19248
- alignItems: "center",
19249
- background: OHW_BLUE,
19250
- padding: 8,
19251
- borderRadius: 12,
19252
- boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
19253
- pointerEvents: pending ? "none" : "auto",
19254
- opacity: pending ? 0.6 : 1,
19255
- maxWidth: isMobile ? "calc(100vw - 24px)" : "none"
19256
- },
19552
+ className: cn("flex flex-col items-center gap-1.5 font-sans", pending && "pointer-events-none opacity-60"),
19553
+ style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
19257
19554
  children: [
19258
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
19259
- VARIANTS.map((v) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: v.label, active: variant === v.value, onClick: () => onSetVariant(v.value) }, v.value)),
19260
- ALIGNS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: a.label, active: align === a.value, onClick: () => onSetAlign(a.value) }, a.value))
19261
- ] }),
19262
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
19263
- HOVERS.map((h) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: h.label, active: hover === h.value, onClick: () => onSetHover(h.value) }, h.value)),
19264
- TREATMENTS.map((t) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: t.label, active: treatment === t.value, onClick: () => onSetTreatment(t.value) }, t.value))
19265
- ] }),
19266
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
19267
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2212", onClick: () => onResize(-8) }),
19268
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { style: { color: "#ffffff", fontSize: 12, minWidth: 34, textAlign: "center" }, children: [
19269
- sizeValue,
19270
- "px"
19271
- ] }),
19272
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "+", onClick: () => onResize(8) })
19273
- ] }),
19274
- /* @__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 }) }),
19275
- /* @__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) }) }),
19276
- currentHref && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
19277
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "New tab", active: currentTarget === "new-tab", onClick: () => onSetLink(currentHref, "new-tab") }),
19278
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "Same tab", active: currentTarget === "same-tab", onClick: () => onSetLink(currentHref, "same-tab") })
19279
- ] }),
19280
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
19281
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "Change Image", onClick: onChangeImage }),
19282
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "+ Item", onClick: onAddItem }),
19283
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2212 Item", onClick: onRemoveItem, disabled: itemCount <= 1 })
19284
- ] }),
19285
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: rowStyle, children: [
19286
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2190 Move", onClick: onMoveLeft, disabled: index <= 0 }),
19287
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Pill, { label: "\u2192 Move", onClick: onMoveRight, disabled: index >= itemCount - 1 })
19288
- ] }),
19289
- error && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
19555
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
19556
+ "div",
19557
+ {
19558
+ className: cn(
19559
+ "flex items-center gap-1 rounded-[10px] border border-border bg-background p-1 whitespace-nowrap",
19560
+ "shadow-[0px_10px_15px_-3px_rgba(0,0,0,0.1),0px_4px_6px_-4px_rgba(0,0,0,0.1)]",
19561
+ mobile ? "flex-wrap justify-center" : "flex-nowrap"
19562
+ ),
19563
+ children: [
19564
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19565
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19566
+ CustomToolbarButton,
19567
+ {
19568
+ type: "button",
19569
+ "aria-label": itemEditOn ? "Done editing this image" : "Edit this image",
19570
+ "aria-pressed": itemEditOn,
19571
+ onClick: onToggleItemEdit,
19572
+ className: "group size-8",
19573
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19574
+ import_lucide_react9.Star,
19575
+ {
19576
+ size: 16,
19577
+ fill: "currentColor",
19578
+ strokeWidth: 0,
19579
+ className: cn(
19580
+ "transition-colors",
19581
+ itemEditOn ? "text-[#f59e0b] group-hover:text-[#d97706]" : "text-[#d6d3d1] group-hover:text-[#fbbf24]"
19582
+ ),
19583
+ style: { pointerEvents: "none" }
19584
+ }
19585
+ )
19586
+ }
19587
+ ) }),
19588
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: itemEditOn ? "Done editing this image" : "Edit this image" })
19589
+ ] }),
19590
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19591
+ /* @__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" } }) }),
19592
+ /* @__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" } }) }),
19593
+ /* @__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" } }) }),
19594
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19595
+ ToolbarAction,
19596
+ {
19597
+ label: gatedLabel("Move right"),
19598
+ onClick: onMoveRight,
19599
+ disabled: !itemEditOn || index >= itemCount - 1,
19600
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ArrowRight, { size: 16, style: { pointerEvents: "none" } })
19601
+ }
19602
+ ),
19603
+ /* @__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" } }) }),
19604
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19605
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(OptionMenu, { label: "Layout", value: variant, options: VARIANTS, onChange: onSetVariant }),
19606
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(OptionMenu, { label: "Hover", value: hover, options: HOVERS, onChange: onSetHover }),
19607
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19608
+ ToolbarAction,
19609
+ {
19610
+ label: treatment === "grayscale" ? "Show in color" : "Show grayscale",
19611
+ active: treatment === "grayscale",
19612
+ onClick: () => onSetTreatment(treatment === "grayscale" ? "color" : "grayscale"),
19613
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Palette, { size: 16, style: { pointerEvents: "none" } })
19614
+ }
19615
+ ),
19616
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19617
+ 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)),
19618
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19619
+ /* @__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" } }) }),
19620
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "min-w-[40px] text-center text-xs tabular-nums text-muted-foreground", children: [
19621
+ sizeValue,
19622
+ "px"
19623
+ ] }),
19624
+ /* @__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" } }) }),
19625
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19626
+ /* @__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" } }) })
19627
+ ]
19628
+ }
19629
+ ),
19630
+ 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 })
19290
19631
  ]
19291
19632
  }
19292
- );
19633
+ ) });
19293
19634
  }
19294
19635
  function findMediaStripSlots(tree) {
19295
19636
  const stack = tree.rows.flatMap((row) => row.blocks);
@@ -19308,31 +19649,23 @@ function AiMediaStripOverlay({
19308
19649
  }) {
19309
19650
  const [hover, setHover] = (0, import_react10.useState)(null);
19310
19651
  const [active, setActive] = (0, import_react10.useState)(null);
19652
+ const [itemEditOn, setItemEditOn] = (0, import_react10.useState)(false);
19653
+ const [linkOpen, setLinkOpen] = (0, import_react10.useState)(false);
19311
19654
  const [pending, setPending] = (0, import_react10.useState)(false);
19312
19655
  const [error, setError] = (0, import_react10.useState)(null);
19313
19656
  const activeRef = (0, import_react10.useRef)(null);
19314
19657
  activeRef.current = active;
19658
+ const linkOpenRef = (0, import_react10.useRef)(false);
19659
+ linkOpenRef.current = linkOpen;
19315
19660
  (0, import_react10.useEffect)(() => {
19316
19661
  const onMove = (e) => {
19662
+ const ref = itemRefFromTarget(e.target);
19317
19663
  const t = e.target;
19318
- if (!(t instanceof HTMLElement)) return;
19319
- 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]")) {
19320
- return;
19321
- }
19322
- const itemEl = t.closest("[data-ohw-media-strip-item]");
19323
- if (!itemEl) {
19324
- setHover(null);
19325
- return;
19326
- }
19327
- const sectionEl = itemEl.closest("[data-ohw-section]");
19328
- const unit = sectionEl ? movableUnit(sectionEl) : null;
19329
- const sectionId = unit ? instanceIdOf(unit) || null : null;
19330
- const index = Number(itemEl.dataset.ohwMediaStripIndex);
19331
- if (!sectionId || Number.isNaN(index)) {
19332
- setHover(null);
19333
- return;
19334
- }
19335
- setHover({ sectionId, index });
19664
+ const overEditable = Boolean(t?.closest?.("[data-ohw-editable]"));
19665
+ setHover((prev) => {
19666
+ const next = ref && !overEditable ? ref : null;
19667
+ return sameRef(prev, next) ? prev : next;
19668
+ });
19336
19669
  };
19337
19670
  const onLeave = () => setHover(null);
19338
19671
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -19344,23 +19677,25 @@ function AiMediaStripOverlay({
19344
19677
  }, []);
19345
19678
  (0, import_react10.useEffect)(() => {
19346
19679
  const onClick = (e) => {
19347
- const current = activeRef.current;
19348
- if (!current) return;
19349
19680
  const t = e.target;
19350
- if (!(t instanceof HTMLElement)) return;
19351
- if (t.closest("[data-ohw-item-interaction]")) return;
19352
- const itemEl = findMediaStripItemElement(current.sectionId, current.index);
19353
- if (itemEl && itemEl.contains(t)) return;
19354
- setActive(null);
19681
+ if (!(t instanceof Element)) return;
19682
+ if (t.closest(CHROME_SELECTOR)) return;
19683
+ const ref = itemRefFromTarget(t);
19684
+ if (sameRef(activeRef.current, ref)) return;
19685
+ if (ref) clearInteractionChromeRef.current();
19686
+ setItemEditOn(false);
19355
19687
  setError(null);
19688
+ setActive(ref);
19356
19689
  };
19357
19690
  document.addEventListener("click", onClick, true);
19358
19691
  return () => document.removeEventListener("click", onClick, true);
19359
- }, []);
19692
+ }, [clearInteractionChromeRef]);
19360
19693
  (0, import_react10.useEffect)(() => {
19361
19694
  const onBlur = () => {
19362
19695
  if (!activeRef.current) return;
19363
19696
  setActive(null);
19697
+ setItemEditOn(false);
19698
+ setLinkOpen(false);
19364
19699
  setError(null);
19365
19700
  };
19366
19701
  window.addEventListener("blur", onBlur);
@@ -19370,7 +19705,10 @@ function AiMediaStripOverlay({
19370
19705
  const onKeyDown = (e) => {
19371
19706
  if (e.key !== "Escape") return;
19372
19707
  if (!activeRef.current) return;
19708
+ if (linkOpenRef.current) return;
19709
+ if (document.querySelector('[data-ohw-more-menu][data-state="open"]')) return;
19373
19710
  setActive(null);
19711
+ setItemEditOn(false);
19374
19712
  setError(null);
19375
19713
  };
19376
19714
  document.addEventListener("keydown", onKeyDown);
@@ -19404,137 +19742,133 @@ function AiMediaStripOverlay({
19404
19742
  tree: nextTree,
19405
19743
  label: entry.label,
19406
19744
  media: extraMedia ? { ...entry.media, ...extraMedia } : entry.media,
19407
- ...entry.brand ? { brand: entry.brand } : {}
19745
+ ...entry.brand ? { brand: entry.brand } : {},
19746
+ // The user is already looking at the strip they just edited — recentering the viewport
19747
+ // on every edit reads as the section jumping around.
19748
+ skipScrollIntoView: true
19408
19749
  });
19409
19750
  return true;
19410
19751
  },
19411
19752
  [findEntry, postToParent2, applyTreeLocalRef]
19412
19753
  );
19413
19754
  const activeEntry = active ? findEntry(active.sectionId) : null;
19414
- const hoverHandleRect = useLiveItemRect(!active && hover ? hover : null);
19415
19755
  const activeItemRect = useLiveItemRect(active);
19416
- const TOOLBAR_APPROX_H = 170;
19417
- const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
19418
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
19419
- hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19420
- "button",
19421
- {
19422
- type: "button",
19423
- "data-ohw-toolbar": "",
19424
- onClick: () => {
19425
- clearInteractionChromeRef.current();
19426
- setActive(hover);
19427
- },
19428
- "aria-label": "Configure media item",
19429
- style: {
19430
- position: "fixed",
19431
- top: hoverHandleRect.top + 8,
19432
- left: hoverHandleRect.left + hoverHandleRect.width - 36,
19433
- width: 28,
19434
- height: 28,
19435
- borderRadius: 8,
19436
- border: `1.5px dashed ${PRIMARY3}`,
19437
- background: "#ffffff",
19438
- color: PRIMARY3,
19439
- display: "flex",
19440
- alignItems: "center",
19441
- justifyContent: "center",
19442
- cursor: "pointer",
19443
- zIndex: 2147483643,
19444
- fontSize: 14,
19445
- fontWeight: 700
19446
- },
19447
- children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react8.Star, { size: 14, style: { pointerEvents: "none" } })
19448
- }
19449
- ),
19450
- activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19451
- ItemInteractionLayer,
19452
- {
19453
- rect: activeItemRect,
19454
- state: toolbarState,
19455
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
19456
- MediaStripToolbar,
19457
- {
19458
- entry: activeEntry,
19459
- index: active.index,
19460
- pending,
19461
- error,
19462
- onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
19463
- onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
19464
- onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
19465
- onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
19466
- onResize: (delta) => {
19467
- const slots = findMediaStripSlots(activeEntry.tree);
19468
- const variant = slots?.variant === "reel" ? "reel" : "logos";
19469
- const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19470
- void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
19471
- },
19472
- onSetAlt: (alt) => {
19473
- void runMutation(active.sectionId, (tree) => setMediaStripItemAlt(tree, active.index, alt));
19474
- },
19475
- onSetLink: (href, linkTarget) => {
19756
+ const activeStripRect = useLiveItemRect(active, true);
19757
+ const hoverRect = useLiveItemRect(hover && !sameRef(hover, active) ? hover : null);
19758
+ const activeItems = activeEntry ? mediaStripItems(activeEntry.tree) : [];
19759
+ const activeItem = active ? activeItems[active.index] : void 0;
19760
+ const activeHref = activeItem?.href ?? "";
19761
+ const activeTarget = activeItem?.linkTarget === "same-tab" ? "same-tab" : "new-tab";
19762
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19763
+ hoverRect && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19764
+ activeItemRect && activeStripRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19765
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: activeItemRect, state: "active-top" }),
19766
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StripToolbarSlot, { stripRect: activeStripRect, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19767
+ MediaStripToolbar,
19768
+ {
19769
+ entry: activeEntry,
19770
+ index: active.index,
19771
+ pending,
19772
+ error,
19773
+ itemEditOn,
19774
+ onToggleItemEdit: () => setItemEditOn((on) => !on),
19775
+ onSetVariant: (variant) => runMutation(active.sectionId, (tree) => setMediaStripVariant(tree, variant)),
19776
+ onSetAlign: (align) => runMutation(active.sectionId, (tree) => setMediaStripAlign(tree, align)),
19777
+ onSetHover: (hoverPreset) => runMutation(active.sectionId, (tree) => setMediaStripHover(tree, hoverPreset)),
19778
+ onSetTreatment: (treatment) => runMutation(active.sectionId, (tree) => setMediaStripTreatment(tree, treatment)),
19779
+ onResize: (delta) => {
19780
+ const slots = findMediaStripSlots(activeEntry.tree);
19781
+ const variant = slots?.variant === "reel" ? "reel" : "logos";
19782
+ const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
19783
+ void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
19784
+ },
19785
+ onOpenLink: () => setLinkOpen(true),
19786
+ onAddItem: () => {
19787
+ void requestAssetPick(postToParent2).then((picked) => {
19788
+ if (!picked) return;
19789
+ const item = { assetId: picked.assetId, alt: "Logo" };
19476
19790
  void runMutation(
19477
19791
  active.sectionId,
19478
- (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
19479
- );
19480
- },
19481
- onAddItem: () => {
19482
- void requestAssetPick(postToParent2).then((picked) => {
19483
- if (!picked) return;
19484
- const item = { assetId: picked.assetId, alt: "Logo" };
19485
- void runMutation(
19486
- active.sectionId,
19487
- (tree) => addMediaStripItem(tree, item, active.index),
19488
- { [picked.assetId]: picked.url }
19489
- ).then((applied) => {
19490
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19491
- });
19792
+ (tree) => addMediaStripItem(tree, item, active.index),
19793
+ { [picked.assetId]: picked.url }
19794
+ ).then((applied) => {
19795
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19492
19796
  });
19493
- },
19494
- onChangeImage: () => {
19495
- void requestAssetPick(postToParent2).then((picked) => {
19496
- if (!picked) return;
19497
- void runMutation(
19498
- active.sectionId,
19499
- (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
19500
- { [picked.assetId]: picked.url }
19501
- );
19502
- });
19503
- },
19504
- onRemoveItem: () => {
19505
- void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
19506
- (applied) => {
19507
- if (applied) setActive(null);
19508
- }
19509
- );
19510
- },
19511
- onMoveLeft: () => {
19512
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
19513
- (applied) => {
19514
- if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
19515
- }
19516
- );
19517
- },
19518
- onMoveRight: () => {
19519
- void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
19520
- (applied) => {
19521
- if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19522
- }
19797
+ });
19798
+ },
19799
+ onChangeImage: () => {
19800
+ void requestAssetPick(postToParent2).then((picked) => {
19801
+ if (!picked) return;
19802
+ void runMutation(
19803
+ active.sectionId,
19804
+ (tree) => setMediaStripItemAsset(tree, active.index, picked.assetId),
19805
+ { [picked.assetId]: picked.url }
19523
19806
  );
19524
- }
19807
+ });
19808
+ },
19809
+ onRemoveItem: () => {
19810
+ void runMutation(active.sectionId, (tree) => removeMediaStripItem(tree, active.index)).then(
19811
+ (applied) => {
19812
+ if (applied) setActive(null);
19813
+ }
19814
+ );
19815
+ },
19816
+ onMoveLeft: () => {
19817
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, -1)).then(
19818
+ (applied) => {
19819
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index - 1 });
19820
+ }
19821
+ );
19822
+ },
19823
+ onMoveRight: () => {
19824
+ void runMutation(active.sectionId, (tree) => reorderMediaStripItem(tree, active.index, 1)).then(
19825
+ (applied) => {
19826
+ if (applied) setActive({ sectionId: active.sectionId, index: active.index + 1 });
19827
+ }
19828
+ );
19525
19829
  }
19526
- )
19527
- }
19528
- )
19830
+ }
19831
+ ) }),
19832
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19833
+ "div",
19834
+ {
19835
+ "data-ohw-bridge": "",
19836
+ "data-ohw-item-interaction": "",
19837
+ "aria-hidden": true,
19838
+ className: "pointer-events-none fixed rounded-[4px] px-[7px] py-[3px] font-sans text-[11px] font-medium leading-none text-primary-foreground whitespace-nowrap",
19839
+ style: {
19840
+ top: activeItemRect.top + activeItemRect.height + 10,
19841
+ left: activeItemRect.left - 5,
19842
+ background: PRIMARY3,
19843
+ zIndex: 2147483644
19844
+ },
19845
+ children: "Media item"
19846
+ }
19847
+ ),
19848
+ linkOpen && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19849
+ LinkModal,
19850
+ {
19851
+ href: activeHref,
19852
+ linkTarget: activeTarget,
19853
+ onClose: () => setLinkOpen(false),
19854
+ onSave: (href, linkTarget) => {
19855
+ void runMutation(
19856
+ active.sectionId,
19857
+ (tree) => setMediaStripItemLink(tree, active.index, href ? { href, linkTarget } : {})
19858
+ );
19859
+ }
19860
+ }
19861
+ )
19862
+ ] })
19529
19863
  ] });
19530
19864
  }
19531
19865
 
19532
19866
  // src/ui/ai-section/AiPricingCardOverlay.tsx
19533
19867
  var import_react11 = require("react");
19534
- var import_lucide_react9 = require("lucide-react");
19535
- var import_jsx_runtime21 = require("react/jsx-runtime");
19868
+ var import_lucide_react10 = require("lucide-react");
19869
+ var import_jsx_runtime22 = require("react/jsx-runtime");
19536
19870
  var PRIMARY4 = "var(--ohw-primary, #0885FE)";
19537
- 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]";
19871
+ 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]";
19538
19872
  function findPricingCardElement(sectionId, index) {
19539
19873
  const sectionEl = findByInstanceId(sectionId);
19540
19874
  if (!sectionEl) return null;
@@ -19546,7 +19880,7 @@ function findPricingCardElement(sectionId, index) {
19546
19880
  }
19547
19881
  function cardRefFromTarget(target) {
19548
19882
  if (!(target instanceof Element)) return null;
19549
- if (target.closest(CHROME_SELECTOR)) return null;
19883
+ if (target.closest(CHROME_SELECTOR2)) return null;
19550
19884
  const cardEl = target.closest("[data-ohw-pricing-card]");
19551
19885
  if (!cardEl) return null;
19552
19886
  const sectionEl = cardEl.closest("[data-ohw-section]");
@@ -19556,21 +19890,21 @@ function cardRefFromTarget(target) {
19556
19890
  if (!sectionId || Number.isNaN(index)) return null;
19557
19891
  return { sectionId, index };
19558
19892
  }
19559
- function sameRef(a, b) {
19893
+ function sameRef2(a, b) {
19560
19894
  return a?.sectionId === b?.sectionId && a?.index === b?.index;
19561
19895
  }
19562
- function rectOf(el) {
19896
+ function rectOf2(el) {
19563
19897
  if (!el) return null;
19564
19898
  const r2 = el.getBoundingClientRect();
19565
19899
  if (r2.width <= 0 || r2.height <= 0) return null;
19566
19900
  return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
19567
19901
  }
19568
19902
  function readRect3(ref) {
19569
- return rectOf(ref && findPricingCardElement(ref.sectionId, ref.index));
19903
+ return rectOf2(ref && findPricingCardElement(ref.sectionId, ref.index));
19570
19904
  }
19571
19905
  function readGridRect(ref) {
19572
19906
  const card = ref && findPricingCardElement(ref.sectionId, ref.index);
19573
- return rectOf(card?.closest("[data-ohw-pricing-collection]") ?? card);
19907
+ return rectOf2(card?.closest("[data-ohw-pricing-collection]") ?? card);
19574
19908
  }
19575
19909
  function useLiveCardRect(ref, grid = false) {
19576
19910
  const [rect, setRect] = (0, import_react11.useState)(null);
@@ -19600,7 +19934,7 @@ function useLiveCardRect(ref, grid = false) {
19600
19934
  }
19601
19935
  var TOOLBAR_GRID_GAP = 52;
19602
19936
  var TOOLBAR_GRID_GAP_MOBILE = 42;
19603
- var TOOLBAR_EDGE_MARGIN2 = 4;
19937
+ var TOOLBAR_EDGE_MARGIN3 = 4;
19604
19938
  function GridToolbarSlot({ gridRect, children }) {
19605
19939
  const slotRef = (0, import_react11.useRef)(null);
19606
19940
  const [shift, setShift] = (0, import_react11.useState)(0);
@@ -19612,7 +19946,7 @@ function GridToolbarSlot({ gridRect, children }) {
19612
19946
  const w = slot.offsetWidth;
19613
19947
  if (w <= 0) return;
19614
19948
  const half = w / 2;
19615
- const clamped = Math.max(TOOLBAR_EDGE_MARGIN2 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half));
19949
+ const clamped = Math.max(TOOLBAR_EDGE_MARGIN3 + half, Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half));
19616
19950
  setShift((prev) => Math.abs(prev - (clamped - centerX)) < 0.5 ? prev : clamped - centerX);
19617
19951
  };
19618
19952
  clamp();
@@ -19621,7 +19955,7 @@ function GridToolbarSlot({ gridRect, children }) {
19621
19955
  return () => ro.disconnect();
19622
19956
  }, [centerX, children]);
19623
19957
  const mobile = window.innerWidth <= 480;
19624
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19958
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19625
19959
  "div",
19626
19960
  {
19627
19961
  ref: slotRef,
@@ -19656,7 +19990,7 @@ function requestValidateTree2(postToParent2, tree) {
19656
19990
  });
19657
19991
  }
19658
19992
  function StyleSwatch({ style }) {
19659
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19993
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19660
19994
  "span",
19661
19995
  {
19662
19996
  "aria-hidden": true,
@@ -19670,35 +20004,35 @@ function StyleSwatch({ style }) {
19670
20004
  );
19671
20005
  }
19672
20006
  function ButtonGlyph() {
19673
- 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: [
19674
- /* @__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" }),
19675
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
20007
+ 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: [
20008
+ /* @__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" }),
20009
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M5.5 6H12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
19676
20010
  ] });
19677
20011
  }
19678
20012
  function FeatureGlyph({ mode }) {
19679
- 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: [
19680
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
19681
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
19682
- /* @__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" }),
19683
- /* @__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" }),
19684
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
19685
- mode === "add" && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
20013
+ 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: [
20014
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("circle", { cx: "8", cy: "8", r: "7.25", stroke: "currentColor", strokeWidth: "1.5" }),
20015
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M5 8.2L7.2 10.3L11 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
20016
+ /* @__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" }),
20017
+ /* @__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" }),
20018
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M11.5 13.5H15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" }),
20019
+ mode === "add" && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("path", { d: "M13.5 11.5V15.5", stroke: PRIMARY4, strokeWidth: "1.5", strokeLinecap: "round" })
19686
20020
  ] });
19687
20021
  }
19688
- function ToolbarAction({
20022
+ function ToolbarAction2({
19689
20023
  label,
19690
20024
  onClick,
19691
20025
  disabled = false,
19692
20026
  children
19693
20027
  }) {
19694
- const button = /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
19695
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19696
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "inline-flex", children: button }) : button }),
19697
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
20028
+ const button = /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, onClick, className: "size-8", children });
20029
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Tooltip, { children: [
20030
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "inline-flex", children: button }) : button }),
20031
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: label })
19698
20032
  ] });
19699
20033
  }
19700
- function ToolbarSeparator() {
19701
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
20034
+ function ToolbarSeparator2() {
20035
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { "aria-hidden": true, className: "mx-0.5 block h-6 w-px shrink-0 bg-border" });
19702
20036
  }
19703
20037
  function CardStyleMenu({
19704
20038
  value,
@@ -19706,7 +20040,7 @@ function CardStyleMenu({
19706
20040
  disabled = false
19707
20041
  }) {
19708
20042
  const current = PRICING_CARD_STYLES.find((s) => s.value === value) ?? PRICING_CARD_STYLES[0];
19709
- const trigger = /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
20043
+ const trigger = /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19710
20044
  "button",
19711
20045
  {
19712
20046
  type: "button",
@@ -19717,28 +20051,28 @@ function CardStyleMenu({
19717
20051
  disabled && "cursor-not-allowed opacity-40 hover:bg-transparent"
19718
20052
  ),
19719
20053
  children: [
19720
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StyleSwatch, { style: current.value }),
19721
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "flex-1 text-left", children: current.label }),
19722
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.ChevronDown, { size: 16, style: { pointerEvents: "none" } })
20054
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(StyleSwatch, { style: current.value }),
20055
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "flex-1 text-left", children: current.label }),
20056
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.ChevronDown, { size: 16, style: { pointerEvents: "none" } })
19723
20057
  ]
19724
20058
  }
19725
20059
  );
19726
20060
  if (disabled) {
19727
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19728
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
19729
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19730
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "inline-flex", children: trigger }) }),
19731
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
20061
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
20062
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-xs text-muted-foreground opacity-50", children: "Card style" }),
20063
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Tooltip, { children: [
20064
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "inline-flex", children: trigger }) }),
20065
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: "Mark as featured to edit card style" })
19732
20066
  ] })
19733
20067
  ] });
19734
20068
  }
19735
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
19736
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
19737
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DropdownMenu, { modal: false, children: [
19738
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DropdownMenuTrigger, { asChild: true, children: trigger }),
19739
- /* @__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) => {
20069
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-1.5 pl-1.5", children: [
20070
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-xs text-muted-foreground", children: "Card style" }),
20071
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(DropdownMenu, { modal: false, children: [
20072
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(DropdownMenuTrigger, { asChild: true, children: trigger }),
20073
+ /* @__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) => {
19740
20074
  const selected = style.value === value;
19741
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
20075
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19742
20076
  DropdownMenuItem,
19743
20077
  {
19744
20078
  role: "menuitemradio",
@@ -19746,9 +20080,9 @@ function CardStyleMenu({
19746
20080
  onSelect: () => onChange(style.value),
19747
20081
  className: cn("grid grid-cols-[18px_1fr_16px] gap-2 py-2 pl-2 pr-2 text-[13px]", selected && "bg-muted"),
19748
20082
  children: [
19749
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StyleSwatch, { style: style.value }),
19750
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: style.label }),
19751
- selected && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react9.Check, { size: 16, className: "text-bridge-primary" })
20083
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(StyleSwatch, { style: style.value }),
20084
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { children: style.label }),
20085
+ selected && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Check, { size: 16, className: "text-bridge-primary" })
19752
20086
  ]
19753
20087
  },
19754
20088
  style.value
@@ -19778,7 +20112,7 @@ function PricingCardToolbar({
19778
20112
  const style = pricingCardStyle(entry.tree, index);
19779
20113
  const featuredTag = pricingCardFeaturedTag(entry.tree, index);
19780
20114
  const mobile = window.innerWidth <= 480;
19781
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
20115
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19782
20116
  "div",
19783
20117
  {
19784
20118
  "data-ohw-bridge": "",
@@ -19790,7 +20124,7 @@ function PricingCardToolbar({
19790
20124
  ),
19791
20125
  style: { maxWidth: mobile ? "calc(100vw - 24px)" : void 0 },
19792
20126
  children: [
19793
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
20127
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
19794
20128
  "div",
19795
20129
  {
19796
20130
  className: cn(
@@ -19799,8 +20133,8 @@ function PricingCardToolbar({
19799
20133
  mobile ? "flex-wrap justify-center" : "flex-nowrap"
19800
20134
  ),
19801
20135
  children: [
19802
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Tooltip, { children: [
19803
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
20136
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Tooltip, { children: [
20137
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19804
20138
  CustomToolbarButton,
19805
20139
  {
19806
20140
  type: "button",
@@ -19808,8 +20142,8 @@ function PricingCardToolbar({
19808
20142
  "aria-pressed": featuredTag,
19809
20143
  onClick: () => onSetFeaturedTag(!featuredTag),
19810
20144
  className: "group size-8",
19811
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
19812
- import_lucide_react9.Star,
20145
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20146
+ import_lucide_react10.Star,
19813
20147
  {
19814
20148
  size: 16,
19815
20149
  fill: "currentColor",
@@ -19823,25 +20157,25 @@ function PricingCardToolbar({
19823
20157
  )
19824
20158
  }
19825
20159
  ) }),
19826
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
20160
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TooltipContent, { side: "bottom", sideOffset: 8, children: featuredTag ? "Unmark featured" : "Mark as featured" })
19827
20161
  ] }),
19828
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19829
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
19830
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19831
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarAction, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ButtonGlyph, {}) }),
19832
- /* @__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: [
19833
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ButtonGlyph, {}),
19834
- /* @__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" })
20162
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarSeparator2, {}),
20163
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(CardStyleMenu, { value: style, onChange: onSetStyle, disabled: !featuredTag }),
20164
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarSeparator2, {}),
20165
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarAction2, { label: "Add button", onClick: onAddButton, disabled: buttonCount >= 2, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ButtonGlyph, {}) }),
20166
+ /* @__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: [
20167
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ButtonGlyph, {}),
20168
+ /* @__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" })
19835
20169
  ] }) }),
19836
- /* @__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" }) }),
19837
- /* @__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" }) }),
19838
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToolbarSeparator, {}),
19839
- /* @__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" } }) }),
19840
- /* @__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" } }) })
20170
+ /* @__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" }) }),
20171
+ /* @__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" }) }),
20172
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolbarSeparator2, {}),
20173
+ /* @__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" } }) }),
20174
+ /* @__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" } }) })
19841
20175
  ]
19842
20176
  }
19843
20177
  ),
19844
- 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 })
20178
+ 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 })
19845
20179
  ]
19846
20180
  }
19847
20181
  ) });
@@ -19875,7 +20209,7 @@ function AiPricingCardOverlay({
19875
20209
  const overEditable = Boolean(t?.closest?.("[data-ohw-editable], [data-ohw-role], a, button"));
19876
20210
  setHover((prev) => {
19877
20211
  const next = ref && !overEditable ? ref : null;
19878
- return sameRef(prev, next) ? prev : next;
20212
+ return sameRef2(prev, next) ? prev : next;
19879
20213
  });
19880
20214
  };
19881
20215
  const onLeave = () => setHover(null);
@@ -19890,10 +20224,10 @@ function AiPricingCardOverlay({
19890
20224
  const onClick = (e) => {
19891
20225
  const t = e.target;
19892
20226
  if (!(t instanceof Element)) return;
19893
- if (t.closest(CHROME_SELECTOR)) return;
20227
+ if (t.closest(CHROME_SELECTOR2)) return;
19894
20228
  const ref = cardRefFromTarget(t);
19895
- setActive((prev) => sameRef(prev, ref) ? prev : ref);
19896
- if (!ref || !sameRef(activeRef.current, ref)) setError(null);
20229
+ setActive((prev) => sameRef2(prev, ref) ? prev : ref);
20230
+ if (!ref || !sameRef2(activeRef.current, ref)) setError(null);
19897
20231
  };
19898
20232
  document.addEventListener("click", onClick, true);
19899
20233
  return () => document.removeEventListener("click", onClick, true);
@@ -19958,12 +20292,12 @@ function AiPricingCardOverlay({
19958
20292
  const activeEntry = active ? findEntry(active.sectionId) : null;
19959
20293
  const activeCardRect = useLiveCardRect(active);
19960
20294
  const activeGridRect = useLiveCardRect(active, true);
19961
- const hoverRect = useLiveCardRect(hover && !sameRef(hover, active) ? hover : null);
19962
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19963
- hoverRect && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
19964
- activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
19965
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
19966
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
20295
+ const hoverRect = useLiveCardRect(hover && !sameRef2(hover, active) ? hover : null);
20296
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
20297
+ hoverRect && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ItemInteractionLayer, { rect: hoverRect, state: "hover" }),
20298
+ activeCardRect && activeGridRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
20299
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ItemInteractionLayer, { rect: activeCardRect, state: "active-top" }),
20300
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(GridToolbarSlot, { gridRect: activeGridRect, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19967
20301
  PricingCardToolbar,
19968
20302
  {
19969
20303
  entry: activeEntry,
@@ -19991,7 +20325,7 @@ function AiPricingCardOverlay({
19991
20325
  onRemoveFeature: () => runMutation(active.sectionId, (tree) => removePricingCardFeature(tree, active.index))
19992
20326
  }
19993
20327
  ) }),
19994
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
20328
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
19995
20329
  "div",
19996
20330
  {
19997
20331
  "data-ohw-bridge": "",
@@ -20013,7 +20347,7 @@ function AiPricingCardOverlay({
20013
20347
 
20014
20348
  // src/ui/ai-section/AiVideoOverlay.tsx
20015
20349
  var import_react12 = require("react");
20016
- var import_lucide_react10 = require("lucide-react");
20350
+ var import_lucide_react11 = require("lucide-react");
20017
20351
 
20018
20352
  // src/lib/ai-video-tree.ts
20019
20353
  function resolveVideoBlock(tree, path) {
@@ -20046,48 +20380,20 @@ function setVideoId(tree, path, videoId) {
20046
20380
  return true;
20047
20381
  });
20048
20382
  }
20049
- function clearVideo(tree, path) {
20050
- return withVideo(tree, path, (block) => {
20051
- const slots = { ...block.slots };
20052
- delete slots.videoId;
20053
- block.slots = slots;
20054
- return true;
20055
- });
20056
- }
20057
-
20058
- // src/lib/youtube.ts
20059
- var YOUTUBE_ID_PATTERN = /^[A-Za-z0-9_-]{11}$/;
20060
- function extractYouTubeId(input) {
20061
- const trimmed = input.trim();
20062
- if (!trimmed) return null;
20063
- if (YOUTUBE_ID_PATTERN.test(trimmed)) return trimmed;
20064
- let url;
20065
- try {
20066
- url = new URL(trimmed);
20067
- } catch {
20068
- return null;
20069
- }
20070
- const host = url.hostname.replace(/^www\./, "");
20071
- if (host !== "youtube.com" && host !== "m.youtube.com" && host !== "youtu.be" && host !== "youtube-nocookie.com") {
20072
- return null;
20073
- }
20074
- let candidate = null;
20075
- if (host === "youtu.be") {
20076
- candidate = url.pathname.slice(1).split("/")[0] ?? null;
20077
- } else if (url.pathname === "/watch") {
20078
- candidate = url.searchParams.get("v");
20079
- } else {
20080
- const match = url.pathname.match(/^\/(?:embed|shorts)\/([^/]+)/);
20081
- candidate = match ? match[1] : null;
20082
- }
20083
- return candidate && YOUTUBE_ID_PATTERN.test(candidate) ? candidate : null;
20383
+ function clearVideo(tree, path) {
20384
+ return withVideo(tree, path, (block) => {
20385
+ const slots = { ...block.slots };
20386
+ delete slots.videoId;
20387
+ block.slots = slots;
20388
+ return true;
20389
+ });
20084
20390
  }
20085
20391
 
20086
20392
  // src/ui/ai-section/AiVideoOverlay.tsx
20087
- var import_jsx_runtime22 = require("react/jsx-runtime");
20393
+ var import_jsx_runtime23 = require("react/jsx-runtime");
20088
20394
  var PRIMARY5 = "#0885FE";
20089
- var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY5})`;
20090
- var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
20395
+ var OHW_BLUE = `var(--ohw-primary, ${PRIMARY5})`;
20396
+ var OHW_BLUE_DARK = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY5}) 60%, #000000)`;
20091
20397
  function findVideoBlockElement(sectionId, path) {
20092
20398
  const sectionEl = findByInstanceId(sectionId);
20093
20399
  if (!sectionEl) return null;
@@ -20147,12 +20453,12 @@ function requestValidateTree3(postToParent2, tree) {
20147
20453
  postToParent2({ type: "ow:ai-validate-tree-request", id, tree });
20148
20454
  });
20149
20455
  }
20150
- function Pill2({
20456
+ function Pill({
20151
20457
  label,
20152
20458
  onClick,
20153
20459
  disabled
20154
20460
  }) {
20155
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20461
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20156
20462
  "button",
20157
20463
  {
20158
20464
  type: "button",
@@ -20168,7 +20474,7 @@ function Pill2({
20168
20474
  border: "none",
20169
20475
  cursor: disabled ? "default" : "pointer",
20170
20476
  background: "#ffffff",
20171
- color: OHW_BLUE_DARK2,
20477
+ color: OHW_BLUE_DARK,
20172
20478
  opacity: disabled ? 0.4 : 1,
20173
20479
  fontSize: 12,
20174
20480
  lineHeight: "16px",
@@ -20190,7 +20496,7 @@ function VideoUrlInput({ value, onCommit }) {
20190
20496
  setDraft(value ? watchUrl(value) : "");
20191
20497
  setInvalid(false);
20192
20498
  }, [value]);
20193
- const commit = () => {
20499
+ const commit2 = () => {
20194
20500
  const next = draft.trim();
20195
20501
  if (!next) {
20196
20502
  setInvalid(false);
@@ -20211,8 +20517,8 @@ function VideoUrlInput({ value, onCommit }) {
20211
20517
  onCommit(id);
20212
20518
  }
20213
20519
  };
20214
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
20215
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
20520
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: "100%", maxWidth: 280 }, children: [
20521
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20216
20522
  "div",
20217
20523
  {
20218
20524
  style: {
@@ -20228,12 +20534,12 @@ function VideoUrlInput({ value, onCommit }) {
20228
20534
  boxShadow: invalid ? "inset 0 0 0 1.5px #EF4444" : "none"
20229
20535
  },
20230
20536
  children: [
20231
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20537
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20232
20538
  "span",
20233
20539
  {
20234
20540
  style: {
20235
20541
  flex: "0 0 auto",
20236
- color: OHW_BLUE_DARK2,
20542
+ color: OHW_BLUE_DARK,
20237
20543
  fontSize: 10,
20238
20544
  lineHeight: "16px",
20239
20545
  fontWeight: 700,
@@ -20243,14 +20549,14 @@ function VideoUrlInput({ value, onCommit }) {
20243
20549
  children: "Video"
20244
20550
  }
20245
20551
  ),
20246
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20552
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20247
20553
  "input",
20248
20554
  {
20249
20555
  type: "text",
20250
20556
  inputMode: "url",
20251
20557
  value: draft,
20252
20558
  onChange: (e) => handleChange(e.target.value),
20253
- onBlur: commit,
20559
+ onBlur: commit2,
20254
20560
  onKeyDown: (e) => {
20255
20561
  if (e.key === "Enter") e.currentTarget.blur();
20256
20562
  if (e.key === "Escape") {
@@ -20280,7 +20586,7 @@ function VideoUrlInput({ value, onCommit }) {
20280
20586
  ]
20281
20587
  }
20282
20588
  ),
20283
- 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." })
20589
+ 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." })
20284
20590
  ] });
20285
20591
  }
20286
20592
  function VideoToolbar({
@@ -20290,7 +20596,7 @@ function VideoToolbar({
20290
20596
  onSetVideoId,
20291
20597
  onRemove
20292
20598
  }) {
20293
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
20599
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20294
20600
  "div",
20295
20601
  {
20296
20602
  "data-ohw-bridge": "",
@@ -20302,7 +20608,7 @@ function VideoToolbar({
20302
20608
  gap: 6,
20303
20609
  alignItems: "center",
20304
20610
  width: 280,
20305
- background: OHW_BLUE2,
20611
+ background: OHW_BLUE,
20306
20612
  padding: 8,
20307
20613
  borderRadius: 12,
20308
20614
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -20310,9 +20616,9 @@ function VideoToolbar({
20310
20616
  opacity: pending ? 0.6 : 1
20311
20617
  },
20312
20618
  children: [
20313
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
20314
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Pill2, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
20315
- error && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20619
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(VideoUrlInput, { value: videoId, onCommit: onSetVideoId }),
20620
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Pill, { label: "Remove video", onClick: onRemove, disabled: !videoId }),
20621
+ error && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20316
20622
  ]
20317
20623
  }
20318
20624
  );
@@ -20432,8 +20738,8 @@ function AiVideoOverlay({
20432
20738
  const activeBlockRect = useLiveBlockRect(active);
20433
20739
  const TOOLBAR_APPROX_H = 90;
20434
20740
  const toolbarState = activeBlockRect && activeBlockRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
20435
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
20436
- hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20741
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
20742
+ hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20437
20743
  "button",
20438
20744
  {
20439
20745
  type: "button",
@@ -20461,15 +20767,15 @@ function AiVideoOverlay({
20461
20767
  fontSize: 14,
20462
20768
  fontWeight: 700
20463
20769
  },
20464
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react10.Star, { size: 14, style: { pointerEvents: "none" } })
20770
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.Star, { size: 14, style: { pointerEvents: "none" } })
20465
20771
  }
20466
20772
  ),
20467
- activeBlockRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20773
+ activeBlockRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20468
20774
  ItemInteractionLayer,
20469
20775
  {
20470
20776
  rect: activeBlockRect,
20471
20777
  state: toolbarState,
20472
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
20778
+ toolbar: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20473
20779
  VideoToolbar,
20474
20780
  {
20475
20781
  videoId: videoIdAt(activeEntry.tree, active.path),
@@ -20493,7 +20799,7 @@ function AiVideoOverlay({
20493
20799
 
20494
20800
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
20495
20801
  var import_react13 = require("react");
20496
- var import_lucide_react11 = require("lucide-react");
20802
+ var import_lucide_react12 = require("lucide-react");
20497
20803
 
20498
20804
  // src/lib/ai-carousel-tree.ts
20499
20805
  function isCarouselBlock(node) {
@@ -20544,10 +20850,10 @@ function setCarouselAutoplay(tree, on) {
20544
20850
  }
20545
20851
 
20546
20852
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
20547
- var import_jsx_runtime23 = require("react/jsx-runtime");
20853
+ var import_jsx_runtime24 = require("react/jsx-runtime");
20548
20854
  var PRIMARY6 = "#0885FE";
20549
- var OHW_BLUE3 = `var(--ohw-primary, ${PRIMARY6})`;
20550
- var OHW_BLUE_DARK3 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
20855
+ var OHW_BLUE2 = `var(--ohw-primary, ${PRIMARY6})`;
20856
+ var OHW_BLUE_DARK2 = `color-mix(in srgb, var(--ohw-primary, ${PRIMARY6}) 60%, #000000)`;
20551
20857
  function findCarouselItemElement(sectionId, index) {
20552
20858
  const sectionEl = findByInstanceId(sectionId);
20553
20859
  if (!sectionEl) return null;
@@ -20626,8 +20932,8 @@ function requestAssetPick2(postToParent2) {
20626
20932
  postToParent2({ type: "ow:asset-pick", id });
20627
20933
  });
20628
20934
  }
20629
- function Pill3({ label, onClick, disabled }) {
20630
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20935
+ function Pill2({ label, onClick, disabled }) {
20936
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20631
20937
  "button",
20632
20938
  {
20633
20939
  type: "button",
@@ -20643,7 +20949,7 @@ function Pill3({ label, onClick, disabled }) {
20643
20949
  border: "none",
20644
20950
  cursor: disabled ? "default" : "pointer",
20645
20951
  background: "#ffffff",
20646
- color: OHW_BLUE_DARK3,
20952
+ color: OHW_BLUE_DARK2,
20647
20953
  opacity: disabled ? 0.4 : 1,
20648
20954
  fontSize: 12,
20649
20955
  lineHeight: "16px",
@@ -20661,7 +20967,7 @@ function TogglePill({
20661
20967
  onToggle,
20662
20968
  disabled
20663
20969
  }) {
20664
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
20970
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
20665
20971
  "button",
20666
20972
  {
20667
20973
  type: "button",
@@ -20680,7 +20986,7 @@ function TogglePill({
20680
20986
  border: "none",
20681
20987
  cursor: disabled ? "default" : "pointer",
20682
20988
  background: "#ffffff",
20683
- color: OHW_BLUE_DARK3,
20989
+ color: OHW_BLUE_DARK2,
20684
20990
  opacity: disabled ? 0.4 : 1,
20685
20991
  fontSize: 12,
20686
20992
  lineHeight: "16px",
@@ -20690,7 +20996,7 @@ function TogglePill({
20690
20996
  },
20691
20997
  children: [
20692
20998
  label,
20693
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
20999
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20694
21000
  "span",
20695
21001
  {
20696
21002
  "aria-hidden": true,
@@ -20703,10 +21009,10 @@ function TogglePill({
20703
21009
  // Off is a grey track, not a washed-out blue: the pill's own white background already
20704
21010
  // carries the brand colour in the text, and a pale blue track next to it read as
20705
21011
  // "disabled control" rather than "switch that is off".
20706
- background: on ? OHW_BLUE_DARK3 : "#CBD5E1",
21012
+ background: on ? OHW_BLUE_DARK2 : "#CBD5E1",
20707
21013
  transition: "background 120ms ease"
20708
21014
  },
20709
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
21015
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20710
21016
  "span",
20711
21017
  {
20712
21018
  style: {
@@ -20735,7 +21041,7 @@ function CarouselSlideToolbar({
20735
21041
  onChangeImage,
20736
21042
  onToggleAutoplay
20737
21043
  }) {
20738
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
21044
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
20739
21045
  "div",
20740
21046
  {
20741
21047
  "data-ohw-bridge": "",
@@ -20746,7 +21052,7 @@ function CarouselSlideToolbar({
20746
21052
  flexDirection: "column",
20747
21053
  gap: 6,
20748
21054
  alignItems: "center",
20749
- background: OHW_BLUE3,
21055
+ background: OHW_BLUE2,
20750
21056
  padding: 8,
20751
21057
  borderRadius: 12,
20752
21058
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
@@ -20758,11 +21064,11 @@ function CarouselSlideToolbar({
20758
21064
  pointerEvents: pending ? "none" : "auto"
20759
21065
  },
20760
21066
  children: [
20761
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
20762
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Pill3, { label: "Change Image", onClick: onChangeImage }),
20763
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
21067
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
21068
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(Pill2, { label: "Change Image", onClick: onChangeImage }),
21069
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
20764
21070
  ] }),
20765
- error && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
21071
+ error && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
20766
21072
  ]
20767
21073
  }
20768
21074
  );
@@ -20902,8 +21208,8 @@ function AiFadeCarouselOverlay({
20902
21208
  const activeItemRect = useLiveItemRect2(active);
20903
21209
  const TOOLBAR_APPROX_H = 90;
20904
21210
  const toolbarState = activeItemRect && activeItemRect.top < TOOLBAR_APPROX_H ? "active-bottom" : "active-top";
20905
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
20906
- hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
21211
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
21212
+ hoverHandleRect && hover && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20907
21213
  "button",
20908
21214
  {
20909
21215
  type: "button",
@@ -20932,10 +21238,10 @@ function AiFadeCarouselOverlay({
20932
21238
  fontSize: 14,
20933
21239
  fontWeight: 700
20934
21240
  },
20935
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.Star, { size: 14, style: { pointerEvents: "none" } })
21241
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react12.Star, { size: 14, style: { pointerEvents: "none" } })
20936
21242
  }
20937
21243
  ),
20938
- swapping && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
21244
+ swapping && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20939
21245
  "div",
20940
21246
  {
20941
21247
  ref: skeletonRef,
@@ -20953,15 +21259,15 @@ function AiFadeCarouselOverlay({
20953
21259
  zIndex: 2147483646,
20954
21260
  pointerEvents: "none"
20955
21261
  },
20956
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("style", { children: MEDIA_SKELETON_CSS })
21262
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("style", { children: MEDIA_SKELETON_CSS })
20957
21263
  }
20958
21264
  ),
20959
- activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
21265
+ activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20960
21266
  ItemInteractionLayer,
20961
21267
  {
20962
21268
  rect: activeItemRect,
20963
21269
  state: toolbarState,
20964
- toolbar: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
21270
+ toolbar: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
20965
21271
  CarouselSlideToolbar,
20966
21272
  {
20967
21273
  pending,
@@ -21179,113 +21485,6 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
21179
21485
  // src/ui/link-modal/LinkPopover.tsx
21180
21486
  var import_react17 = require("react");
21181
21487
 
21182
- // src/ui/dialog.tsx
21183
- var React9 = __toESM(require("react"), 1);
21184
- var import_radix_ui6 = require("radix-ui");
21185
- var import_lucide_react12 = require("lucide-react");
21186
- var import_jsx_runtime24 = require("react/jsx-runtime");
21187
- function Dialog2({
21188
- ...props
21189
- }) {
21190
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_radix_ui6.Dialog.Root, { "data-slot": "dialog", ...props });
21191
- }
21192
- function DialogPortal({
21193
- ...props
21194
- }) {
21195
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_radix_ui6.Dialog.Portal, { ...props });
21196
- }
21197
- function DialogOverlay({
21198
- className,
21199
- ...props
21200
- }) {
21201
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21202
- import_radix_ui6.Dialog.Overlay,
21203
- {
21204
- "data-slot": "dialog-overlay",
21205
- "data-ohw-link-modal-root": "",
21206
- className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
21207
- ...props
21208
- }
21209
- );
21210
- }
21211
- var DialogContent = React9.forwardRef(
21212
- ({ className, children, showCloseButton = true, container, ...props }, ref) => {
21213
- const positionMode = container ? "absolute" : "fixed";
21214
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(DialogPortal, { container: container ?? void 0, children: [
21215
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
21216
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
21217
- import_radix_ui6.Dialog.Content,
21218
- {
21219
- ref,
21220
- "data-slot": "dialog-content",
21221
- className: cn(
21222
- positionMode,
21223
- "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",
21224
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
21225
- container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
21226
- className
21227
- ),
21228
- onMouseDown: (e) => e.stopPropagation(),
21229
- ...props,
21230
- children: [
21231
- children,
21232
- showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21233
- import_radix_ui6.Dialog.Close,
21234
- {
21235
- type: "button",
21236
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
21237
- "aria-label": "Close",
21238
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react12.X, { size: 16, "aria-hidden": true })
21239
- }
21240
- ) : null
21241
- ]
21242
- }
21243
- )
21244
- ] });
21245
- }
21246
- );
21247
- DialogContent.displayName = import_radix_ui6.Dialog.Content.displayName;
21248
- function DialogHeader({
21249
- className,
21250
- ...props
21251
- }) {
21252
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
21253
- }
21254
- function DialogFooter({
21255
- className,
21256
- ...props
21257
- }) {
21258
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21259
- "div",
21260
- {
21261
- className: cn("flex items-center justify-end gap-2", className),
21262
- ...props
21263
- }
21264
- );
21265
- }
21266
- var DialogTitle = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21267
- import_radix_ui6.Dialog.Title,
21268
- {
21269
- ref,
21270
- className: cn(
21271
- "text-lg font-semibold leading-none tracking-tight text-card-foreground",
21272
- className
21273
- ),
21274
- ...props
21275
- }
21276
- ));
21277
- DialogTitle.displayName = import_radix_ui6.Dialog.Title.displayName;
21278
- var DialogDescription = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
21279
- import_radix_ui6.Dialog.Description,
21280
- {
21281
- ref,
21282
- className: cn("text-sm text-muted-foreground", className),
21283
- ...props
21284
- }
21285
- ));
21286
- DialogDescription.displayName = import_radix_ui6.Dialog.Description.displayName;
21287
- var DialogClose = import_radix_ui6.Dialog.Close;
21288
-
21289
21488
  // src/ui/link-modal/LinkEditorPanel.tsx
21290
21489
  var import_lucide_react16 = require("lucide-react");
21291
21490
 
@@ -22389,6 +22588,7 @@ function shouldUseDevFixtures() {
22389
22588
  function getLogoElement(el) {
22390
22589
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
22391
22590
  if (marked) return marked;
22591
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
22392
22592
  const root = el.closest("nav, [data-ohw-nav-root], footer");
22393
22593
  if (!root) return null;
22394
22594
  const anchor = el.closest("a");
@@ -24083,6 +24283,102 @@ function deleteSelectedNavFooterItem(deps) {
24083
24283
  return false;
24084
24284
  }
24085
24285
 
24286
+ // src/lib/nav-page-sync.ts
24287
+ function labelKeyFor(hrefKey) {
24288
+ return hrefKey.replace(/-href$/, "-label");
24289
+ }
24290
+ function isPageLinkKey(key) {
24291
+ return isNavbarHrefKey(key) || isFooterHrefKey(key);
24292
+ }
24293
+ function currentHref(key, anchor, content) {
24294
+ const stored = content[key];
24295
+ if (typeof stored === "string" && stored.length > 0) return stored;
24296
+ return anchor.getAttribute("href") ?? "";
24297
+ }
24298
+ function normalizePath2(value) {
24299
+ if (value.length > 1 && value.endsWith("/")) return value.slice(0, -1);
24300
+ return value;
24301
+ }
24302
+ function pointsAt(href, path) {
24303
+ const normalized = normalizePath2(href);
24304
+ const target = normalizePath2(path);
24305
+ if (normalized === target) return true;
24306
+ return (normalized.startsWith(`${target}#`) || normalized.startsWith(`${target}?`)) && target !== "/";
24307
+ }
24308
+ function retargetHref(href, oldPath, newPath) {
24309
+ const normalized = normalizePath2(href);
24310
+ const target = normalizePath2(oldPath);
24311
+ if (normalized === target) return newPath;
24312
+ return `${newPath}${normalized.slice(target.length)}`;
24313
+ }
24314
+ function setLabelText(key, text) {
24315
+ document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`).forEach((el) => {
24316
+ el.textContent = text;
24317
+ });
24318
+ }
24319
+ function commit(delta, deps) {
24320
+ if (Object.keys(delta).length === 0) return;
24321
+ deps.setEditContent({ ...deps.getEditContent(), ...delta });
24322
+ deps.postToParent({
24323
+ type: "ow:change",
24324
+ nodes: Object.entries(delta).map(([key, text]) => ({ key, text }))
24325
+ });
24326
+ }
24327
+ function navLinksTo(path, content) {
24328
+ return Array.from(document.querySelectorAll("[data-ohw-href-key]")).filter((anchor) => {
24329
+ const key = anchor.getAttribute("data-ohw-href-key");
24330
+ return isNavbarHrefKey(key) && pointsAt(currentHref(key, anchor, content), path);
24331
+ }).map((anchor) => anchor.getAttribute("data-ohw-href-key"));
24332
+ }
24333
+ function addNavLinkForPage(page, deps) {
24334
+ const content = deps.getEditContent();
24335
+ if (navLinksTo(page.path, content).length > 0) return false;
24336
+ const roots = listNavbarRootItems();
24337
+ const afterAnchor = roots[roots.length - 1] ?? null;
24338
+ let inserted;
24339
+ try {
24340
+ inserted = insertNavbarItem(page.path, page.title, afterAnchor);
24341
+ } catch {
24342
+ return false;
24343
+ }
24344
+ const { hrefKey, labelKey, index } = inserted;
24345
+ deps.applyLinkByKey(hrefKey, page.path);
24346
+ setLabelText(labelKey, page.title);
24347
+ commit(
24348
+ {
24349
+ [hrefKey]: page.path,
24350
+ [labelKey]: page.title,
24351
+ [NAV_COUNT_KEY]: String(index + 1),
24352
+ [NAV_ORDER_KEY]: getNavbarOrderJson()
24353
+ },
24354
+ deps
24355
+ );
24356
+ return true;
24357
+ }
24358
+ function retargetPageLinks(change, deps) {
24359
+ const { oldPath, newPath, oldTitle, newTitle } = change;
24360
+ if (!oldPath || !newPath || normalizePath2(oldPath) === normalizePath2(newPath)) return 0;
24361
+ const content = deps.getEditContent();
24362
+ const delta = {};
24363
+ for (const anchor of Array.from(document.querySelectorAll("[data-ohw-href-key]"))) {
24364
+ const hrefKey = anchor.getAttribute("data-ohw-href-key");
24365
+ if (!isPageLinkKey(hrefKey)) continue;
24366
+ const href = currentHref(hrefKey, anchor, content);
24367
+ if (!pointsAt(href, oldPath)) continue;
24368
+ const nextHref = retargetHref(href, oldPath, newPath);
24369
+ deps.applyLinkByKey(hrefKey, nextHref);
24370
+ delta[hrefKey] = nextHref;
24371
+ if (!oldTitle || !newTitle) continue;
24372
+ const labelKey = labelKeyFor(hrefKey);
24373
+ const label = content[labelKey] ?? anchor.querySelector('[data-ohw-editable="text"]')?.textContent ?? "";
24374
+ if (label.trim() !== oldTitle) continue;
24375
+ setLabelText(labelKey, newTitle);
24376
+ delta[labelKey] = newTitle;
24377
+ }
24378
+ commit(delta, deps);
24379
+ return Object.keys(delta).filter((key) => key.endsWith("-href")).length;
24380
+ }
24381
+
24086
24382
  // src/ui/navbar-container-chrome.tsx
24087
24383
  var import_lucide_react21 = require("lucide-react");
24088
24384
  var import_jsx_runtime37 = require("react/jsx-runtime");
@@ -24970,7 +25266,7 @@ function getNavigationSelectionParent(el) {
24970
25266
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
24971
25267
  return resolveFooterLinksContainerFrom(el);
24972
25268
  }
24973
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
25269
+ 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)) {
24974
25270
  return getNavigationRoot(el);
24975
25271
  }
24976
25272
  return null;
@@ -25192,7 +25488,6 @@ var ICONS = {
25192
25488
  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"/>',
25193
25489
  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"/>'
25194
25490
  };
25195
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
25196
25491
  var SELECTION_CHROME_GAP2 = 4;
25197
25492
  var TOOLBAR_STROKE_GAP2 = 4;
25198
25493
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -25290,7 +25585,7 @@ function applyVisibleViewport(el, parentScroll) {
25290
25585
  el.style.top = `${top}px`;
25291
25586
  el.style.height = `${height}px`;
25292
25587
  }
25293
- var TOOLBAR_EDGE_MARGIN3 = 4;
25588
+ var TOOLBAR_EDGE_MARGIN4 = 4;
25294
25589
  function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
25295
25590
  const isAbove = transform.includes("translateY(-100%)");
25296
25591
  let visualTop = isAbove ? top - approxH : top;
@@ -25317,8 +25612,8 @@ function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
25317
25612
  function clampToolbarLeft(centerX, toolbarWidth) {
25318
25613
  const half = toolbarWidth / 2;
25319
25614
  return Math.max(
25320
- TOOLBAR_EDGE_MARGIN3 + half,
25321
- Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN3 - half)
25615
+ TOOLBAR_EDGE_MARGIN4 + half,
25616
+ Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN4 - half)
25322
25617
  );
25323
25618
  }
25324
25619
  function calcToolbarPos(rect, parentScroll, toolbarW = 306) {
@@ -26198,21 +26493,6 @@ function OhhwellsBridge() {
26198
26493
  const [isItemDragging, setIsItemDragging] = (0, import_react21.useState)(false);
26199
26494
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react21.useState)(false);
26200
26495
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
26201
- const [floatingPanel, setFloatingPanel] = (0, import_react21.useState)(null);
26202
- const floatingPanelOpenRef = (0, import_react21.useRef)(false);
26203
- floatingPanelOpenRef.current = floatingPanel !== null;
26204
- const floatingPanelRef = (0, import_react21.useRef)(null);
26205
- floatingPanelRef.current = floatingPanel;
26206
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react21.useState)(null);
26207
- const buttonCollectionDragRef = (0, import_react21.useRef)(null);
26208
- const buttonDropInsertIndexRef = (0, import_react21.useRef)(null);
26209
- const [buttonDropSlots, setButtonDropSlots] = (0, import_react21.useState)([]);
26210
- const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react21.useState)(null);
26211
- const buttonPointerDragRef = (0, import_react21.useRef)(null);
26212
- const [buttonStyleTick, setButtonStyleTick] = (0, import_react21.useState)(0);
26213
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react21.useState)(null);
26214
- const [editorViewport, setEditorViewport] = (0, import_react21.useState)("desktop");
26215
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react21.useState)(null);
26216
26496
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react21.useState)(null);
26217
26497
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react21.useState)(null);
26218
26498
  const footerDragRef = (0, import_react21.useRef)(null);
@@ -26238,6 +26518,21 @@ function OhhwellsBridge() {
26238
26518
  const stylesRef = (0, import_react21.useRef)("");
26239
26519
  const appliedDrawerRef = (0, import_react21.useRef)(null);
26240
26520
  const pendingDeleteUndoRef = (0, import_react21.useRef)(null);
26521
+ const [floatingPanel, setFloatingPanel] = (0, import_react21.useState)(null);
26522
+ const floatingPanelOpenRef = (0, import_react21.useRef)(false);
26523
+ const floatingPanelRef = (0, import_react21.useRef)(null);
26524
+ floatingPanelRef.current = floatingPanel;
26525
+ const setFloatingPanelRef = (0, import_react21.useRef)(setFloatingPanel);
26526
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react21.useState)(null);
26527
+ const buttonCollectionDragRef = (0, import_react21.useRef)(null);
26528
+ const buttonDropInsertIndexRef = (0, import_react21.useRef)(null);
26529
+ const [buttonDropSlots, setButtonDropSlots] = (0, import_react21.useState)([]);
26530
+ const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react21.useState)(null);
26531
+ const buttonPointerDragRef = (0, import_react21.useRef)(null);
26532
+ const [buttonStyleTick, setButtonStyleTick] = (0, import_react21.useState)(0);
26533
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react21.useState)(null);
26534
+ const [editorViewport, setEditorViewport] = (0, import_react21.useState)("desktop");
26535
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react21.useState)(null);
26241
26536
  const [sitePages, setSitePages] = (0, import_react21.useState)([]);
26242
26537
  const collectionItemsRef = (0, import_react21.useRef)(/* @__PURE__ */ new Map());
26243
26538
  const [sectionsByPath, setSectionsByPath] = (0, import_react21.useState)({});
@@ -26247,7 +26542,18 @@ function OhhwellsBridge() {
26247
26542
  const linkPopoverOpenRef = (0, import_react21.useRef)(false);
26248
26543
  const linkPopoverGraceUntilRef = (0, import_react21.useRef)(0);
26249
26544
  setLinkPopoverRef.current = setLinkPopover;
26545
+ setFloatingPanelRef.current = setFloatingPanel;
26250
26546
  linkPopoverSessionRef.current = linkPopover;
26547
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
26548
+ (0, import_react21.useEffect)(() => {
26549
+ const syncViewport = () => {
26550
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
26551
+ setEditorViewport((prev) => prev === next ? prev : next);
26552
+ };
26553
+ syncViewport();
26554
+ window.addEventListener("resize", syncViewport);
26555
+ return () => window.removeEventListener("resize", syncViewport);
26556
+ }, []);
26251
26557
  (0, import_react21.useEffect)(() => {
26252
26558
  setSectionOrderPersistHandler((entries) => {
26253
26559
  const currentPath = window.location.pathname;
@@ -26396,6 +26702,43 @@ function OhhwellsBridge() {
26396
26702
  if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
26397
26703
  return () => window.removeEventListener("message", onSitePages);
26398
26704
  }, [isEditMode, postToParent2]);
26705
+ (0, import_react21.useEffect)(() => {
26706
+ if (!isEditMode) return;
26707
+ const deps = {
26708
+ getEditContent: () => editContentRef.current,
26709
+ setEditContent: (next) => {
26710
+ editContentRef.current = next;
26711
+ },
26712
+ applyLinkByKey,
26713
+ postToParent: postToParent2
26714
+ };
26715
+ const onPageMessage = (e) => {
26716
+ if (e.data?.type === "ow:page-created") {
26717
+ const { path, title } = e.data;
26718
+ if (typeof path !== "string" || !path) return;
26719
+ if (addNavLinkForPage({ path, title: typeof title === "string" && title ? title : path }, deps)) {
26720
+ enforceLinkHrefs();
26721
+ }
26722
+ return;
26723
+ }
26724
+ if (e.data?.type === "ow:page-renamed") {
26725
+ const { oldPath, newPath, oldTitle, newTitle } = e.data;
26726
+ if (typeof oldPath !== "string" || typeof newPath !== "string") return;
26727
+ const moved = retargetPageLinks(
26728
+ {
26729
+ oldPath,
26730
+ newPath,
26731
+ oldTitle: typeof oldTitle === "string" ? oldTitle : void 0,
26732
+ newTitle: typeof newTitle === "string" ? newTitle : void 0
26733
+ },
26734
+ deps
26735
+ );
26736
+ if (moved > 0) enforceLinkHrefs();
26737
+ }
26738
+ };
26739
+ window.addEventListener("message", onPageMessage);
26740
+ return () => window.removeEventListener("message", onPageMessage);
26741
+ }, [isEditMode, postToParent2]);
26399
26742
  (0, import_react21.useEffect)(() => {
26400
26743
  if (!isEditMode || shouldUseDevFixtures()) return;
26401
26744
  void loadAllSectionsManifest().then((manifest) => {
@@ -27944,6 +28287,7 @@ function OhhwellsBridge() {
27944
28287
  applyAiFooterFromContent(content);
27945
28288
  if (typeof content[STYLE_STORE_KEY] === "string") {
27946
28289
  stylesRef.current = content[STYLE_STORE_KEY];
28290
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
27947
28291
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
27948
28292
  }
27949
28293
  applyBrandChrome(content);
@@ -27954,15 +28298,16 @@ function OhhwellsBridge() {
27954
28298
  if (key === "__ohw_sections") continue;
27955
28299
  if (key === AI_SECTIONS_KEY) continue;
27956
28300
  if (key === AI_FOOTER_KEY) continue;
28301
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
28302
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
27957
28303
  if (key === BRAND_KIT_KEY) continue;
27958
28304
  if (key === STYLE_STORE_KEY) continue;
27959
28305
  if (BRAND_CHROME_KEYS.has(key)) continue;
27960
- if (key === LOGO_PLACEHOLDER_KEY) continue;
27961
28306
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
27962
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
27963
28307
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
27964
28308
  continue;
27965
28309
  }
28310
+ if (key.endsWith(SECTION_EMBED_SUFFIX)) continue;
27966
28311
  if (applyVideoSettingNode(key, val)) continue;
27967
28312
  if (applyCarouselNode(key, val)) continue;
27968
28313
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -28002,6 +28347,7 @@ function OhhwellsBridge() {
28002
28347
  });
28003
28348
  applyLinkByKey(key, val);
28004
28349
  }
28350
+ applySectionEmbeds(content);
28005
28351
  applyLogoFromContent(content);
28006
28352
  applyLogoSizes(content);
28007
28353
  reconcileNavbarItemsFromContent(content);
@@ -28190,20 +28536,22 @@ function OhhwellsBridge() {
28190
28536
  }
28191
28537
  applyAiFooterFromContent(content);
28192
28538
  if (typeof content[STYLE_STORE_KEY] === "string") {
28539
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
28193
28540
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
28194
28541
  }
28195
28542
  for (const [key, val] of Object.entries(content)) {
28196
28543
  if (key === "__ohw_sections") continue;
28197
28544
  if (key === AI_SECTIONS_KEY) continue;
28198
28545
  if (key === AI_FOOTER_KEY) continue;
28546
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
28547
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
28199
28548
  if (key === BRAND_KIT_KEY) continue;
28200
28549
  if (key === STYLE_STORE_KEY) continue;
28201
28550
  if (BRAND_CHROME_KEYS.has(key)) continue;
28202
- if (key === LOGO_PLACEHOLDER_KEY) continue;
28203
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
28204
28551
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
28205
28552
  continue;
28206
28553
  }
28554
+ if (key.endsWith(SECTION_EMBED_SUFFIX)) continue;
28207
28555
  if (applyVideoSettingNode(key, val)) continue;
28208
28556
  if (applyCarouselNode(key, val)) continue;
28209
28557
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -28228,6 +28576,7 @@ function OhhwellsBridge() {
28228
28576
  });
28229
28577
  applyLinkByKey(key, val);
28230
28578
  }
28579
+ applySectionEmbeds(content);
28231
28580
  applyLogoFromContent(content);
28232
28581
  reconcileNavbarItemsFromContent(content);
28233
28582
  reconcileFooterOrderFromContent(content);
@@ -28467,26 +28816,11 @@ function OhhwellsBridge() {
28467
28816
  const t2 = setTimeout(measure, 500);
28468
28817
  const ro = new ResizeObserver(schedule);
28469
28818
  ro.observe(document.body);
28470
- let lastWidth = window.innerWidth;
28471
- let resizeTimers = [];
28472
- const clearResizeTimers = () => {
28473
- resizeTimers.forEach(clearTimeout);
28474
- resizeTimers = [];
28475
- };
28476
- const handleResize = () => {
28477
- if (window.innerWidth === lastWidth) return;
28478
- lastWidth = window.innerWidth;
28479
- clearResizeTimers();
28480
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
28481
- };
28482
- window.addEventListener("resize", handleResize);
28483
28819
  return () => {
28484
28820
  clearTimeout(t1);
28485
28821
  clearTimeout(t2);
28486
28822
  if (raf != null) cancelAnimationFrame(raf);
28487
28823
  ro.disconnect();
28488
- clearResizeTimers();
28489
- window.removeEventListener("resize", handleResize);
28490
28824
  };
28491
28825
  }, [pathname, isEditMode, postToParent2]);
28492
28826
  (0, import_react21.useEffect)(() => {
@@ -28783,6 +29117,13 @@ function OhhwellsBridge() {
28783
29117
  if (target.closest("[data-ohw-state-toggle]")) return;
28784
29118
  if (target.closest("[data-ohw-max-badge]")) return;
28785
29119
  if (isInsideLinkEditor(target)) return;
29120
+ if (isInsideFloatingPanel(target)) return;
29121
+ if (target.closest("[data-ohw-form-toolbar]")) return;
29122
+ if (target.closest(
29123
+ '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
29124
+ )) {
29125
+ return;
29126
+ }
28786
29127
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
28787
29128
  clearMediaSelectionRef.current();
28788
29129
  }
@@ -28806,8 +29147,6 @@ function OhhwellsBridge() {
28806
29147
  return;
28807
29148
  }
28808
29149
  }
28809
- if (isInsideFloatingPanel(target)) return;
28810
- if (target.closest("[data-ohw-form-toolbar]")) return;
28811
29150
  const collectionItem = matchCollectionItemClick(target, collectionItemsRef.current);
28812
29151
  if (collectionItem) {
28813
29152
  e.preventDefault();
@@ -28819,11 +29158,6 @@ function OhhwellsBridge() {
28819
29158
  });
28820
29159
  return;
28821
29160
  }
28822
- if (target.closest(
28823
- '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
28824
- )) {
28825
- return;
28826
- }
28827
29161
  {
28828
29162
  const formEl = getFormElement(target);
28829
29163
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -28977,9 +29311,13 @@ function OhhwellsBridge() {
28977
29311
  if (isIconEditable(editable) && !getSocialItem(editable)) {
28978
29312
  e.preventDefault();
28979
29313
  e.stopPropagation();
28980
- selectIconElementRef.current(editable);
28981
29314
  const aiIcon = editable.hasAttribute("data-ai-icon");
28982
29315
  const featureIcon = aiIcon && /\.features\.\d+\.icon$/.test(editable.dataset.ohwKey ?? "");
29316
+ if (aiIcon) {
29317
+ aiSectionApiRef.current?.selectFromElement(editable);
29318
+ } else {
29319
+ selectIconElementRef.current(editable);
29320
+ }
28983
29321
  postToParentRef.current({
28984
29322
  type: "ow:icon-pick",
28985
29323
  key: editable.dataset.ohwKey ?? "",
@@ -29207,6 +29545,9 @@ function OhhwellsBridge() {
29207
29545
  setHoveredItemRect(null);
29208
29546
  hoveredNavContainerRef.current = null;
29209
29547
  setHoveredNavContainerRect(null);
29548
+ siblingHintElRef.current = null;
29549
+ setSiblingHintRect(null);
29550
+ setSiblingHintRects([]);
29210
29551
  return;
29211
29552
  }
29212
29553
  {
@@ -29327,7 +29668,6 @@ function OhhwellsBridge() {
29327
29668
  hoveredNavContainerRef.current = null;
29328
29669
  setHoveredNavContainerRect(null);
29329
29670
  hoveredItemElRef.current = editable;
29330
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
29331
29671
  }
29332
29672
  }
29333
29673
  }
@@ -30037,7 +30377,11 @@ function OhhwellsBridge() {
30037
30377
  };
30038
30378
  const handleMouseMove = (e) => {
30039
30379
  const { clientX, clientY } = e;
30040
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
30380
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
30381
+ dismissImageHover();
30382
+ clearImageHover();
30383
+ return;
30384
+ }
30041
30385
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) {
30042
30386
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
30043
30387
  formHoverElRef.current = null;
@@ -30066,7 +30410,12 @@ function OhhwellsBridge() {
30066
30410
  setHoveredItemRect(null);
30067
30411
  hoveredNavContainerRef.current = null;
30068
30412
  setHoveredNavContainerRect(null);
30413
+ siblingHintElRef.current = null;
30414
+ setSiblingHintRect(null);
30415
+ setSiblingHintRects([]);
30069
30416
  dismissImageHover();
30417
+ clearImageHover();
30418
+ setSectionGap(null);
30070
30419
  return;
30071
30420
  }
30072
30421
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -30078,7 +30427,11 @@ function OhhwellsBridge() {
30078
30427
  if (e.data?.type !== "ow:pointer-sync") return;
30079
30428
  const { clientX, clientY } = e.data;
30080
30429
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
30081
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
30430
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
30431
+ dismissImageHover();
30432
+ clearImageHover();
30433
+ return;
30434
+ }
30082
30435
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) return;
30083
30436
  if (probeSocialsRowAt(clientX, clientY)) return;
30084
30437
  probeSectionGapAt(clientX, clientY);
@@ -30458,6 +30811,7 @@ function OhhwellsBridge() {
30458
30811
  applyAiFooterFromContent(content);
30459
30812
  if (typeof content[STYLE_STORE_KEY] === "string") {
30460
30813
  stylesRef.current = content[STYLE_STORE_KEY];
30814
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
30461
30815
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
30462
30816
  }
30463
30817
  applyBrandChrome(content);
@@ -30472,15 +30826,16 @@ function OhhwellsBridge() {
30472
30826
  }
30473
30827
  if (key === AI_SECTIONS_KEY) continue;
30474
30828
  if (key === AI_FOOTER_KEY) continue;
30829
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
30830
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
30475
30831
  if (key === BRAND_KIT_KEY) continue;
30476
30832
  if (key === STYLE_STORE_KEY) continue;
30477
30833
  if (BRAND_CHROME_KEYS.has(key)) continue;
30478
- if (key === LOGO_PLACEHOLDER_KEY) continue;
30479
30834
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
30480
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
30481
30835
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
30482
30836
  continue;
30483
30837
  }
30838
+ if (key.endsWith(SECTION_EMBED_SUFFIX)) continue;
30484
30839
  if (applyVideoSettingNode(key, val)) continue;
30485
30840
  if (applyCarouselNode(key, val)) continue;
30486
30841
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -30506,6 +30861,7 @@ function OhhwellsBridge() {
30506
30861
  });
30507
30862
  applyLinkByKey(key, val);
30508
30863
  }
30864
+ applySectionEmbeds(content);
30509
30865
  applyLogoFromContent(content);
30510
30866
  applyLogoSizes(content);
30511
30867
  document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
@@ -31113,6 +31469,12 @@ function OhhwellsBridge() {
31113
31469
  closeLinkPopoverRef.current();
31114
31470
  return;
31115
31471
  }
31472
+ if (floatingPanelOpenRef.current) {
31473
+ setFloatingPanelRef.current(null);
31474
+ deselectRef.current();
31475
+ deactivateRef.current();
31476
+ return;
31477
+ }
31116
31478
  deselectRef.current();
31117
31479
  deactivateRef.current();
31118
31480
  clearMediaSelectionRef.current();
@@ -31501,7 +31863,7 @@ function OhhwellsBridge() {
31501
31863
  const sectionEl = foundEl && !foundEl.hasAttribute("data-ohw-ai-template-hidden") && !foundEl.hasAttribute("data-ohw-ai-removed") && !foundEl.hasAttribute("data-ohw-ai-replaced-by") ? foundEl : null;
31502
31864
  const nodes = sectionEl ? collectEditableNodes(void 0, sectionEl).filter(
31503
31865
  (n) => n.key && !n.key.startsWith("__ohw_") && !["meta", "sections", "video-setting"].includes(n.type)
31504
- ).map((n) => ({ ...n, text: n.text ?? "", type: normalizeAiNodeType(n.type) })) : [];
31866
+ ).map((n) => ({ ...n, text: n.text ?? "", type: normalizeAiNodeType(n.type) })).map((n) => withNodeFontSize(n, sectionEl)) : [];
31505
31867
  const items = sectionEl ? collectItemGroups(sectionEl) : [];
31506
31868
  const box = sectionEl?.getBoundingClientRect();
31507
31869
  const rect = box ? {
@@ -31948,10 +32310,10 @@ function OhhwellsBridge() {
31948
32310
  window.removeEventListener("message", handleDuplicateSection);
31949
32311
  window.removeEventListener("message", handleDeactivate);
31950
32312
  window.removeEventListener("message", handleSelectNav);
31951
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
31952
32313
  window.removeEventListener("message", handleToastAction);
31953
32314
  window.removeEventListener("message", handleFormCount);
31954
32315
  window.removeEventListener("message", handleUiEscape);
32316
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
31955
32317
  autoSaveTimers.current.forEach(clearTimeout);
31956
32318
  autoSaveTimers.current.clear();
31957
32319
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -32245,7 +32607,7 @@ function OhhwellsBridge() {
32245
32607
  postToParent2({
32246
32608
  type: "ow:ready",
32247
32609
  version: "1",
32248
- bridgeVersion: "0.1.159",
32610
+ bridgeVersion: "0.1.160-next.428",
32249
32611
  path: pathname,
32250
32612
  nodes: collectEditableNodes(editContentRef.current),
32251
32613
  sections